summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Gundersen <teg@jklm.no>2019-05-13 20:02:48 +0200
committerBeniamino Galvani <bgalvani@redhat.com>2019-07-05 11:04:32 +0200
commit6adade6f21d5fefd0a00abee64f99ff96a54f0f3 (patch)
tree777f195bc2c032492ae3f58aed2012ee414a8976
parent401fee7c2040e87d7f9c83fd350733b0f538d578 (diff)
dhcp: add nettools dhcp4 client
This is inspired by the existing systemd integration, with a few differences: * This parses the WPAD option, which systemd requested, but did not use. * We hook into the DAD handling, only making use of the configured address once DAD has completed successfully, and declining the lease if it fails. There are still many areas of possible improvement. In particular, we need to ensure the parsing of all options are compliant, as n-dhcp4 treats all options as opaque, unlike sd-dhcp4. We probably also need to look at how to handle failures and retries (in particular if we decline a lease). We need to query the current MTU at client startu, as well as the hardware broadcast address. Both these are provided by the kernel over netlink, so it should simply be a matter of hooking that up with NM's netlink layer. Contribution under LGPL2.0+, in addition to stated licenses.
-rw-r--r--Makefile.am47
-rwxr-xr-xcontrib/scripts/checkpatch.pl2
-rw-r--r--meson_options.txt2
-rw-r--r--shared/meson.build37
-rw-r--r--src/dhcp/nm-dhcp-client.h1
-rw-r--r--src/dhcp/nm-dhcp-listener.c3
-rw-r--r--src/dhcp/nm-dhcp-manager.h2
-rw-r--r--src/dhcp/nm-dhcp-nettools.c1336
-rw-r--r--src/meson.build2
-rw-r--r--src/nm-iface-helper.c3
10 files changed, 1429 insertions, 6 deletions
diff --git a/Makefile.am b/Makefile.am
index 3f918b04e4..22787d62b9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -266,6 +266,45 @@ endif
###############################################################################
+noinst_LTLIBRARIES += shared/libndhcp4.la
+
+shared_libndhcp4_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ -std=c11 \
+ -Wno-error=declaration-after-statement \
+ -Wno-pointer-arith \
+ $(NULL)
+
+shared_libndhcp4_la_CPPFLAGS = \
+ -D_GNU_SOURCE \
+ $(CODE_COVERAGE_CFLAGS) \
+ $(SANITIZER_LIB_CFLAGS) \
+ -I$(srcdir)/shared/c-stdaux/src \
+ -I$(srcdir)/shared/c-list/src \
+ -I$(srcdir)/shared/c-siphash/src \
+ $(NULL)
+
+shared_libndhcp4_la_LDFLAGS = \
+ $(SANITIZER_LIB_LDFLAGS)
+
+shared_libndhcp4_la_SOURCES = \
+ shared/n-dhcp4/src/n-dhcp4-c-connection.c \
+ shared/n-dhcp4/src/n-dhcp4-c-lease.c \
+ shared/n-dhcp4/src/n-dhcp4-c-probe.c \
+ shared/n-dhcp4/src/n-dhcp4-client.c \
+ shared/n-dhcp4/src/n-dhcp4-incoming.c \
+ shared/n-dhcp4/src/n-dhcp4-outgoing.c \
+ shared/n-dhcp4/src/n-dhcp4-private.h \
+ shared/n-dhcp4/src/n-dhcp4-socket.c \
+ shared/n-dhcp4/src/n-dhcp4.h \
+ shared/n-dhcp4/src/util/packet.c \
+ shared/n-dhcp4/src/util/packet.h \
+ shared/n-dhcp4/src/util/socket.c \
+ shared/n-dhcp4/src/util/socket.h \
+ $(NULL)
+
+###############################################################################
+
noinst_LTLIBRARIES += shared/nm-std-aux/libnm-std-aux.la
shared_nm_std_aux_libnm_std_aux_la_CPPFLAGS = \
@@ -1866,7 +1905,9 @@ EXTRA_DIST += \
###############################################################################
-src_libNetworkManagerBase_la_CPPFLAGS = $(src_cppflags)
+src_libNetworkManagerBase_la_CPPFLAGS = \
+ $(libsystemd_cppflags) \
+ $(src_cppflags)
src_libNetworkManagerBase_la_SOURCES = \
\
@@ -1920,6 +1961,7 @@ src_libNetworkManagerBase_la_SOURCES = \
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-systemd.c \
@@ -2141,6 +2183,7 @@ src_libNetworkManager_la_LIBADD = \
src/libnm-systemd-core.la \
shared/systemd/libnm-systemd-shared.la \
shared/libnacd.la \
+ shared/libndhcp4.la \
shared/libcrbtree.la \
shared/libcsiphash.la \
$(GLIB_LIBS) \
@@ -2228,6 +2271,7 @@ src_nm_iface_helper_LDADD = \
shared/nm-std-aux/libnm-std-aux.la \
src/libnm-systemd-core.la \
shared/systemd/libnm-systemd-shared.la \
+ shared/libndhcp4.la \
shared/libcsiphash.la \
$(GLIB_LIBS) \
$(LIBUDEV_LIBS) \
@@ -2275,6 +2319,7 @@ src_initrd_nm_initrd_generator_LDADD = \
shared/systemd/libnm-systemd-shared.la \
shared/nm-glib-aux/libnm-glib-aux.la \
shared/nm-std-aux/libnm-std-aux.la \
+ shared/libndhcp4.la \
shared/libcsiphash.la \
$(GLIB_LIBS) \
$(NULL)
diff --git a/contrib/scripts/checkpatch.pl b/contrib/scripts/checkpatch.pl
index f50b37d413..b5af6dfc2f 100755
--- a/contrib/scripts/checkpatch.pl
+++ b/contrib/scripts/checkpatch.pl
@@ -181,7 +181,7 @@ next if $filename =~ /\/nm-[^\/]+-enum-types\.[ch]$/;
next if $filename =~ /\bsrc\/systemd\//
and not $filename =~ /\/sd-adapt\//
and not $filename =~ /\/nm-/;
-next if $filename =~ /\/(n-acd|c-list|c-siphash)\//;
+next if $filename =~ /\/(n-acd|c-list|c-siphash|n-dhcp4)\//;
complain ('Tabs are only allowed at the beginning of a line') if $line =~ /[^\t]\t/;
complain ('Trailing whitespace') if $line =~ /[ \t]$/;
diff --git a/meson_options.txt b/meson_options.txt
index 3c9a84a8ad..3ffd9a54be 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -53,7 +53,7 @@ option('config_dns_rc_manager_default', type: 'combo', choices: ['symlink', 'fil
option('dhclient', type: 'string', value: '', description: 'Enable dhclient support')
option('dhcpcanon', type: 'string', value: '', description: 'Enable dhcpcanon support (experimental)')
option('dhcpcd', type: 'string', value: '', description: 'Enable dhcpcd support')
-option('config_dhcp_default', type: 'combo', choices: ['dhcpcanon', 'dhclient', 'dhcpcd', 'internal'], value: 'internal', description: 'Default configuration option for main.dhcp setting, used as fallback if the configuration option is unset')
+option('config_dhcp_default', type: 'combo', choices: ['dhcpcanon', 'dhclient', 'dhcpcd', 'internal', 'nettools'], value: 'internal', description: 'Default configuration option for main.dhcp setting, used as fallback if the configuration option is unset')
# miscellaneous
option('introspection', type: 'boolean', value: true, description: 'Enable introspection for this build')
diff --git a/shared/meson.build b/shared/meson.build
index b29a7c91b7..6407c699e9 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -88,6 +88,43 @@ shared_n_acd_dep = declare_dependency(
###############################################################################
+shared_n_dhcp4 = static_library(
+ 'n-dhcp4',
+ sources: files('n-dhcp4/src/n-dhcp4-c-connection.c',
+ 'n-dhcp4/src/n-dhcp4-c-lease.c',
+ 'n-dhcp4/src/n-dhcp4-c-probe.c',
+ 'n-dhcp4/src/n-dhcp4-client.c',
+ 'n-dhcp4/src/n-dhcp4-incoming.c',
+ 'n-dhcp4/src/n-dhcp4-outgoing.c',
+ 'n-dhcp4/src/n-dhcp4-private.h',
+ 'n-dhcp4/src/n-dhcp4-socket.c',
+ 'n-dhcp4/src/n-dhcp4.h',
+ 'n-dhcp4/src/util/packet.c',
+ 'n-dhcp4/src/util/packet.h',
+ 'n-dhcp4/src/util/socket.c',
+ 'n-dhcp4/src/util/socket.h'),
+ c_args: [
+ '-D_GNU_SOURCE',
+ '-Wno-declaration-after-statement',
+ '-Wno-pointer-arith',
+ ],
+ include_directories: [
+ include_directories('c-list/src'),
+ include_directories('c-siphash/src'),
+ include_directories('c-stdaux/src'),
+ ],
+ dependencies: [
+ shared_c_siphash_dep,
+ ],
+)
+
+shared_n_dhcp4_dep = declare_dependency(
+ include_directories: shared_inc,
+ link_with: shared_n_dhcp4,
+)
+
+###############################################################################
+
version_conf = configuration_data()
version_conf.set('NM_MAJOR_VERSION', nm_major_version)
version_conf.set('NM_MINOR_VERSION', nm_minor_version)
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 33030a4f23..dd61cd9959 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -213,5 +213,6 @@ 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_nettools;
#endif /* __NETWORKMANAGER_DHCP_CLIENT_H__ */
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index d0656ca707..a6e4fbd202 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -38,7 +38,7 @@
/*****************************************************************************/
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5] = {
/* the order here matters, as we will try the plugins in this order to find
* the first available plugin. */
@@ -52,6 +52,7 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
&_nm_dhcp_client_factory_dhcpcd,
#endif
&_nm_dhcp_client_factory_internal,
+ &_nm_dhcp_client_factory_nettools,
};
/*****************************************************************************/
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index f66930db11..0125813556 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -84,7 +84,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6 (NMDhcpManager *manager,
/* For testing only */
extern const char* nm_dhcp_helper_path;
-extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4];
+extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5];
void nmtst_dhcp_manager_unget (gpointer singleton_instance);
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/dhcp/nm-dhcp-nettools.c
new file mode 100644
index 0000000000..fee29a603d
--- /dev/null
+++ b/src/dhcp/nm-dhcp-nettools.c
@@ -0,0 +1,1336 @@
+/* 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
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014-2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <net/if_arp.h>
+
+#include "nm-sd-adapt-shared.h"
+#include "hostname-util.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-core-utils.h"
+#include "NetworkManagerUtils.h"
+#include "platform/nm-platform.h"
+#include "nm-dhcp-client-logging.h"
+#include "n-dhcp4/src/n-dhcp4.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;
+
+static GType nm_dhcp_nettools_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+ NDhcp4Client *client;
+ NDhcp4ClientProbe *probe;
+ NDhcp4ClientLease *lease;
+ GIOChannel *channel;
+ guint event_id;
+} 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)
+
+/*****************************************************************************/
+
+#define DHCP_OPTION_SUBNET_MASK 1
+#define DHCP_OPTION_TIME_OFFSET 2
+#define DHCP_OPTION_ROUTER 3
+#define DHCP_OPTION_DOMAIN_NAME_SERVER 6
+#define DHCP_OPTION_HOST_NAME 12
+#define DHCP_OPTION_DOMAIN_NAME 15
+#define DHCP_OPTION_ROOT_PATH 17
+#define DHCP_OPTION_INTERFACE_MTU 26
+#define DHCP_OPTION_BROADCAST 28
+#define DHCP_OPTION_STATIC_ROUTE 33
+#define DHCP_OPTION_NIS_DOMAIN 40
+#define DHCP_OPTION_NIS_SERVERS 41
+#define DHCP_OPTION_NTP_SERVER 42
+#define DHCP_OPTION_VENDOR_SPECIFIC 43
+#define DHCP_OPTION_IP_ADDRESS_LEASE_TIME 51
+#define DHCP_OPTION_SERVER_IDENTIFIER 54
+#define DHCP_OPTION_CLIENT_IDENTIFIER 61
+#define DHCP_OPTION_DOMAIN_SEARCH_LIST 119
+#define DHCP_OPTION_CLASSLESS_STATIC_ROUTE 121
+#define DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE 249
+#define DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY 252
+
+/* Internal values */
+#define DHCP_OPTION_IP_ADDRESS 1024
+#define DHCP_OPTION_EXPIRY 1025
+
+enum {
+ NM_IN_ADDR_CLASS_A,
+ NM_IN_ADDR_CLASS_B,
+ NM_IN_ADDR_CLASS_C,
+ NM_IN_ADDR_CLASS_INVALID,
+};
+
+typedef struct {
+ const char *name;
+ uint16_t option_num;
+ bool include;
+} ReqOption;
+
+#define REQPREFIX "requested_"
+
+#define REQ(_num, _name, _include) \
+ { \
+ .name = REQPREFIX""_name, \
+ .option_num = _num, \
+ .include = _include, \
+ }
+
+static const ReqOption dhcp4_requests[] = {
+ REQ (DHCP_OPTION_SUBNET_MASK, "subnet_mask", TRUE ),
+ REQ (DHCP_OPTION_TIME_OFFSET, "time_offset", TRUE ),
+ REQ (DHCP_OPTION_DOMAIN_NAME_SERVER, "domain_name_servers", TRUE ),
+ REQ (DHCP_OPTION_HOST_NAME, "host_name", TRUE ),
+ REQ (DHCP_OPTION_DOMAIN_NAME, "domain_name", TRUE ),
+ REQ (DHCP_OPTION_INTERFACE_MTU, "interface_mtu", TRUE ),
+ REQ (DHCP_OPTION_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 (DHCP_OPTION_CLASSLESS_STATIC_ROUTE, "rfc3442_classless_static_routes", TRUE ),
+ REQ (DHCP_OPTION_ROUTER, "routers", TRUE ),
+ REQ (DHCP_OPTION_STATIC_ROUTE, "static_routes", TRUE ),
+ REQ (DHCP_OPTION_NIS_DOMAIN, "nis_domain", TRUE ),
+ REQ (DHCP_OPTION_NIS_SERVERS, "nis_servers", TRUE ),
+ REQ (DHCP_OPTION_NTP_SERVER, "ntp_servers", TRUE ),
+ REQ (DHCP_OPTION_SERVER_IDENTIFIER, "dhcp_server_identifier", TRUE ),
+ REQ (DHCP_OPTION_DOMAIN_SEARCH_LIST, "domain_search", TRUE ),
+ REQ (DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, "ms_classless_static_routes", TRUE ),
+ REQ (DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, "wpad", TRUE ),
+ REQ (DHCP_OPTION_ROOT_PATH, "root_path", TRUE ),
+
+ /* Internal values */
+ REQ (DHCP_OPTION_IP_ADDRESS_LEASE_TIME, "expiry", FALSE ),
+ REQ (DHCP_OPTION_CLIENT_IDENTIFIER, "dhcp_client_identifier", FALSE ),
+ REQ (DHCP_OPTION_IP_ADDRESS, "ip_address", FALSE ),
+
+ { 0 }
+};
+
+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 void
+take_option (GHashTable *options,
+ const ReqOption *requests,
+ guint option,
+ char *value)
+{
+ guint i;
+
+ nm_assert (options);
+ nm_assert (requests);
+ nm_assert (value);
+
+ for (i = 0; requests[i].name; i++) {
+ nm_assert (g_str_has_prefix (requests[i].name, REQPREFIX));
+ if (requests[i].option_num == option) {
+ g_hash_table_insert (options,
+ (gpointer) (requests[i].name + NM_STRLEN (REQPREFIX)),
+ value);
+ return;
+ }
+ }
+
+ /* Option should always be found */
+ nm_assert_not_reached ();
+}
+
+static void
+add_option (GHashTable *options, const ReqOption *requests, guint option, const char *value)
+{
+ if (options)
+ take_option (options, requests, option, g_strdup (value));
+}
+
+static void
+add_option_u64 (GHashTable *options, const ReqOption *requests, guint option, guint64 value)
+{
+ if (options)
+ take_option (options, requests, option, g_strdup_printf ("%" G_GUINT64_FORMAT, value));
+}
+
+static void
+add_requests_to_options (GHashTable *options, const ReqOption *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"));
+ }
+}
+
+static GHashTable *
+create_options_dict (void)
+{
+ return g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+}
+
+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;
+
+ if (classless) {
+ if (!lease_option_consume (&plen, sizeof (plen), &data, &n_data))
+ return FALSE;
+
+ if (plen > 32)
+ return FALSE;
+
+ if (!lease_option_consume (&dest, plen / 8, &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, '.');
+ 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;
+}
+
+#define LOG_LEASE(domain, ...) \
+ G_STMT_START { \
+ _LOG2I ((domain), (iface), " "__VA_ARGS__); \
+ } G_STMT_END
+
+static gboolean
+lease_parse_address (NDhcp4ClientLease *lease,
+ const char *iface,
+ NMIP4Config *ip4_config,
+ GHashTable *options,
+ GError **error)
+{
+ char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+ const gint64 ts = nm_utils_get_monotonic_timestamp_ns ();
+ struct in_addr a_address;
+ struct in_addr a_netmask;
+ guint32 a_plen;
+ guint64 a_lifetime;
+
+ n_dhcp4_client_lease_get_yiaddr (lease, &a_address);
+ n_dhcp4_client_lease_get_lifetime (lease, &a_lifetime);
+
+ if (!lease_get_in_addr (lease, DHCP_OPTION_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);
+
+ LOG_LEASE (LOGD_DHCP4, "address %s/%u", addr_str, a_plen);
+ add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, addr_str);
+ add_option (options,
+ dhcp4_requests,
+ DHCP_OPTION_SUBNET_MASK,
+ nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+
+ LOG_LEASE (LOGD_DHCP4, "expires in %u seconds",
+ (guint) ((a_lifetime - ts)/1000000000));
+ add_option_u64 (options,
+ dhcp4_requests,
+ DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
+ (guint64) (a_lifetime / 1000000000));
+
+ 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 / 1000000000,
+ .lifetime = (a_lifetime - ts) / 1000000000,
+ .preferred = (a_lifetime - ts) / 1000000000,
+ }));
+
+ return TRUE;
+}
+
+static void
+lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
+ const char *iface,
+ NMIP4Config *ip4_config,
+ 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, DHCP_OPTION_DOMAIN_NAME_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);
+
+ 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);
+ }
+
+ LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
+}
+
+static void
+lease_parse_routes (NDhcp4ClientLease *lease,
+ const char *iface,
+ 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, DHCP_OPTION_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);
+
+ LOG_LEASE (LOGD_DHCP4,
+ "classless static route %s/%d gw %s",
+ dest_str,
+ (int) plen,
+ 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);
+ }
+ add_option (options, dhcp4_requests, DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str->str);
+ }
+
+ r = n_dhcp4_client_lease_query (lease, DHCP_OPTION_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);
+
+ LOG_LEASE (LOGD_DHCP4,
+ "static route %s/%d gw %s",
+ dest_str,
+ (int) plen,
+ 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);
+ }
+ add_option (options, dhcp4_requests, DHCP_OPTION_STATIC_ROUTE, str->str);
+ }
+
+ r = n_dhcp4_client_lease_query (lease, DHCP_OPTION_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);
+ }
+ LOG_LEASE (LOGD_DHCP4, "router %s", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_ROUTER, str->str);
+ }
+}
+
+static void
+lease_parse_mtu (NDhcp4ClientLease *lease,
+ const char *iface,
+ NMIP4Config *ip4_config,
+ GHashTable *options)
+{
+ uint16_t mtu;
+
+ if (!lease_get_u16 (lease, DHCP_OPTION_INTERFACE_MTU, &mtu))
+ return;
+
+ if (mtu < 68)
+ return;
+
+ LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
+ add_option_u64 (options, dhcp4_requests, DHCP_OPTION_INTERFACE_MTU, mtu);
+ nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
+}
+
+static void
+lease_parse_metered (NDhcp4ClientLease *lease,
+ const char *iface,
+ NMIP4Config *ip4_config,
+ GHashTable *options)
+{
+ gboolean metered = FALSE;
+ uint8_t *data;
+ size_t n_data;
+ int r;
+
+ r = n_dhcp4_client_lease_query (lease, DHCP_OPTION_VENDOR_SPECIFIC, &data, &n_data);
+ if (r) {
+ metered = FALSE;
+ } else {
+ metered = !!memmem (data, n_data, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
+ }
+
+ LOG_LEASE (LOGD_DHCP4, "%s", metered ? "metered" : "unmetered");
+ nm_ip4_config_set_metered (ip4_config, metered);
+}
+
+static void
+lease_parse_ntps (NDhcp4ClientLease *lease,
+ const char *iface,
+ 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, DHCP_OPTION_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);
+ }
+
+ LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_NTP_SERVER, str->str);
+}
+
+static void
+lease_parse_hostname (NDhcp4ClientLease *lease,
+ const char *iface,
+ 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, DHCP_OPTION_HOST_NAME, &data, &n_data);
+ if (r)
+ return;
+
+ str = g_string_new_len ((char *)data, n_data);
+
+ if (is_localhost(str->str))
+ return;
+
+ LOG_LEASE (LOGD_DHCP4, "hostname '%s'", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_HOST_NAME, str->str);
+}
+
+static void
+lease_parse_domainname (NDhcp4ClientLease *lease,
+ const char *iface,
+ 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, DHCP_OPTION_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 (is_localhost(*d))
+ return;
+
+ g_string_append (nm_gstring_add_space_delimiter (str), *d);
+ nm_ip4_config_add_domain (ip4_config, *d);
+ }
+ LOG_LEASE (LOGD_DHCP4, "domain name '%s'", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_DOMAIN_NAME, str->str);
+}
+
+static void
+lease_parse_search_domains (NDhcp4ClientLease *lease,
+ const char *iface,
+ NMIP4Config *ip4_config,
+ GHashTable *options)
+{
+ nm_auto_free_gstring GString *str = NULL;
+ uint8_t *data, *cache;
+ size_t n_data, n_cache = 0;
+ int r;
+
+ r = n_dhcp4_client_lease_query (lease, DHCP_OPTION_DOMAIN_SEARCH_LIST, &data, &n_data);
+ if (r)
+ return;
+
+ cache = data;
+
+ nm_gstring_prepare (&str);
+
+ 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;
+
+ g_string_append (nm_gstring_add_space_delimiter (str), domain->str);
+ nm_ip4_config_add_search (ip4_config, domain->str);
+ }
+ LOG_LEASE (LOGD_DHCP4, "domain search '%s'", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
+}
+
+static void
+lease_parse_root_path (NDhcp4ClientLease *lease,
+ const char *iface,
+ 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, DHCP_OPTION_ROOT_PATH, &data, &n_data);
+ if (r)
+ return;
+
+ str = g_string_new_len ((char *)data, n_data);
+ LOG_LEASE (LOGD_DHCP4, "root path '%s'", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_ROOT_PATH, str->str);
+}
+
+static void
+lease_parse_wpad (NDhcp4ClientLease *lease,
+ const char *iface,
+ 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, DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, &data, &n_data);
+ if (r)
+ return;
+
+ str = g_string_new_len ((char *)data, n_data);
+ LOG_LEASE (LOGD_DHCP4, "wpad '%s'", str->str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, str->str);
+}
+
+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 = out_options ? create_options_dict () : NULL;
+
+ if (!lease_parse_address (lease, iface, ip4_config, options, error))
+ return NULL;
+
+ lease_parse_routes (lease, iface, ip4_config, options, route_table, route_metric);
+ lease_parse_domain_name_servers (lease, iface, ip4_config, options);
+ lease_parse_domainname (lease, iface, ip4_config, options);
+ lease_parse_search_domains (lease, iface, ip4_config, options);
+ lease_parse_mtu (lease, iface, ip4_config, options);
+ lease_parse_metered (lease, iface, ip4_config, options);
+
+ lease_parse_hostname (lease, iface, options);
+ lease_parse_ntps (lease, iface, options);
+ lease_parse_root_path (lease, iface, options);
+ lease_parse_wpad (lease, iface, options);
+
+ NM_SET_OUT (out_options, g_steal_pointer (&options));
+ return g_steal_pointer (&ip4_config);
+}
+
+/*****************************************************************************/
+
+static void
+bound4_handle (NMDhcpNettools *self, NDhcp4ClientLease *lease)
+{
+ 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");
+
+ 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;
+ }
+
+ add_requests_to_options (options, dhcp4_requests);
+
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+ NM_DHCP_STATE_BOUND,
+ NM_IP_CONFIG_CAST (ip4_config),
+ options);
+}
+
+static gboolean
+dhcp4_event_handle (NMDhcpNettools *self,
+ NDhcp4ClientEvent *event)
+{
+ NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+ int r;
+
+ _LOGT ("client event %d", event->event);
+
+ switch (event->event) {
+ case N_DHCP4_CLIENT_EVENT_OFFER:
+ /* always accept the first lease */
+ r = n_dhcp4_client_lease_select (event->offer.lease);
+ if (r) {
+ _LOGW ("selecting lease failed: %d", r);
+ }
+ break;
+ 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_RETRACTED:
+ 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);
+ break;
+ case N_DHCP4_CLIENT_EVENT_EXTENDED:
+ bound4_handle (self, event->extended.lease);
+ break;
+ case N_DHCP4_CLIENT_EVENT_DOWN:
+ /* ignore down events, they are purely informational */
+ break;
+ default:
+ _LOGW ("unhandled DHCP event %d", event->event);
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+dhcp4_event_cb (GIOChannel *source,
+ 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)
+ return G_SOURCE_CONTINUE;
+
+ 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;
+ const uint8_t *hwaddr_arr;
+ gsize 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;
+ }
+
+ 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) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "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, (unsigned char[]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, ETH_ALEN); /* XXX */
+ r = n_dhcp4_client_config_set_client_id (config, client_id_arr, client_id_len);
+ if (r) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "failed to set client-id");
+ return FALSE;
+ }
+
+ r = n_dhcp4_client_new (&client, config);
+ if (r) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "failed to create client");
+ return FALSE;
+ }
+
+ priv->client = client;
+ client = NULL;
+
+ n_dhcp4_client_get_fd (priv->client, &fd);
+ priv->channel = g_io_channel_unix_new (fd);
+ priv->event_id = g_io_add_watch (priv->channel, G_IO_IN, dhcp4_event_cb, self);
+
+ 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) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "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) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "failed to decline lease");
+ return FALSE;
+ }
+
+ priv->lease = n_dhcp4_client_lease_unref (priv->lease);
+
+ return TRUE;
+}
+
+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);
+ struct in_addr last_addr = { 0 };
+ const char *hostname;
+ 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) {
+ nm_utils_error_set_errno (error, r, "failed to create dhcp-client-probe-config: %s");
+ return FALSE;
+ }
+
+ /*
+ * XXX
+ * Select, or configure, a reasonable start delay, to protect poor servers beeing flooded.
+ */
+ n_dhcp4_client_probe_config_set_start_delay (config, 500);
+
+ if (last_ip4_address) {
+ inet_pton (AF_INET, last_ip4_address, &last_addr);
+ n_dhcp4_client_probe_config_set_requested_ip (config, last_addr);
+ }
+
+ /* Add requested options */
+ for (i = 0; dhcp4_requests[i].name; i++) {
+ if (dhcp4_requests[i].include) {
+ nm_assert (dhcp4_requests[i].option_num <= 255);
+ n_dhcp4_client_probe_config_request_option (config, dhcp4_requests[i].option_num);
+ }
+ }
+
+ hostname = nm_dhcp_client_get_hostname (client);
+ if (hostname) {
+ /* XXX: select hostname/FQDN */
+ r = n_dhcp4_client_probe_config_append_option (config,
+ DHCP_OPTION_HOST_NAME,
+ hostname,
+ strlen (hostname));
+ if (r) {
+ nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
+ return FALSE;
+ }
+ }
+
+ r = n_dhcp4_client_probe (priv->client, &priv->probe, config);
+ if (r) {
+ nm_utils_error_set_errno (error, r, "failed to start DHCP client: %s");
+ return FALSE;
+ }
+
+ _LOGT ("dhcp-client4: start %p", (gpointer) priv->client);
+
+ nm_dhcp_client_start_timeout (client);
+ return TRUE;
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+ const char *dhcp_anycast_addr,
+ const struct in6_addr *ll_addr,
+ NMSettingIP6ConfigPrivacy privacy,
+ guint needed_prefixes,
+ GError **error)
+{
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "nettools plugin does not support IPv6");
+ return FALSE;
+}
+
+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 ((NMDhcpNettools *) object);
+
+ nm_clear_pointer (&priv->channel, g_io_channel_unref);
+ nm_clear_g_source (&priv->event_id);
+ 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->ip6_start = ip6_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,
+ .get_path = NULL,
+};
diff --git a/src/meson.build b/src/meson.build
index a3ac4625ac..af0bcec99c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,6 +22,7 @@ cflags = nm_cflags
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',
'ndisc/nm-lndp-ndisc.c',
@@ -50,6 +51,7 @@ deps = [
libsystemd_dep,
libudev_dep,
libnm_core_dep,
+ shared_n_dhcp4_dep,
]
if enable_wext
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index 7f130ab4a9..adf64c7515 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -589,8 +589,9 @@ main (int argc, char *argv[])
/*****************************************************************************/
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5] = {
&_nm_dhcp_client_factory_internal,
+ &_nm_dhcp_client_factory_nettools,
};
/*****************************************************************************/