nm-dhcp-utils.c 21.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* 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) 2005 - 2010 Red Hat, Inc.
 *
 */

20
#include "nm-default.h"
21

22 23 24
#include <string.h>
#include <errno.h>
#include <unistd.h>
25
#include <arpa/inet.h>
26

27 28
#include "nm-utils/nm-dedup-multi.h"

29 30 31
#include "nm-dhcp-utils.h"
#include "nm-utils.h"
#include "NetworkManagerUtils.h"
32
#include "platform/nm-platform.h"
33
#include "nm-dhcp-client-logging.h"
34
#include "nm-core-internal.h"
35

36
/*****************************************************************************/
37 38

static gboolean
39 40
ip4_process_dhcpcd_rfc3442_routes (const char *iface,
                                   const char *str,
41
                                   guint32 route_table,
42
                                   guint32 route_metric,
43 44 45 46 47 48 49 50 51 52 53
                                   NMIP4Config *ip4_config,
                                   guint32 *gwaddr)
{
	char **routes, **r;
	gboolean have_routes = FALSE;

	routes = g_strsplit (str, " ", 0);
	if (g_strv_length (routes) == 0)
		goto out;

	if ((g_strv_length (routes) % 2) != 0) {
54
		_LOG2W (LOGD_DHCP4, iface, "  classless static routes provided, but invalid");
55 56 57 58 59 60 61 62 63 64 65 66 67 68
		goto out;
	}

	for (r = routes; *r; r += 2) {
		char *slash;
		NMPlatformIP4Route route;
		int rt_cidr = 32;
		guint32 rt_addr, rt_route;

		slash = strchr(*r, '/');
		if (slash) {
			*slash = '\0';
			errno = 0;
			rt_cidr = strtol (slash + 1, NULL, 10);
69
			if (errno || rt_cidr > 32) {
70
				_LOG2W (LOGD_DHCP4, iface, "DHCP provided invalid classless static route cidr: '%s'", slash + 1);
71 72 73 74
				continue;
			}
		}
		if (inet_pton (AF_INET, *r, &rt_addr) <= 0) {
75
			_LOG2W (LOGD_DHCP4, iface, "DHCP provided invalid classless static route address: '%s'", *r);
76 77 78
			continue;
		}
		if (inet_pton (AF_INET, *(r + 1), &rt_route) <= 0) {
79
			_LOG2W (LOGD_DHCP4, iface, "DHCP provided invalid classless static route gateway: '%s'", *(r + 1));
80 81 82 83 84 85 86 87
			continue;
		}

		have_routes = TRUE;
		if (rt_cidr == 0 && rt_addr == 0) {
			/* FIXME: how to handle multiple routers? */
			*gwaddr = rt_route;
		} else {
88
			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1));
89
			memset (&route, 0, sizeof (route));
90
			route.network = nm_utils_ip4_address_clear_host_address (rt_addr, rt_cidr);
91 92
			route.plen = rt_cidr;
			route.gateway = rt_route;
93
			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
94
			route.metric = route_metric;
95
			route.table_coerced = nm_platform_route_table_coerce (route_table);
96
			nm_ip4_config_add_route (ip4_config, &route, NULL);
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
		}
	}

out:
	g_strfreev (routes);
	return have_routes;
}

static const char **
process_dhclient_rfc3442_route (const char **octets,
                                NMPlatformIP4Route *route,
                                gboolean *success)
{
	const char **o = octets;
	int addr_len = 0, i = 0;
	long int tmp;
	char *next_hop;
	guint32 tmp_addr;

	*success = FALSE;

	if (!*o)
		return o; /* no prefix */

	tmp = strtol (*o, NULL, 10);
	if (tmp < 0 || tmp > 32)  /* 32 == max IP4 prefix length */
		return o;

	memset (route, 0, sizeof (*route));
	route->plen = tmp;
	o++;

	if (tmp > 0)
		addr_len = ((tmp - 1) / 8) + 1;

	/* ensure there's at least the address + next hop left */
	if (g_strv_length ((char **) o) < addr_len + 4)
		goto error;

	if (tmp) {
		const char *addr[4] = { "0", "0", "0", "0" };
		char *str_addr;

		for (i = 0; i < addr_len; i++)
			addr[i] = *o++;

		str_addr = g_strjoin (".", addr[0], addr[1], addr[2], addr[3], NULL);
		if (inet_pton (AF_INET, str_addr, &tmp_addr) <= 0) {
			g_free (str_addr);
			goto error;
		}
148
		g_free (str_addr);
149
		route->network = nm_utils_ip4_address_clear_host_address (tmp_addr, tmp);
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
	}

	/* Handle next hop */
	next_hop = g_strjoin (".", o[0], o[1], o[2], o[3], NULL);
	if (inet_pton (AF_INET, next_hop, &tmp_addr) <= 0) {
		g_free (next_hop);
		goto error;
	}
	route->gateway = tmp_addr;
	g_free (next_hop);

	*success = TRUE;
	return o + 4; /* advance to past the next hop */

error:
	return o;
}

static gboolean
169 170
ip4_process_dhclient_rfc3442_routes (const char *iface,
                                     const char *str,
171
                                     guint32 route_table,
172
                                     guint32 route_metric,
173 174 175 176 177 178 179 180 181 182
                                     NMIP4Config *ip4_config,
                                     guint32 *gwaddr)
{
	char **octets, **o;
	gboolean have_routes = FALSE;
	NMPlatformIP4Route route;
	gboolean success;

	o = octets = g_strsplit_set (str, " .", 0);
	if (g_strv_length (octets) < 5) {
183
		_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str);
184 185 186 187 188 189 190
		goto out;
	}

	while (*o) {
		memset (&route, 0, sizeof (route));
		o = (char **) process_dhclient_rfc3442_route ((const char **) o, &route, &success);
		if (!success) {
191
			_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes");
192 193 194 195 196 197 198 199
			break;
		}

		have_routes = TRUE;
		if (!route.plen) {
			/* gateway passed as classless static route */
			*gwaddr = route.gateway;
		} else {
200 201
			char b1[INET_ADDRSTRLEN];
			char b2[INET_ADDRSTRLEN];
202 203

			/* normal route */
204
			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
205
			route.metric = route_metric;
206
			route.table_coerced = nm_platform_route_table_coerce (route_table);
207
			nm_ip4_config_add_route (ip4_config, &route, NULL);
208

209
			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s",
210 211 212
			        nm_utils_inet4_ntop (route.network, b1),
			        route.plen,
			        nm_utils_inet4_ntop (route.gateway, b2));
213 214 215 216 217 218 219 220 221
		}
	}

out:
	g_strfreev (octets);
	return have_routes;
}

static gboolean
222 223
ip4_process_classless_routes (const char *iface,
                              GHashTable *options,
224
                              guint32 route_table,
225
                              guint32 route_metric,
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
                              NMIP4Config *ip4_config,
                              guint32 *gwaddr)
{
	const char *str, *p;

	g_return_val_if_fail (options != NULL, FALSE);
	g_return_val_if_fail (ip4_config != NULL, FALSE);

	*gwaddr = 0;

	/* dhcpd/dhclient in Fedora has support for rfc3442 implemented using a
	 * slightly different format:
	 *
	 * option classless-static-routes = array of (destination-descriptor ip-address);
	 *
	 * which results in:
	 *
	 * 0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6
	 *
	 * dhcpcd supports classless static routes natively and uses this same
	 * option identifier with the following format:
	 *
	 * 192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41
	 */
250
	str = g_hash_table_lookup (options, "classless_static_routes");
251 252 253 254 255 256 257 258 259 260

	/* dhclient doesn't have actual support for rfc3442 classless static routes
	 * upstream.  Thus, people resort to defining the option in dhclient.conf
	 * and using arbitrary formats like so:
	 *
	 * option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
	 *
	 * See https://lists.isc.org/pipermail/dhcp-users/2008-December/007629.html
	 */
	if (!str)
261
		str = g_hash_table_lookup (options, "rfc3442_classless_static_routes");
262 263 264

	/* Microsoft version; same as rfc3442 but with a different option # (249) */
	if (!str)
265
		str = g_hash_table_lookup (options, "ms_classless_static_routes");
266 267 268 269 270 271 272

	if (!str || !strlen (str))
		return FALSE;

	p = str;
	while (*p) {
		if (!g_ascii_isdigit (*p) && (*p != ' ') && (*p != '.') && (*p != '/')) {
273
			_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str);
274 275 276 277 278 279 280
			return FALSE;
		}
		p++;
	};

	if (strchr (str, '/')) {
		/* dhcpcd format */
281
		return ip4_process_dhcpcd_rfc3442_routes (iface, str, route_table, route_metric, ip4_config, gwaddr);
282 283
	}

284
	return ip4_process_dhclient_rfc3442_routes (iface, str, route_table, route_metric, ip4_config, gwaddr);
285 286 287
}

static void
288 289
process_classful_routes (const char *iface,
                         GHashTable *options,
290
                         guint32 route_table,
291
                         guint32 route_metric,
292
                         NMIP4Config *ip4_config)
293 294 295 296
{
	const char *str;
	char **searches, **s;

297
	str = g_hash_table_lookup (options, "static_routes");
298 299 300 301 302
	if (!str)
		return;

	searches = g_strsplit (str, " ", 0);
	if ((g_strv_length (searches) % 2)) {
303
		_LOG2I (LOGD_DHCP, iface, "  static routes provided, but invalid");
304 305 306 307 308 309 310 311
		goto out;
	}

	for (s = searches; *s; s += 2) {
		NMPlatformIP4Route route;
		guint32 rt_addr, rt_route;

		if (inet_pton (AF_INET, *s, &rt_addr) <= 0) {
312
			_LOG2W (LOGD_DHCP, iface, "DHCP provided invalid static route address: '%s'", *s);
313 314 315
			continue;
		}
		if (inet_pton (AF_INET, *(s + 1), &rt_route) <= 0) {
316
			_LOG2W (LOGD_DHCP, iface, "DHCP provided invalid static route gateway: '%s'", *(s + 1));
317 318 319 320 321 322 323 324 325 326 327
			continue;
		}

		// FIXME: ensure the IP address and route are sane

		memset (&route, 0, sizeof (route));
		route.network = rt_addr;
		/* RFC 2132, updated by RFC 3442:
		   The Static Routes option (option 33) does not provide a subnet mask
		   for each route - it is assumed that the subnet mask is implicit in
		   whatever network number is specified in each route entry */
328 329
		route.plen = _nm_utils_ip4_get_default_prefix (rt_addr);
		if (rt_addr & ~_nm_utils_ip4_prefix_to_netmask (route.plen)) {
330 331 332 333
			/* RFC 943: target not "this network"; using host routing */
			route.plen = 32;
		}
		route.gateway = rt_route;
334
		route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
335
		route.metric = route_metric;
336
		route.table_coerced = nm_platform_route_table_coerce (route_table);
337

338 339
		route.network = nm_utils_ip4_address_clear_host_address (route.network, route.plen);

340
		nm_ip4_config_add_route (ip4_config, &route, NULL);
341
		_LOG2I (LOGD_DHCP, iface, "  static route %s",
342
		             nm_platform_ip4_route_to_string (&route, NULL, 0));
343 344 345 346 347 348 349
	}

out:
	g_strfreev (searches);
}

static void
350 351 352 353
process_domain_search (const char *iface,
                       const char *str,
                       GFunc add_func,
                       gpointer user_data)
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
{
	char **searches, **s;
	char *unescaped, *p;
	int i;

	g_return_if_fail (str != NULL);
	g_return_if_fail (add_func != NULL);

	p = unescaped = g_strdup (str);
	do {
		p = strstr (p, "\\032");
		if (!p)
			break;

		/* Clear the escaped space with real spaces */
		for (i = 0; i < 4; i++)
			*p++ = ' ';
	} while (*p++);

	if (strchr (unescaped, '\\')) {
374
		_LOG2W (LOGD_DHCP, iface, "  invalid domain search: '%s'", unescaped);
375 376 377 378 379 380
		goto out;
	}

	searches = g_strsplit (unescaped, " ", 0);
	for (s = searches; *s; s++) {
		if (strlen (*s)) {
381
			_LOG2I (LOGD_DHCP, iface, "  domain search '%s'", *s);
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
			add_func (*s, user_data);
		}
	}
	g_strfreev (searches);

out:
	g_free (unescaped);
}

static void
ip4_add_domain_search (gpointer data, gpointer user_data)
{
	nm_ip4_config_add_search (NM_IP4_CONFIG (user_data), (const char *) data);
}

NMIP4Config *
398 399
nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
                                       int ifindex,
400
                                       const char *iface,
401
                                       GHashTable *options,
402
                                       guint32 route_table,
403
                                       guint32 route_metric)
404 405 406
{
	NMIP4Config *ip4_config = NULL;
	guint32 tmp_addr;
407
	in_addr_t addr;
408 409
	NMPlatformIP4Address address;
	char *str = NULL;
410 411
	gboolean gateway_has = FALSE;
	guint32 gateway = 0;
412
	guint8 plen = 0;
413
	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
414 415 416

	g_return_val_if_fail (options != NULL, NULL);

417
	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
418 419 420
	memset (&address, 0, sizeof (address));
	address.timestamp = nm_utils_get_monotonic_timestamp_s ();

421
	str = g_hash_table_lookup (options, "ip_address");
422
	if (str && (inet_pton (AF_INET, str, &addr) > 0))
423
		_LOG2I (LOGD_DHCP4, iface, "  address %s", str);
424
	else
425 426
		goto error;

427
	str = g_hash_table_lookup (options, "subnet_mask");
428 429
	if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) {
		plen = nm_utils_ip4_netmask_to_prefix (tmp_addr);
430
		_LOG2I (LOGD_DHCP4, iface, "  plen %d (%s)", plen, str);
431 432
	} else {
		/* Get default netmask for the IP according to appropriate class. */
433
		plen = _nm_utils_ip4_get_default_prefix (addr);
434
		_LOG2I (LOGD_DHCP4, iface, "  plen %d (default)", plen);
435
	}
436
	nm_platform_ip4_address_set_addr (&address, addr, plen);
437 438 439 440

	/* Routes: if the server returns classless static routes, we MUST ignore
	 * the 'static_routes' option.
	 */
441
	if (!ip4_process_classless_routes (iface, options, route_table, route_metric, ip4_config, &gateway))
442
		process_classful_routes (iface, options, route_table, route_metric, ip4_config);
443

444
	if (gateway) {
445
		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, sbuf));
446
		gateway_has = TRUE;
447 448 449 450
	} else {
		/* If the gateway wasn't provided as a classless static route with a
		 * subnet length of 0, try to find it using the old-style 'routers' option.
		 */
451
		str = g_hash_table_lookup (options, "routers");
452 453 454 455 456 457
		if (str) {
			char **routers = g_strsplit (str, " ", 0);
			char **s;

			for (s = routers; *s; s++) {
				/* FIXME: how to handle multiple routers? */
458
				if (inet_pton (AF_INET, *s, &gateway) > 0) {
459
					_LOG2I (LOGD_DHCP4, iface, "  gateway %s", *s);
460
					gateway_has = TRUE;
461 462
					break;
				} else
463
					_LOG2W (LOGD_DHCP4, iface, "ignoring invalid gateway '%s'", *s);
464 465 466 467 468
			}
			g_strfreev (routers);
		}
	}

469 470 471 472 473 474 475 476 477 478 479
	if (gateway_has) {
		const NMPlatformIP4Route r = {
			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
			.gateway = gateway,
			.table_coerced = nm_platform_route_table_coerce (route_table),
			.metric = route_metric,
		};

		nm_ip4_config_add_route (ip4_config, &r, NULL);
	}

480
	str = g_hash_table_lookup (options, "dhcp_lease_time");
481 482
	if (str) {
		address.lifetime = address.preferred = strtoul (str, NULL, 10);
483
		_LOG2I (LOGD_DHCP4, iface, "  lease time %u", address.lifetime);
484 485
	}

486
	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
487 488
	nm_ip4_config_add_address (ip4_config, &address);

489
	str = g_hash_table_lookup (options, "host_name");
490
	if (str)
491
		_LOG2I (LOGD_DHCP4, iface, "  hostname '%s'", str);
492

493
	str = g_hash_table_lookup (options, "domain_name_servers");
494
	if (str) {
495
		char **dns = g_strsplit (str, " ", 0);
496 497
		char **s;

498
		for (s = dns; *s; s++) {
499
			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
500 501
				if (tmp_addr) {
					nm_ip4_config_add_nameserver (ip4_config, tmp_addr);
502
					_LOG2I (LOGD_DHCP4, iface, "  nameserver '%s'", *s);
503
				}
504
			} else
505
				_LOG2W (LOGD_DHCP4, iface, "ignoring invalid nameserver '%s'", *s);
506
		}
507
		g_strfreev (dns);
508 509
	}

510
	str = g_hash_table_lookup (options, "domain_name");
511 512 513 514 515
	if (str) {
		char **domains = g_strsplit (str, " ", 0);
		char **s;

		for (s = domains; *s; s++) {
516
			_LOG2I (LOGD_DHCP4, iface, "  domain name '%s'", *s);
517 518 519 520 521
			nm_ip4_config_add_domain (ip4_config, *s);
		}
		g_strfreev (domains);
	}

522
	str = g_hash_table_lookup (options, "domain_search");
523
	if (str)
524
		process_domain_search (iface, str, ip4_add_domain_search, ip4_config);
525

526
	str = g_hash_table_lookup (options, "netbios_name_servers");
527
	if (str) {
528
		char **nbns = g_strsplit (str, " ", 0);
529 530
		char **s;

531
		for (s = nbns; *s; s++) {
532
			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
533 534
				if (tmp_addr) {
					nm_ip4_config_add_wins (ip4_config, tmp_addr);
535
					_LOG2I (LOGD_DHCP4, iface, "  wins '%s'", *s);
536
				}
537
			} else
538
				_LOG2W (LOGD_DHCP4, iface, "ignoring invalid WINS server '%s'", *s);
539
		}
540
		g_strfreev (nbns);
541 542
	}

543
	str = g_hash_table_lookup (options, "interface_mtu");
544 545 546 547 548 549 550 551 552
	if (str) {
		int int_mtu;

		errno = 0;
		int_mtu = strtol (str, NULL, 10);
		if ((errno == EINVAL) || (errno == ERANGE))
			goto error;

		if (int_mtu > 576)
553
			nm_ip4_config_set_mtu (ip4_config, int_mtu, NM_IP_CONFIG_SOURCE_DHCP);
554 555
	}

556
	str = g_hash_table_lookup (options, "nis_domain");
557
	if (str) {
558
		_LOG2I (LOGD_DHCP4, iface, "  NIS domain '%s'", str);
559 560 561
		nm_ip4_config_set_nis_domain (ip4_config, str);
	}

562
	str = g_hash_table_lookup (options, "nis_servers");
563
	if (str) {
564
		char **nis = g_strsplit (str, " ", 0);
565 566
		char **s;

567
		for (s = nis; *s; s++) {
568
			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
569 570
				if (tmp_addr) {
					nm_ip4_config_add_nis_server (ip4_config, tmp_addr);
571
					_LOG2I (LOGD_DHCP4, iface, "  nis '%s'", *s);
572
				}
573
			} else
574
				_LOG2W (LOGD_DHCP4, iface, "ignoring invalid NIS server '%s'", *s);
575
		}
576
		g_strfreev (nis);
577 578
	}

579 580 581
	str = g_hash_table_lookup (options, "vendor_encapsulated_options");
	nm_ip4_config_set_metered (ip4_config, str && strstr (str, "ANDROID_METERED"));

582 583 584 585 586 587 588
	return ip4_config;

error:
	g_object_unref (ip4_config);
	return NULL;
}

589
/*****************************************************************************/
590 591 592 593 594 595 596

static void
ip6_add_domain_search (gpointer data, gpointer user_data)
{
	nm_ip6_config_add_search (NM_IP6_CONFIG (user_data), (const char *) data);
}

597 598 599
NMPlatformIP6Address
nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options)
{
600
	gs_strfreev char **split_addr = NULL;
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
	NMPlatformIP6Address address = { 0, };
	struct in6_addr tmp_addr;
	char *str = NULL;
	int prefix;

	g_return_val_if_fail (options != NULL, address);

	str = g_hash_table_lookup (options, "ip6_prefix");
	if (!str)
		return address;

	split_addr = g_strsplit (str, "/", 2);
	if (split_addr[0] == NULL && split_addr[1] == NULL) {
		nm_log_warn (LOGD_DHCP6, "DHCP returned prefix without length '%s'", str);
		return address;
	}

	if (!inet_pton (AF_INET6, split_addr[0], &tmp_addr)) {
		nm_log_warn (LOGD_DHCP6, "DHCP returned invalid prefix '%s'", str);
		return address;
	}

	prefix = _nm_utils_ascii_str_to_int64 (split_addr[1], 10, 0, 128, -1);
	if (prefix < 0) {
		nm_log_warn (LOGD_DHCP6, "DHCP returned prefix with invalid length '%s'", str);
		return address;
	}

	address.address = tmp_addr;
	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
	address.plen = prefix;
	address.timestamp = nm_utils_get_monotonic_timestamp_s ();

	str = g_hash_table_lookup (options, "max_life");
	if (str)
		address.lifetime = strtoul (str, NULL, 10);

	str = g_hash_table_lookup (options, "preferred_life");
	if (str)
		address.preferred = strtoul (str, NULL, 10);

	return address;
}

645
NMIP6Config *
646 647
nm_dhcp_utils_ip6_config_from_options (NMDedupMultiIndex *multi_idx,
                                       int ifindex,
648
                                       const char *iface,
649 650 651 652 653 654 655 656 657 658 659 660 661 662
                                       GHashTable *options,
                                       gboolean info_only)
{
	NMIP6Config *ip6_config = NULL;
	struct in6_addr tmp_addr;
	NMPlatformIP6Address address;
	char *str = NULL;

	g_return_val_if_fail (options != NULL, NULL);

	memset (&address, 0, sizeof (address));
	address.plen = 128;
	address.timestamp = nm_utils_get_monotonic_timestamp_s ();

663
	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
664

665
	str = g_hash_table_lookup (options, "max_life");
666 667
	if (str) {
		address.lifetime = strtoul (str, NULL, 10);
668
		_LOG2I (LOGD_DHCP6, iface, "  valid_lft %u", address.lifetime);
669 670
	}

671
	str = g_hash_table_lookup (options, "preferred_life");
672 673
	if (str) {
		address.preferred = strtoul (str, NULL, 10);
674
		_LOG2I (LOGD_DHCP6, iface, "  preferred_lft %u", address.preferred);
675 676
	}

677
	str = g_hash_table_lookup (options, "ip6_address");
678 679
	if (str) {
		if (!inet_pton (AF_INET6, str, &tmp_addr)) {
680 681
			_LOG2W (LOGD_DHCP6, iface, "(%s): DHCP returned invalid address '%s'",
			        iface, str);
682 683 684 685
			goto error;
		}

		address.address = tmp_addr;
686
		address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
687
		nm_ip6_config_add_address (ip6_config, &address);
688
		_LOG2I (LOGD_DHCP6, iface, "  address %s", str);
689 690 691 692 693
	} else if (info_only == FALSE) {
		/* No address in Managed mode is a hard error */
		goto error;
	}

694
	str = g_hash_table_lookup (options, "host_name");
695
	if (str)
696
		_LOG2I (LOGD_DHCP6, iface, "  hostname '%s'", str);
697

698
	str = g_hash_table_lookup (options, "dhcp6_name_servers");
699
	if (str) {
700
		char **dns = g_strsplit (str, " ", 0);
701 702
		char **s;

703
		for (s = dns; *s; s++) {
704
			if (inet_pton (AF_INET6, *s, &tmp_addr) > 0) {
705 706
				if (!IN6_IS_ADDR_UNSPECIFIED (&tmp_addr)) {
					nm_ip6_config_add_nameserver (ip6_config, &tmp_addr);
707
					_LOG2I (LOGD_DHCP6, iface, "  nameserver '%s'", *s);
708
				}
709
			} else
710
				_LOG2W (LOGD_DHCP6, iface, "ignoring invalid nameserver '%s'", *s);
711
		}
712
		g_strfreev (dns);
713 714
	}

715
	str = g_hash_table_lookup (options, "dhcp6_domain_search");
716
	if (str)
717
		process_domain_search (iface, str, ip6_add_domain_search, ip6_config);
718 719 720 721 722 723 724 725

	return ip6_config;

error:
	g_object_unref (ip6_config);
	return NULL;
}

726
char *
727
nm_dhcp_utils_duid_to_string (GBytes *duid)
728
{
729 730 731
	gconstpointer data;
	gsize len;

732
	g_return_val_if_fail (duid, NULL);
733

734
	data = g_bytes_get_data (duid, &len);
735
	return _nm_utils_bin2hexstr_full (data, len, ':', FALSE, NULL);
736 737
}

738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
/**
 * nm_dhcp_utils_client_id_string_to_bytes:
 * @client_id: the client ID string
 *
 * Accepts either a hex string ("aa:bb:cc") representing a binary client ID
 * (the first byte is assumed to be the 'type' field per RFC 2132 section 9.14),
 * or a string representing a non-hardware-address client ID, in which case
 * the 'type' field is set to 0.
 *
 * Returns: the binary client ID suitable for sending over the wire
 * to the DHCP server.
 */
GBytes *
nm_dhcp_utils_client_id_string_to_bytes (const char *client_id)
{
	GBytes *bytes = NULL;
	guint len;
	char *c;

	g_return_val_if_fail (client_id && client_id[0], NULL);

759
	/* Try as hex encoded */
760
	if (strchr (client_id, ':')) {
761
		bytes = nm_utils_hexstr2bin (client_id);
762 763 764 765 766 767 768

		/* the result must be at least two bytes long,
		 * because @client_id contains a delimiter
		 * but nm_utils_hexstr2bin() does not allow
		 * leading nor trailing delimiters. */
		nm_assert (!bytes || g_bytes_get_size (bytes) >= 2);
	}
769 770 771
	if (!bytes) {
		/* Fall back to string */
		len = strlen (client_id);
772 773 774 775 776 777 778 779 780
		c = g_malloc (len + 1);
		c[0] = 0;  /* type: non-hardware address per RFC 2132 section 9.14 */
		memcpy (c + 1, client_id, len);
		bytes = g_bytes_new_take (c, len + 1);
	}

	return bytes;
}