
The "expiry" is the Unix timestamp when the lease expires. This is not at all a useful parameter, in particular because the system's clock can be reset. Instead, we should expose the lease receive time stamp (in CLOCK_BOOTTIME), and the lease lifetime. Anyway. So, we somehow need to express infinite lifetimes. Previously, we would use the special value 4294967295 (2^32-1). However, that value does not seem so great, because it's also the Unix timestamp of 2106-02-07T06:28:15+0000. While that is quite far in the future, it's a valid timestamp still. Of course, the code worked around that by never setting a timestamp larger than 4294967295-1, but it still limits the range of what we can expose. Note that for the lifetime "dhcp_lease_time", we do express infinity with 4294967295. That's fine, it also does not contradict what we receive in the DHCP lease on the wire because the lifetime there is expressed by a 32 bit integer. Instead, for the "expiry" timestamp, don't perform such triming. The expiry timestamp is just the start timestamp plus the lease lifetime. If that is larger than 2106-02-07, so be it. On the other hand, express infinity by omitting the "expiry" field.
1356 lines
40 KiB
C
1356 lines
40 KiB
C
// SPDX-License-Identifier: LGPL-2.1+
|
|
/*
|
|
* 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-dhcp-options.h"
|
|
#include "nm-core-utils.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "platform/nm-platform.h"
|
|
#include "nm-dhcp-client-logging.h"
|
|
#include "n-dhcp4/src/n-dhcp4.h"
|
|
#include "systemd/nm-sd-utils-shared.h"
|
|
#include "systemd/nm-sd-utils-dhcp.h"
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define NM_TYPE_DHCP_NETTOOLS (nm_dhcp_nettools_get_type ())
|
|
#define NM_DHCP_NETTOOLS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettools))
|
|
#define NM_DHCP_NETTOOLS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass))
|
|
#define NM_IS_DHCP_NETTOOLS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_NETTOOLS))
|
|
#define NM_IS_DHCP_NETTOOLS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_NETTOOLS))
|
|
#define NM_DHCP_NETTOOLS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass))
|
|
|
|
typedef struct _NMDhcpNettools NMDhcpNettools;
|
|
typedef struct _NMDhcpNettoolsClass NMDhcpNettoolsClass;
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
NDhcp4Client *client;
|
|
NDhcp4ClientProbe *probe;
|
|
NDhcp4ClientLease *lease;
|
|
GIOChannel *channel;
|
|
guint event_id;
|
|
char *lease_file;
|
|
} NMDhcpNettoolsPrivate;
|
|
|
|
struct _NMDhcpNettools {
|
|
NMDhcpClient parent;
|
|
NMDhcpNettoolsPrivate _priv;
|
|
};
|
|
|
|
struct _NMDhcpNettoolsClass {
|
|
NMDhcpClientClass parent;
|
|
};
|
|
|
|
G_DEFINE_TYPE (NMDhcpNettools, nm_dhcp_nettools, NM_TYPE_DHCP_CLIENT)
|
|
|
|
#define NM_DHCP_NETTOOLS_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpNettools, NM_IS_DHCP_NETTOOLS)
|
|
|
|
/*****************************************************************************/
|
|
|
|
#define DHCP_MAX_FQDN_LENGTH 255
|
|
|
|
enum {
|
|
DHCP_FQDN_FLAG_S = (1 << 0),
|
|
DHCP_FQDN_FLAG_O = (1 << 1),
|
|
DHCP_FQDN_FLAG_E = (1 << 2),
|
|
DHCP_FQDN_FLAG_N = (1 << 3),
|
|
};
|
|
|
|
enum {
|
|
NM_IN_ADDR_CLASS_A,
|
|
NM_IN_ADDR_CLASS_B,
|
|
NM_IN_ADDR_CLASS_C,
|
|
NM_IN_ADDR_CLASS_INVALID,
|
|
};
|
|
|
|
static int
|
|
in_addr_class (struct in_addr addr)
|
|
{
|
|
switch (ntohl (addr.s_addr) >> 24) {
|
|
case 0 ... 127:
|
|
return NM_IN_ADDR_CLASS_A;
|
|
case 128 ... 191:
|
|
return NM_IN_ADDR_CLASS_B;
|
|
case 192 ... 223:
|
|
return NM_IN_ADDR_CLASS_C;
|
|
default:
|
|
return NM_IN_ADDR_CLASS_INVALID;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
lease_option_consume (void *out,
|
|
size_t n_out,
|
|
uint8_t **datap,
|
|
size_t *n_datap)
|
|
{
|
|
if (*n_datap < n_out)
|
|
return FALSE;
|
|
|
|
memcpy (out, *datap, n_out);
|
|
*datap += n_out;
|
|
*n_datap -= n_out;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
lease_option_next_in_addr (struct in_addr *addrp,
|
|
uint8_t **datap,
|
|
size_t *n_datap)
|
|
{
|
|
return lease_option_consume (addrp, sizeof (struct in_addr), datap, n_datap);
|
|
}
|
|
|
|
static gboolean
|
|
lease_option_next_route (struct in_addr *destp,
|
|
uint8_t *plenp,
|
|
struct in_addr *gatewayp,
|
|
gboolean classless,
|
|
uint8_t **datap,
|
|
size_t *n_datap)
|
|
{
|
|
struct in_addr dest = {}, gateway;
|
|
uint8_t *data = *datap;
|
|
size_t n_data = *n_datap;
|
|
uint8_t plen;
|
|
|
|
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;
|
|
}
|
|
|
|
static gboolean
|
|
lease_parse_address (NDhcp4ClientLease *lease,
|
|
NMIP4Config *ip4_config,
|
|
GHashTable *options,
|
|
GError **error)
|
|
{
|
|
char addr_str[NM_UTILS_INET_ADDRSTRLEN];
|
|
const gint64 ts = nm_utils_get_monotonic_timestamp_ns ();
|
|
const gint64 ts_clock_boottime = nm_utils_monotonic_timestamp_as_boottime (ts, 1);
|
|
struct in_addr a_address;
|
|
struct in_addr a_netmask;
|
|
struct in_addr a_next_server;
|
|
guint32 a_plen;
|
|
guint64 nettools_lifetime;
|
|
gint64 a_lifetime;
|
|
gint64 a_expiry;
|
|
|
|
n_dhcp4_client_lease_get_yiaddr (lease, &a_address);
|
|
if (a_address.s_addr == INADDR_ANY) {
|
|
nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get address from lease");
|
|
return FALSE;
|
|
}
|
|
|
|
/* n_dhcp4_client_lease_get_lifetime() never fails */
|
|
n_dhcp4_client_lease_get_lifetime (lease, &nettools_lifetime);
|
|
/* FIXME: n_dhcp4_client_lease_get_lifetime() returns the time in nsec of CLOCK_BOOTTIME.
|
|
* We want to retrieve the original lifetime value in seconds, so we approximate it in a_lifetime.
|
|
* Use a nettools API to retrieve the original value as passed by the server.
|
|
*/
|
|
if (nettools_lifetime == G_MAXUINT64) {
|
|
a_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
|
|
a_expiry = -1;
|
|
} else {
|
|
gint64 ts_time = time (NULL);
|
|
|
|
a_lifetime = ((gint64) nettools_lifetime - ts_clock_boottime + (NM_UTILS_NS_PER_SECOND / 2)) / NM_UTILS_NS_PER_SECOND;
|
|
/* A lease time of 0 is allowed on some dhcp servers, so, let's accept it. */
|
|
if (a_lifetime < 0)
|
|
a_lifetime = 0;
|
|
else if (a_lifetime > NM_PLATFORM_LIFETIME_PERMANENT)
|
|
a_lifetime = NM_PLATFORM_LIFETIME_PERMANENT - 1;
|
|
|
|
a_expiry = ts_time + a_lifetime;
|
|
}
|
|
|
|
if (!lease_get_in_addr (lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) {
|
|
nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get netmask from lease");
|
|
return FALSE;
|
|
}
|
|
|
|
nm_utils_inet4_ntop (a_address.s_addr, addr_str);
|
|
a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
|
|
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
|
|
addr_str);
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
|
|
nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
|
|
|
|
nm_dhcp_option_add_option_u64 (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
|
|
(guint64) a_lifetime);
|
|
|
|
if (a_expiry != -1) {
|
|
nm_dhcp_option_add_option_u64 (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
|
|
(guint64) a_expiry);
|
|
}
|
|
|
|
|
|
n_dhcp4_client_lease_get_siaddr (lease, &a_next_server);
|
|
if (a_next_server.s_addr != INADDR_ANY) {
|
|
nm_utils_inet4_ntop (a_next_server.s_addr, addr_str);
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
|
|
addr_str);
|
|
}
|
|
|
|
nm_ip4_config_add_address (ip4_config,
|
|
&((const NMPlatformIP4Address) {
|
|
.address = a_address.s_addr,
|
|
.peer_address = a_address.s_addr,
|
|
.plen = a_plen,
|
|
.addr_source = NM_IP_CONFIG_SOURCE_DHCP,
|
|
.timestamp = ts / NM_UTILS_NS_PER_SECOND,
|
|
.lifetime = a_lifetime,
|
|
.preferred = a_lifetime,
|
|
}));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
|
|
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, NM_DHCP_OPTION_DHCP4_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);
|
|
}
|
|
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,
|
|
str->str);
|
|
}
|
|
|
|
static void
|
|
lease_parse_routes (NDhcp4ClientLease *lease,
|
|
NMIP4Config *ip4_config,
|
|
GHashTable *options,
|
|
guint32 route_table,
|
|
guint32 route_metric)
|
|
{
|
|
nm_auto_free_gstring GString *str = NULL;
|
|
char dest_str[NM_UTILS_INET_ADDRSTRLEN];
|
|
char gateway_str[NM_UTILS_INET_ADDRSTRLEN];
|
|
const char *s;
|
|
struct in_addr dest, gateway;
|
|
uint8_t plen;
|
|
guint32 m;
|
|
gboolean has_router_from_classless = FALSE, has_classless = FALSE;
|
|
guint32 default_route_metric = route_metric;
|
|
uint8_t *data;
|
|
size_t n_data;
|
|
int r;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, &data, &n_data);
|
|
if (!r) {
|
|
nm_gstring_prepare (&str);
|
|
|
|
has_classless = TRUE;
|
|
|
|
while (lease_option_next_route (&dest, &plen, &gateway, TRUE, &data, &n_data)) {
|
|
|
|
nm_utils_inet4_ntop (dest.s_addr, dest_str);
|
|
nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
|
|
|
|
g_string_append_printf (nm_gstring_add_space_delimiter (str),
|
|
"%s/%d %s",
|
|
dest_str,
|
|
(int) plen,
|
|
gateway_str);
|
|
|
|
if (plen == 0) {
|
|
/* if there are multiple default routes, we add them with differing
|
|
* metrics. */
|
|
m = default_route_metric;
|
|
if (default_route_metric < G_MAXUINT32)
|
|
default_route_metric++;
|
|
|
|
has_router_from_classless = TRUE;
|
|
} else {
|
|
m = route_metric;
|
|
}
|
|
|
|
nm_ip4_config_add_route (ip4_config,
|
|
&((const NMPlatformIP4Route) {
|
|
.network = dest.s_addr,
|
|
.plen = plen,
|
|
.gateway = gateway.s_addr,
|
|
.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
|
|
.metric = m,
|
|
.table_coerced = nm_platform_route_table_coerce (route_table),
|
|
}),
|
|
NULL);
|
|
}
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
|
|
str->str);
|
|
}
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &data, &n_data);
|
|
if (!r) {
|
|
nm_gstring_prepare (&str);
|
|
|
|
while (lease_option_next_route (&dest, &plen, &gateway, FALSE, &data, &n_data)) {
|
|
|
|
nm_utils_inet4_ntop (dest.s_addr, dest_str);
|
|
nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
|
|
|
|
g_string_append_printf (nm_gstring_add_space_delimiter (str),
|
|
"%s/%d %s",
|
|
dest_str,
|
|
(int) plen,
|
|
gateway_str);
|
|
|
|
if (has_classless) {
|
|
/* RFC 3443: if the DHCP server returns both a Classless Static Routes
|
|
* option and a Static Routes option, the DHCP client MUST ignore the
|
|
* Static Routes option. */
|
|
continue;
|
|
}
|
|
|
|
if (plen == 0) {
|
|
/* for option 33 (static route), RFC 2132 says:
|
|
*
|
|
* The default route (0.0.0.0) is an illegal destination for a static
|
|
* route. */
|
|
continue;
|
|
}
|
|
|
|
nm_ip4_config_add_route (ip4_config,
|
|
&((const NMPlatformIP4Route) {
|
|
.network = dest.s_addr,
|
|
.plen = plen,
|
|
.gateway = gateway.s_addr,
|
|
.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
|
|
.metric = route_metric,
|
|
.table_coerced = nm_platform_route_table_coerce (route_table),
|
|
}),
|
|
NULL);
|
|
}
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
|
|
str->str);
|
|
}
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_ROUTER, &data, &n_data);
|
|
if (!r) {
|
|
nm_gstring_prepare (&str);
|
|
|
|
while (lease_option_next_in_addr (&gateway, &data, &n_data)) {
|
|
s = nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
|
|
g_string_append (nm_gstring_add_space_delimiter (str), s);
|
|
|
|
if (gateway.s_addr == 0) {
|
|
/* silently skip 0.0.0.0 */
|
|
continue;
|
|
}
|
|
|
|
if (has_router_from_classless) {
|
|
/* If the DHCP server returns both a Classless Static Routes option and a
|
|
* Router option, the DHCP client MUST ignore the Router option [RFC 3442].
|
|
*
|
|
* Be more lenient and ignore the Router option only if Classless Static
|
|
* Routes contain a default gateway (as other DHCP backends do).
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
/* if there are multiple default routes, we add them with differing
|
|
* metrics. */
|
|
m = default_route_metric;
|
|
if (default_route_metric < G_MAXUINT32)
|
|
default_route_metric++;
|
|
|
|
nm_ip4_config_add_route (ip4_config,
|
|
&((const NMPlatformIP4Route) {
|
|
.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
|
|
.gateway = gateway.s_addr,
|
|
.table_coerced = nm_platform_route_table_coerce (route_table),
|
|
.metric = m,
|
|
}),
|
|
NULL);
|
|
}
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_ROUTER,
|
|
str->str);
|
|
}
|
|
}
|
|
|
|
static void
|
|
lease_parse_mtu (NDhcp4ClientLease *lease,
|
|
NMIP4Config *ip4_config,
|
|
GHashTable *options)
|
|
{
|
|
uint16_t mtu;
|
|
|
|
if (!lease_get_u16 (lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &mtu))
|
|
return;
|
|
|
|
if (mtu < 68)
|
|
return;
|
|
|
|
nm_dhcp_option_add_option_u64 (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
|
|
mtu);
|
|
nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
|
|
}
|
|
|
|
static void
|
|
lease_parse_metered (NDhcp4ClientLease *lease,
|
|
NMIP4Config *ip4_config,
|
|
GHashTable *options)
|
|
{
|
|
gboolean metered = FALSE;
|
|
uint8_t *data;
|
|
size_t n_data;
|
|
int r;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &data, &n_data);
|
|
if (r) {
|
|
metered = FALSE;
|
|
} else {
|
|
metered = !!memmem (data, n_data, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
|
|
}
|
|
|
|
/* TODO: expose the vendor specific option when present */
|
|
nm_ip4_config_set_metered (ip4_config, metered);
|
|
}
|
|
|
|
static void
|
|
lease_parse_ntps (NDhcp4ClientLease *lease,
|
|
GHashTable *options)
|
|
{
|
|
nm_auto_free_gstring GString *str = NULL;
|
|
char addr_str[NM_UTILS_INET_ADDRSTRLEN];
|
|
struct in_addr addr;
|
|
uint8_t *data;
|
|
size_t n_data;
|
|
int r;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_NTP_SERVER, &data, &n_data);
|
|
if (r)
|
|
return;
|
|
|
|
nm_gstring_prepare (&str);
|
|
|
|
while (lease_option_next_in_addr (&addr, &data, &n_data)) {
|
|
nm_utils_inet4_ntop (addr.s_addr, addr_str);
|
|
g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
|
|
}
|
|
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_NTP_SERVER,
|
|
str->str);
|
|
}
|
|
|
|
static void
|
|
lease_parse_hostname (NDhcp4ClientLease *lease,
|
|
GHashTable *options)
|
|
{
|
|
nm_auto_free_gstring GString *str = NULL;
|
|
uint8_t *data;
|
|
size_t n_data;
|
|
int r;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &data, &n_data);
|
|
if (r)
|
|
return;
|
|
|
|
str = g_string_new_len ((char *)data, n_data);
|
|
|
|
if (is_localhost(str->str))
|
|
return;
|
|
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_HOST_NAME,
|
|
str->str);
|
|
}
|
|
|
|
static void
|
|
lease_parse_domainname (NDhcp4ClientLease *lease,
|
|
NMIP4Config *ip4_config,
|
|
GHashTable *options)
|
|
{
|
|
nm_auto_free_gstring GString *str = NULL;
|
|
gs_strfreev char **domains = NULL;
|
|
uint8_t *data;
|
|
size_t n_data;
|
|
int r;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &data, &n_data);
|
|
if (r)
|
|
return;
|
|
|
|
str = g_string_new_len ((char *)data, n_data);
|
|
|
|
/* Multiple domains sometimes stuffed into option 15 "Domain Name". */
|
|
domains = g_strsplit (str->str, " ", 0);
|
|
nm_gstring_prepare (&str);
|
|
|
|
for (char **d = domains; *d; d++) {
|
|
if (is_localhost(*d))
|
|
return;
|
|
|
|
g_string_append (nm_gstring_add_space_delimiter (str), *d);
|
|
nm_ip4_config_add_domain (ip4_config, *d);
|
|
}
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,
|
|
str->str);
|
|
}
|
|
|
|
static void
|
|
lease_parse_search_domains (NDhcp4ClientLease *lease,
|
|
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, NM_DHCP_OPTION_DHCP4_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);
|
|
}
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
|
|
str->str);
|
|
}
|
|
|
|
static void
|
|
lease_parse_root_path (NDhcp4ClientLease *lease,
|
|
GHashTable *options)
|
|
{
|
|
nm_auto_free_gstring GString *str = NULL;
|
|
uint8_t *data;
|
|
size_t n_data;
|
|
int r;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &data, &n_data);
|
|
if (r)
|
|
return;
|
|
|
|
str = g_string_new_len ((char *)data, n_data);
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_ROOT_PATH,
|
|
str->str);
|
|
}
|
|
|
|
static void
|
|
lease_parse_wpad (NDhcp4ClientLease *lease,
|
|
GHashTable *options)
|
|
{
|
|
gs_free char *wpad = NULL;
|
|
uint8_t *data;
|
|
size_t n_data;
|
|
int r;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, &data, &n_data);
|
|
if (r)
|
|
return;
|
|
|
|
nm_utils_buf_utf8safe_escape ((char *)data, n_data, 0, &wpad);
|
|
if (wpad == NULL)
|
|
wpad = g_strndup ((char *)data, n_data);
|
|
|
|
nm_dhcp_option_add_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
|
|
wpad);
|
|
}
|
|
|
|
static void
|
|
lease_parse_private_options (NDhcp4ClientLease *lease,
|
|
GHashTable *options)
|
|
{
|
|
int i;
|
|
|
|
for (i = NM_DHCP_OPTION_DHCP4_PRIVATE_224; i <= NM_DHCP_OPTION_DHCP4_PRIVATE_254; i++) {
|
|
gs_free char *option_string = NULL;
|
|
guint8 *data;
|
|
gsize n_data;
|
|
int r;
|
|
|
|
/* We manage private options 249 (private classless static route) and 252 (wpad) in a special
|
|
* way, so skip them as we here just manage all (the other) private options as raw data */
|
|
if (NM_IN_SET (i, NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE,
|
|
NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY))
|
|
continue;
|
|
|
|
r = n_dhcp4_client_lease_query (lease, i, &data, &n_data);
|
|
if (r)
|
|
continue;
|
|
|
|
option_string = nm_utils_bin2hexstr_full (data, n_data, ':', FALSE, NULL);
|
|
nm_dhcp_option_take_option (options,
|
|
_nm_dhcp_option_dhcp4_options,
|
|
i,
|
|
g_steal_pointer (&option_string));
|
|
}
|
|
}
|
|
|
|
static NMIP4Config *
|
|
lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
|
|
const char *iface,
|
|
int ifindex,
|
|
NDhcp4ClientLease *lease,
|
|
guint32 route_table,
|
|
guint32 route_metric,
|
|
GHashTable **out_options,
|
|
GError **error)
|
|
{
|
|
gs_unref_object NMIP4Config *ip4_config = NULL;
|
|
gs_unref_hashtable GHashTable *options = NULL;
|
|
|
|
g_return_val_if_fail (lease != NULL, NULL);
|
|
|
|
ip4_config = nm_ip4_config_new (multi_idx, ifindex);
|
|
options = nm_dhcp_option_create_options_dict ();
|
|
|
|
if (!lease_parse_address (lease, ip4_config, options, error))
|
|
return NULL;
|
|
|
|
lease_parse_routes (lease, ip4_config, options, route_table, route_metric);
|
|
lease_parse_domain_name_servers (lease, ip4_config, options);
|
|
lease_parse_domainname (lease, ip4_config, options);
|
|
lease_parse_search_domains (lease, ip4_config, options);
|
|
lease_parse_mtu (lease, ip4_config, options);
|
|
lease_parse_metered (lease, ip4_config, options);
|
|
|
|
lease_parse_hostname (lease, options);
|
|
lease_parse_ntps (lease, options);
|
|
lease_parse_root_path (lease, options);
|
|
lease_parse_wpad (lease, options);
|
|
lease_parse_private_options (lease, options);
|
|
|
|
NM_SET_OUT (out_options, g_steal_pointer (&options));
|
|
return g_steal_pointer (&ip4_config);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
lease_save (NDhcp4ClientLease *lease, const char *lease_file)
|
|
{
|
|
struct in_addr a_address;
|
|
nm_auto_free_gstring GString *new_contents = NULL;
|
|
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
|
|
|
|
nm_assert (lease);
|
|
nm_assert (lease_file);
|
|
|
|
new_contents = g_string_new ("# This is private data. Do not parse.\n");
|
|
|
|
n_dhcp4_client_lease_get_yiaddr (lease, &a_address);
|
|
if (a_address.s_addr == INADDR_ANY)
|
|
return;
|
|
|
|
g_string_append_printf (new_contents,
|
|
"ADDRESS=%s\n", nm_utils_inet4_ntop (a_address.s_addr, sbuf));
|
|
|
|
g_file_set_contents (lease_file,
|
|
new_contents->str,
|
|
-1,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
bound4_handle (NMDhcpNettools *self, NDhcp4ClientLease *lease)
|
|
{
|
|
NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
|
|
const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
|
|
gs_unref_object NMIP4Config *ip4_config = NULL;
|
|
gs_unref_hashtable GHashTable *options = NULL;
|
|
GError *error = NULL;
|
|
|
|
_LOGT ("lease available");
|
|
|
|
ip4_config = lease_to_ip4_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
|
|
iface,
|
|
nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
|
|
lease,
|
|
nm_dhcp_client_get_route_table (NM_DHCP_CLIENT (self)),
|
|
nm_dhcp_client_get_route_metric (NM_DHCP_CLIENT (self)),
|
|
&options,
|
|
&error);
|
|
if (!ip4_config) {
|
|
_LOGW ("%s", error->message);
|
|
g_clear_error (&error);
|
|
nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
|
|
return;
|
|
}
|
|
|
|
nm_dhcp_option_add_requests_to_options (options, _nm_dhcp_option_dhcp4_options);
|
|
lease_save (lease, priv->lease_file);
|
|
|
|
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;
|
|
GBytes *bcast_hwaddr;
|
|
const uint8_t *hwaddr_arr;
|
|
const uint8_t *bcast_hwaddr_arr;
|
|
gsize hwaddr_len;
|
|
gsize bcast_hwaddr_len;
|
|
GBytes *client_id;
|
|
gs_unref_bytes GBytes *client_id_new = NULL;
|
|
const uint8_t *client_id_arr;
|
|
size_t client_id_len;
|
|
int r, fd, arp_type, transport;
|
|
|
|
g_return_val_if_fail (!priv->client, FALSE);
|
|
|
|
hwaddr = nm_dhcp_client_get_hw_addr (NM_DHCP_CLIENT (self));
|
|
if ( !hwaddr
|
|
|| !(hwaddr_arr = g_bytes_get_data (hwaddr, &hwaddr_len))
|
|
|| (arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len)) < 0) {
|
|
nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
|
|
return FALSE;
|
|
}
|
|
|
|
bcast_hwaddr = nm_dhcp_client_get_broadcast_hw_addr (NM_DHCP_CLIENT (self));
|
|
bcast_hwaddr_arr = g_bytes_get_data (bcast_hwaddr, &bcast_hwaddr_len);
|
|
|
|
switch (arp_type) {
|
|
case ARPHRD_ETHER:
|
|
transport = N_DHCP4_TRANSPORT_ETHERNET;
|
|
break;
|
|
case ARPHRD_INFINIBAND:
|
|
transport = N_DHCP4_TRANSPORT_INFINIBAND;
|
|
break;
|
|
default:
|
|
nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "unsupported ARP type");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Note that we always set a client-id. In particular for infiniband that is necessary,
|
|
* see https://tools.ietf.org/html/rfc4390#section-2.1 . */
|
|
client_id = nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self));
|
|
if (!client_id) {
|
|
client_id_new = nm_utils_dhcp_client_id_mac (arp_type, hwaddr_arr, hwaddr_len);
|
|
client_id = client_id_new;
|
|
}
|
|
|
|
if ( !(client_id_arr = g_bytes_get_data (client_id, &client_id_len))
|
|
|| client_id_len < 2) {
|
|
|
|
/* invalid client-ids are not expected. */
|
|
nm_assert_not_reached ();
|
|
|
|
nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "no valid IPv4 client-id");
|
|
return FALSE;
|
|
}
|
|
|
|
r = n_dhcp4_client_config_new (&config);
|
|
if (r) {
|
|
nm_utils_error_set_errno (error, r, "failed to create client-config: %s");
|
|
return FALSE;
|
|
}
|
|
|
|
n_dhcp4_client_config_set_ifindex (config, nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)));
|
|
n_dhcp4_client_config_set_transport (config, transport);
|
|
n_dhcp4_client_config_set_mac (config, hwaddr_arr, hwaddr_len);
|
|
n_dhcp4_client_config_set_broadcast_mac (config, bcast_hwaddr_arr, bcast_hwaddr_len);
|
|
r = n_dhcp4_client_config_set_client_id (config,
|
|
client_id_arr,
|
|
NM_MIN (client_id_len, 1 + _NM_SD_MAX_CLIENT_ID_LEN));
|
|
if (r) {
|
|
nm_utils_error_set_errno (error, r, "failed to set client-id: %s");
|
|
return FALSE;
|
|
}
|
|
|
|
r = n_dhcp4_client_new (&client, config);
|
|
if (r) {
|
|
nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "failed to create client: error %d", r);
|
|
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_errno (error, r, "failed to accept lease: %s");
|
|
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_errno (error, r, "failed to decline lease: %s");
|
|
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);
|
|
gs_free char *lease_file = NULL;
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* FIXME:
|
|
* Select, or configure, a reasonable start delay, to protect poor servers beeing flooded.
|
|
*/
|
|
n_dhcp4_client_probe_config_set_start_delay (config, 1);
|
|
|
|
nm_dhcp_utils_get_leasefile_path (AF_INET,
|
|
"internal",
|
|
nm_dhcp_client_get_iface (client),
|
|
nm_dhcp_client_get_uuid (client),
|
|
&lease_file);
|
|
|
|
if (last_ip4_address)
|
|
inet_pton (AF_INET, last_ip4_address, &last_addr);
|
|
else {
|
|
/*
|
|
* TODO: we stick to the systemd-networkd lease file format. Quite easy for now to
|
|
* just use the functions in systemd code. Anyway, as in the end we just use the
|
|
* ip address from all the options found in the lease, write a function that parses
|
|
* the lease file just for the assigned address and returns it in &last_address.
|
|
* Then drop reference to systemd-networkd structures and functions.
|
|
*/
|
|
nm_auto (sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
|
|
|
|
dhcp_lease_load (&lease, lease_file);
|
|
if (lease)
|
|
sd_dhcp_lease_get_address (lease, &last_addr);
|
|
}
|
|
|
|
if (last_addr.s_addr)
|
|
n_dhcp4_client_probe_config_set_requested_ip (config, last_addr);
|
|
|
|
/* Add requested options */
|
|
for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) {
|
|
if (_nm_dhcp_option_dhcp4_options[i].include) {
|
|
nm_assert (_nm_dhcp_option_dhcp4_options[i].option_num <= 255);
|
|
n_dhcp4_client_probe_config_request_option (config,
|
|
_nm_dhcp_option_dhcp4_options[i].option_num);
|
|
}
|
|
}
|
|
|
|
hostname = nm_dhcp_client_get_hostname (client);
|
|
if (hostname) {
|
|
if (nm_dhcp_client_get_use_fqdn (client)) {
|
|
uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
|
|
|
|
buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
|
|
DHCP_FQDN_FLAG_E; /* Canonical wire format */
|
|
buffer[1] = 0; /* RCODE1 (deprecated) */
|
|
buffer[2] = 0; /* RCODE2 (deprecated) */
|
|
|
|
r = nm_sd_dns_name_to_wire_format (hostname,
|
|
buffer + 3,
|
|
sizeof (buffer) - 3,
|
|
FALSE);
|
|
if (r < 0) {
|
|
nm_utils_error_set_errno (error, r, "failed to convert DHCP FQDN: %s");
|
|
return FALSE;
|
|
}
|
|
|
|
r = n_dhcp4_client_probe_config_append_option (config,
|
|
NM_DHCP_OPTION_DHCP4_CLIENT_FQDN,
|
|
buffer,
|
|
3 + r);
|
|
if (r) {
|
|
nm_utils_error_set_errno (error, r, "failed to set DHCP FQDN: %s");
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
r = n_dhcp4_client_probe_config_append_option (config,
|
|
NM_DHCP_OPTION_DHCP4_HOST_NAME,
|
|
hostname,
|
|
strlen (hostname));
|
|
if (r) {
|
|
nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_free (priv->lease_file);
|
|
priv->lease_file = g_steal_pointer (&lease_file);
|
|
|
|
r = n_dhcp4_client_probe (priv->client, &priv->probe, config);
|
|
if (r) {
|
|
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 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->lease_file, g_free);
|
|
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->accept = _accept;
|
|
client_class->decline = decline;
|
|
client_class->stop = stop;
|
|
}
|
|
|
|
const NMDhcpClientFactory _nm_dhcp_client_factory_nettools = {
|
|
.name = "nettools",
|
|
.get_type = nm_dhcp_nettools_get_type,
|
|
.experimental = TRUE,
|
|
};
|