diff options
-rw-r--r-- | Makefile.am | 47 | ||||
-rwxr-xr-x | contrib/scripts/checkpatch.pl | 2 | ||||
-rw-r--r-- | meson_options.txt | 2 | ||||
-rw-r--r-- | shared/meson.build | 37 | ||||
-rw-r--r-- | src/dhcp/nm-dhcp-client.h | 1 | ||||
-rw-r--r-- | src/dhcp/nm-dhcp-listener.c | 3 | ||||
-rw-r--r-- | src/dhcp/nm-dhcp-manager.h | 2 | ||||
-rw-r--r-- | src/dhcp/nm-dhcp-nettools.c | 1336 | ||||
-rw-r--r-- | src/meson.build | 2 | ||||
-rw-r--r-- | src/nm-iface-helper.c | 3 |
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, }; /*****************************************************************************/ |