Commit 6adade6f authored by Tom Gundersen's avatar Tom Gundersen Committed by Beniamino Galvani

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.
parent 401fee7c
......@@ -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)
......
......@@ -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]$/;
......
......@@ -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')
......
......@@ -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)
......
......@@ -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__ */
......@@ -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,
};
/*****************************************************************************/
......
......@@ -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);
......
/* 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));