nm-platform.c 139 KB
Newer Older
Pavel Šimerda's avatar
Pavel Šimerda committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* nm-platform.c - Handle runtime kernel networking configuration
 *
 * 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) 2012 Red Hat, Inc.
 */

21
#include "nm-default.h"
22

23 24
#include "nm-platform.h"

Pavel Šimerda's avatar
Pavel Šimerda committed
25 26 27 28
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
Pavel Šimerda's avatar
Pavel Šimerda committed
29
#include <arpa/inet.h>
30
#include <string.h>
31
#include <linux/ip.h>
32
#include <linux/if_tun.h>
33
#include <linux/if_tunnel.h>
34
#include <linux/rtnetlink.h>
Pavel Šimerda's avatar
Pavel Šimerda committed
35

36
#include "nm-utils.h"
37 38 39
#include "nm-core-internal.h"

#include "nm-core-utils.h"
40
#include "nm-platform-utils.h"
41
#include "nmp-object.h"
42
#include "nmp-netns.h"
Pavel Šimerda's avatar
Pavel Šimerda committed
43

44 45 46 47 48 49
/*****************************************************************************/

const NMIPAddr nm_ip_addr_zero = NMIPAddrInit;

/*****************************************************************************/

50
G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWADDR_LEN_MAX);
51 52 53 54
G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address));
G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
55

56 57 58
#define _NMLOG_DOMAIN           LOGD_PLATFORM
#define _NMLOG_PREFIX_NAME      "platform"
#define _NMLOG(level, ...) \
59 60 61
    G_STMT_START { \
        const NMLogLevel __level = (level); \
        \
62
        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
63
            char __prefix[32]; \
64
            const char *__p_prefix = _NMLOG_PREFIX_NAME; \
65 66 67
            const void *const __self = (self); \
            \
            if (__self && __self != nm_platform_try_get ()) { \
68
                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
69 70
                __p_prefix = __prefix; \
            } \
71
            _nm_log (__level, _NMLOG_DOMAIN, 0, \
72 73 74 75 76
                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
        } \
    } G_STMT_END

77 78
#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"

79 80
/*****************************************************************************/

81
static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
Pavel Šimerda's avatar
Pavel Šimerda committed
82

83 84
enum {
	PROP_0,
85
	PROP_NETNS_SUPPORT,
86 87 88 89
	PROP_REGISTER_SINGLETON,
	LAST_PROP,
};

90 91
typedef struct _NMPlatformPrivate {
	bool register_singleton:1;
92 93
} NMPlatformPrivate;

94 95 96 97
G_DEFINE_TYPE (NMPlatform, nm_platform, G_TYPE_OBJECT)

#define NM_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMPlatform, NM_IS_PLATFORM)

98
/*****************************************************************************/
Pavel Šimerda's avatar
Pavel Šimerda committed
99

100 101 102 103 104 105 106 107 108 109
guint
_nm_platform_signal_id_get (NMPlatformSignalIdType signal_type)
{
	nm_assert (   signal_type > 0
	           && signal_type != NM_PLATFORM_SIGNAL_ID_NONE
	           && signal_type < _NM_PLATFORM_SIGNAL_ID_LAST);

	return signals[signal_type];
}

110
/*****************************************************************************/
111

Pavel Šimerda's avatar
Pavel Šimerda committed
112
/* Singleton NMPlatform subclass instance and cached class object */
113 114
NM_DEFINE_SINGLETON_INSTANCE (NMPlatform);

115
NM_DEFINE_SINGLETON_REGISTER (NMPlatform);
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

/* Just always initialize a @klass instance. NM_PLATFORM_GET_CLASS()
 * is only a plain read on the self instance, which the compiler
 * like can optimize out.
 */
#define _CHECK_SELF_VOID(self, klass) \
	NMPlatformClass *klass; \
	do { \
		g_return_if_fail (NM_IS_PLATFORM (self)); \
		klass = NM_PLATFORM_GET_CLASS (self); \
		(void) klass; \
	} while (0)

#define _CHECK_SELF(self, klass, err_val) \
	NMPlatformClass *klass; \
	do { \
		g_return_val_if_fail (NM_IS_PLATFORM (self), err_val); \
		klass = NM_PLATFORM_GET_CLASS (self); \
		(void) klass; \
	} while (0)
Pavel Šimerda's avatar
Pavel Šimerda committed
136

137 138 139 140 141 142 143 144 145 146 147
#define _CHECK_SELF_NETNS(self, klass, netns, err_val) \
	nm_auto_pop_netns NMPNetns *netns = NULL; \
	NMPlatformClass *klass; \
	do { \
		g_return_val_if_fail (NM_IS_PLATFORM (self), err_val); \
		klass = NM_PLATFORM_GET_CLASS (self); \
		(void) klass; \
		if (!nm_platform_netns_push (self, &netns)) \
			return (err_val); \
	} while (0)

Pavel Šimerda's avatar
Pavel Šimerda committed
148 149
/**
 * nm_platform_setup:
150
 * @instance: the #NMPlatform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
151 152 153 154 155 156 157
 *
 * Failing to set up #NMPlatform singleton results in a fatal error,
 * as well as trying to initialize it multiple times without freeing
 * it.
 *
 * NetworkManager will typically use only one platform object during
 * its run. Test programs might want to switch platform implementations,
158
 * though.
Pavel Šimerda's avatar
Pavel Šimerda committed
159 160
 */
void
161
nm_platform_setup (NMPlatform *instance)
Pavel Šimerda's avatar
Pavel Šimerda committed
162
{
163 164
	g_return_if_fail (NM_IS_PLATFORM (instance));
	g_return_if_fail (!singleton_instance);
Pavel Šimerda's avatar
Pavel Šimerda committed
165

166
	singleton_instance = instance;
Pavel Šimerda's avatar
Pavel Šimerda committed
167

168
	nm_singleton_instance_register ();
Pavel Šimerda's avatar
Pavel Šimerda committed
169

170
	nm_log_dbg (LOGD_CORE, "setup %s singleton (%p, %s)", "NMPlatform", singleton_instance, G_OBJECT_TYPE_NAME (instance));
Pavel Šimerda's avatar
Pavel Šimerda committed
171 172 173 174
}

/**
 * nm_platform_get:
175
 * @self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
176 177
 *
 * Retrieve #NMPlatform singleton. Use this whenever you want to connect to
178
 * #NMPlatform signals. It is an error to call it before nm_platform_setup().
Pavel Šimerda's avatar
Pavel Šimerda committed
179 180 181 182
 *
 * Returns: (transfer none): The #NMPlatform singleton reference.
 */
NMPlatform *
183
nm_platform_get ()
Pavel Šimerda's avatar
Pavel Šimerda committed
184
{
185
	g_assert (singleton_instance);
Pavel Šimerda's avatar
Pavel Šimerda committed
186

187
	return singleton_instance;
Pavel Šimerda's avatar
Pavel Šimerda committed
188 189
}

190 191 192 193 194 195
NMPlatform *
nm_platform_try_get (void)
{
	return singleton_instance;
}

196
/*****************************************************************************/
Pavel Šimerda's avatar
Pavel Šimerda committed
197 198

/**
199
 * _nm_platform_error_to_string:
200
 * @error_code: the error code to stringify.
Pavel Šimerda's avatar
Pavel Šimerda committed
201
 *
202 203 204
 * Returns: A string representation of the error.
 * For negative numbers, this function interprets
 * the code as -errno.
205
 * For invalid (positive) numbers it returns NULL.
Pavel Šimerda's avatar
Pavel Šimerda committed
206
 */
207
NM_UTILS_LOOKUP_STR_DEFINE (_nm_platform_error_to_string, NMPlatformError,
208
	NM_UTILS_LOOKUP_DEFAULT ( val < 0 ? g_strerror (- ((int) val)) : NULL ),
209 210 211 212 213 214 215 216
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_SUCCESS,     "success"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_BUG,         "bug"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_UNSPECIFIED, "unspecified"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_FOUND,   "not-found"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_EXISTS,      "exists"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_WRONG_TYPE,  "wrong-type"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_SLAVE,   "not-slave"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NO_FIRMWARE, "no-firmware"),
217
	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_OPNOTSUPP,   "not-supported"),
218
	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_PLATFORM_ERROR_MININT),
219
);
Pavel Šimerda's avatar
Pavel Šimerda committed
220

221
/*****************************************************************************/
Pavel Šimerda's avatar
Pavel Šimerda committed
222

223
gboolean
224
nm_platform_check_support_kernel_extended_ifa_flags (NMPlatform *self)
225
{
226
	_CHECK_SELF (self, klass, FALSE);
227 228 229 230

	if (!klass->check_support_kernel_extended_ifa_flags)
		return FALSE;

231
	return klass->check_support_kernel_extended_ifa_flags (self);
232 233
}

234
gboolean
235
nm_platform_check_support_user_ipv6ll (NMPlatform *self)
236 237 238
{
	static int supported = -1;

239
	_CHECK_SELF (self, klass, FALSE);
240 241 242 243 244

	if (!klass->check_support_user_ipv6ll)
		return FALSE;

	if (supported < 0)
245
		supported = klass->check_support_user_ipv6ll (self) ? 1 : 0;
246 247 248
	return !!supported;
}

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
/**
 * nm_platform_process_events:
 * @self: platform instance
 *
 * Process pending events or handle pending delayed-actions.
 * Effectively, this reads the netlink socket and processes
 * new netlink messages. Possibly it will raise change signals.
 */
void
nm_platform_process_events (NMPlatform *self)
{
	_CHECK_SELF_VOID (self, klass);

	if (klass->process_events)
		klass->process_events (self);
}

266
/*****************************************************************************/
Pavel Šimerda's avatar
Pavel Šimerda committed
267

268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
/**
 * nm_platform_sysctl_open_netdir:
 * @self: platform instance
 * @ifindex: the ifindex for which to open /sys/class/net/%s
 * @out_ifname: optional output argument of the found ifname.
 *
 * Wraps nmp_utils_sysctl_open_netdir() by first changing into the right
 * network-namespace.
 *
 * Returns: on success, the open file descriptor to the /sys/class/net/%s
 *   directory.
 */
int
nm_platform_sysctl_open_netdir (NMPlatform *self, int ifindex, char *out_ifname)
{
	const char*ifname_guess;
	_CHECK_SELF_NETNS (self, klass, netns, -1);

	g_return_val_if_fail (ifindex > 0, -1);

	/* we don't have an @ifname_guess argument to make the API nicer.
	 * But still do a cache-lookup first. Chances are good that we have
	 * the right ifname cached and save if_indextoname() */
	ifname_guess = nm_platform_link_get_name (self, ifindex);

	return nmp_utils_sysctl_open_netdir (ifindex, ifname_guess, out_ifname);
}

296 297
/**
 * nm_platform_sysctl_set:
298
 * @self: platform instance
299 300 301
 * @pathid: if @dirfd is present, this must be the full path that is looked up.
 *   It is required for logging.
 * @dirfd: optional file descriptor for parent directory for openat()
302 303 304 305 306 307 308 309 310 311
 * @path: Absolute option path
 * @value: Value to write
 *
 * This function is intended to be used for writing values to sysctl-style
 * virtual runtime configuration files. This includes not only /proc/sys
 * but also for example /sys/class.
 *
 * Returns: %TRUE on success.
 */
gboolean
312
nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const char *path, const char *value)
313
{
314
	_CHECK_SELF (self, klass, FALSE);
315 316 317 318

	g_return_val_if_fail (path, FALSE);
	g_return_val_if_fail (value, FALSE);

319
	return klass->sysctl_set (self, pathid, dirfd, path, value);
320 321
}

322 323 324 325 326 327
gboolean
nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface, int value)
{
	const char *path;
	gint64 cur;

328 329
	_CHECK_SELF (self, klass, FALSE);

330 331 332 333 334 335 336 337 338
	/* the hop-limit provided via RA is uint8. */
	if (value > 0xFF)
		return FALSE;

	/* don't allow unreasonable small values */
	if (value < 10)
		return FALSE;

	path = nm_utils_ip6_property_path (iface, "hop_limit");
339
	cur = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_ABSOLUTE (path), 10, 1, G_MAXINT32, -1);
340 341 342 343 344 345 346 347 348 349

	/* only allow increasing the hop-limit to avoid DOS by an attacker
	 * setting a low hop-limit (CVE-2015-2924, rh#1209902) */

	if (value < cur)
		return FALSE;
	if (value != cur) {
		char svalue[20];

		sprintf (svalue, "%d", value);
350
		nm_platform_sysctl_set (self, NMP_SYSCTL_PATHID_ABSOLUTE (path), svalue);
351 352 353 354 355
	}

	return TRUE;
}

356 357
/**
 * nm_platform_sysctl_get:
358
 * @self: platform instance
359 360 361
 * @dirfd: if non-negative, used to lookup the path via openat().
 * @pathid: if @dirfd is present, this must be the full path that is looked up.
 *   It is required for logging.
362 363 364 365 366
 * @path: Absolute path to sysctl
 *
 * Returns: (transfer full): Contents of the virtual sysctl file.
 */
char *
367
nm_platform_sysctl_get (NMPlatform *self, const char *pathid, int dirfd, const char *path)
368
{
369
	_CHECK_SELF (self, klass, NULL);
370 371 372

	g_return_val_if_fail (path, NULL);

373
	return klass->sysctl_get (self, pathid, dirfd, path);
374 375
}

376
/**
377
 * nm_platform_sysctl_get_int32:
378
 * @self: platform instance
379 380 381
 * @pathid: if @dirfd is present, this must be the full path that is looked up.
 *   It is required for logging.
 * @dirfd: if non-negative, used to lookup the path via openat().
382
 * @path: Absolute path to sysctl
383 384
 * @fallback: default value, if the content of path could not be read
 * as decimal integer.
385
 *
386
 * Returns: contents of the sysctl file parsed as s32 integer, or
387 388
 * @fallback on error. On error, %errno will be set to a non-zero
 * value, on success %errno will be set to zero.
389
 */
390
gint32
391
nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, const char *path, gint32 fallback)
392
{
393
	return nm_platform_sysctl_get_int_checked (self, pathid, dirfd, path, 10, G_MININT32, G_MAXINT32, fallback);
394 395 396 397
}

/**
 * nm_platform_sysctl_get_int_checked:
398
 * @self: platform instance
399 400 401
 * @pathid: if @dirfd is present, this must be the full path that is looked up.
 *   It is required for logging.
 * @dirfd: if non-negative, used to lookup the path via openat().
402 403 404 405 406 407 408 409 410 411 412 413 414 415
 * @path: Absolute path to sysctl
 * @base: base of numeric conversion
 * @min: minimal value that is still valid
 * @max: maximal value that is still valid
 * @fallback: default value, if the content of path could not be read
 * as valid integer.
 *
 * Returns: contents of the sysctl file parsed as s64 integer, or
 * @fallback on error. On error, %errno will be set to a non-zero
 * value. On success, %errno will be set to zero. The returned value
 * will always be in the range between @min and @max
 * (inclusive) or @fallback.
 */
gint64
416
nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int dirfd, const char *path, guint base, gint64 min, gint64 max, gint64 fallback)
417
{
418 419
	char *value = NULL;
	gint32 ret;
420

421 422
	_CHECK_SELF (self, klass, fallback);

423
	g_return_val_if_fail (path, fallback);
424

425
	if (path)
426
		value = nm_platform_sysctl_get (self, pathid, dirfd, path);
427 428 429 430 431

	if (!value) {
		errno = EINVAL;
		return fallback;
	}
432

433
	ret = _nm_utils_ascii_str_to_int64 (value, base, min, max, fallback);
434
	g_free (value);
435 436 437
	return ret;
}

438
/*****************************************************************************/
439

440 441 442 443 444 445 446 447 448 449 450 451 452 453
static int
_link_get_all_presort (gconstpointer  p_a,
                       gconstpointer  p_b)
{
	const NMPlatformLink *a = p_a;
	const NMPlatformLink *b = p_b;

	if (a->ifindex < b->ifindex)
		return -1;
	if (a->ifindex > b->ifindex)
		return 1;
	return 0;
}

Pavel Šimerda's avatar
Pavel Šimerda committed
454 455
/**
 * nm_platform_link_get_all:
456
 * self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
457 458 459 460 461
 *
 * Retrieve a snapshot of configuration for all links at once. The result is
 * owned by the caller and should be freed with g_array_unref().
 */
GArray *
462
nm_platform_link_get_all (NMPlatform *self)
Pavel Šimerda's avatar
Pavel Šimerda committed
463
{
464 465 466 467
	GArray *links, *result;
	guint i, j, nresult;
	GHashTable *unseen;
	NMPlatformLink *item;
468

469
	_CHECK_SELF (self, klass, NULL);
Pavel Šimerda's avatar
Pavel Šimerda committed
470

471
	links = klass->link_get_all (self);
472 473 474 475

	if (!links || links->len == 0)
		return links;

476 477 478 479
	/* first sort the links by their ifindex. Below we will sort further by moving
	 * children/slaves to the end. */
	g_array_sort (links, _link_get_all_presort);

480 481 482 483
	unseen = g_hash_table_new (g_direct_hash, g_direct_equal);
	for (i = 0; i < links->len; i++) {
		item = &g_array_index (links, NMPlatformLink, i);

484 485 486
		nm_assert (item->ifindex > 0);
		if (!nm_g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL))
			nm_assert_not_reached ();
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
	}

#ifndef G_DISABLE_ASSERT
	/* Ensure that link_get_all returns a consistent and valid result. */
	for (i = 0; i < links->len; i++) {
		item = &g_array_index (links, NMPlatformLink, i);

		if (!item->ifindex)
			continue;
		if (item->master != 0) {
			g_warn_if_fail (item->master > 0);
			g_warn_if_fail (item->master != item->ifindex);
			g_warn_if_fail (g_hash_table_contains (unseen, GINT_TO_POINTER (item->master)));
		}
		if (item->parent != 0) {
502 503 504
			if (item->parent != NM_PLATFORM_LINK_OTHER_NETNS) {
				g_warn_if_fail (item->parent > 0);
				g_warn_if_fail (item->parent != item->ifindex);
505
				g_warn_if_fail (g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)));
506
			}
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
		}
	}
#endif

	/* Re-order the links list such that children/slaves come after all ancestors */
	nresult = g_hash_table_size (unseen);
	result = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), nresult);
	g_array_set_size (result, nresult);

	j = 0;
	do {
		gboolean found_something = FALSE;
		guint first_idx = G_MAXUINT;

		for (i = 0; i < links->len; i++) {
			item = &g_array_index (links, NMPlatformLink, i);

			if (!item->ifindex)
				continue;

			if (first_idx == G_MAXUINT)
				first_idx = i;

			g_assert (g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex)));

			if (item->master > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->master)))
				continue;
			if (item->parent > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)))
				continue;

			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
			g_array_index (result, NMPlatformLink, j++) = *item;
			item->ifindex = 0;
			found_something = TRUE;
		}

		if (!found_something) {
544 545
			/* There is a loop, pop the first (remaining) element from the list.
			 * This can happen for veth pairs where each peer is parent of the other end. */
546 547 548 549 550 551 552 553 554 555 556 557
			item = &g_array_index (links, NMPlatformLink, first_idx);

			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
			g_array_index (result, NMPlatformLink, j++) = *item;
			item->ifindex = 0;
		}
	} while (j < nresult);

	g_hash_table_destroy (unseen);
	g_array_free (links, TRUE);

	return result;
Pavel Šimerda's avatar
Pavel Šimerda committed
558 559
}

560 561
/**
 * nm_platform_link_get:
562
 * @self: platform instance
563 564
 * @ifindex: ifindex of the link
 *
565
 * Lookup the internal NMPlatformLink object.
566
 *
567 568 569 570
 * Returns: %NULL, if such a link exists or the internal
 * platform link object. Do not modify the returned value.
 * Also, be aware that any subsequent platform call might
 * invalidated/modify the returned instance.
571
 **/
572 573
const NMPlatformLink *
nm_platform_link_get (NMPlatform *self, int ifindex)
574
{
575
	_CHECK_SELF (self, klass, NULL);
576

577 578 579
	if (ifindex > 0)
		return klass->link_get (self, ifindex);
	return NULL;
580 581 582 583 584 585 586 587 588 589 590 591 592 593
}

/**
 * nm_platform_link_get_by_ifname:
 * @self: platform instance
 * @ifname: the ifname
 *
 * Returns: the first #NMPlatformLink instance with the given name.
 **/
const NMPlatformLink *
nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname)
{
	_CHECK_SELF (self, klass, NULL);

594 595 596
	if (ifname && *ifname)
		return klass->link_get_by_ifname (self, ifname);
	return NULL;
597 598
}

599 600 601 602 603 604
/**
 * nm_platform_link_get_by_address:
 * @self: platform instance
 * @address: a pointer to the binary hardware address
 * @length: the size of @address in bytes
 *
605 606
 * Returns: the first #NMPlatformLink object with a matching
 * address.
607
 **/
608
const NMPlatformLink *
609 610
nm_platform_link_get_by_address (NMPlatform *self,
                                 gconstpointer address,
611
                                 size_t length)
612
{
613
	_CHECK_SELF (self, klass, NULL);
614

615 616 617 618 619 620 621
	g_return_val_if_fail (length == 0 || address, NULL);
	if (length > 0) {
		if (length > NM_UTILS_HWADDR_LEN_MAX)
			g_return_val_if_reached (NULL);
		return klass->link_get_by_address (self, address, length);
	}
	return NULL;
622 623
}

624
static NMPlatformError
625
_link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, const NMPlatformLink **out_link)
626
{
627 628 629 630 631 632 633
	const NMPlatformLink *pllink;

	pllink = nm_platform_link_get_by_ifname (self, name);
	if (pllink) {
		gboolean wrong_type;

		wrong_type = type != NM_LINK_TYPE_NONE && pllink->type != type;
634
		_LOGD ("link: skip adding link due to existing interface '%s' of type %s%s%s",
635 636 637 638 639
		       name,
		       nm_link_type_to_string (pllink->type),
		       wrong_type ? ", expected " : "",
		       wrong_type ? nm_link_type_to_string (type) : "");
		if (out_link)
640
			*out_link = pllink;
641 642 643
		if (wrong_type)
			return NM_PLATFORM_ERROR_WRONG_TYPE;
		return NM_PLATFORM_ERROR_EXISTS;
644
	}
645 646
	if (out_link)
		*out_link = NULL;
647 648 649
	return NM_PLATFORM_ERROR_SUCCESS;
}

Pavel Šimerda's avatar
Pavel Šimerda committed
650 651
/**
 * nm_platform_link_add:
652
 * @self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
653 654
 * @name: Interface name
 * @type: Interface type
655 656
 * @address: (allow-none): set the mac address of the link
 * @address_len: the length of the @address
657
 * @out_link: on success, the link object
Pavel Šimerda's avatar
Pavel Šimerda committed
658
 *
659
 * Add a software interface.  If the interface already exists and is of type
660
 * @type, return NM_PLATFORM_ERROR_EXISTS and returns the link
661
 * in @out_link.  If the interface already exists and is not of type @type,
662 663 664 665 666 667
 * return NM_PLATFORM_ERROR_WRONG_TYPE.
 *
 * Any link-changed ADDED signal will be emitted directly, before this
 * function finishes.
 *
 * Returns: the error reason or NM_PLATFORM_ERROR_SUCCESS.
Pavel Šimerda's avatar
Pavel Šimerda committed
668
 */
669
static NMPlatformError
670 671 672 673 674
nm_platform_link_add (NMPlatform *self,
                      const char *name,
                      NMLinkType type,
                      const void *address,
                      size_t address_len,
675
                      const NMPlatformLink **out_link)
Pavel Šimerda's avatar
Pavel Šimerda committed
676
{
677
	NMPlatformError plerr;
678

679
	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
Pavel Šimerda's avatar
Pavel Šimerda committed
680

681 682
	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
	g_return_val_if_fail ( (address != NULL) ^ (address_len == 0) , NM_PLATFORM_ERROR_BUG);
Pavel Šimerda's avatar
Pavel Šimerda committed
683

684 685 686
	plerr = _link_add_check_existing (self, name, type, out_link);
	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
		return plerr;
Pavel Šimerda's avatar
Pavel Šimerda committed
687

688
	_LOGD ("link: adding %s '%s'", nm_link_type_to_string (type), name);
689
	if (!klass->link_add (self, name, type, address, address_len, out_link))
690 691
		return NM_PLATFORM_ERROR_UNSPECIFIED;
	return NM_PLATFORM_ERROR_SUCCESS;
Pavel Šimerda's avatar
Pavel Šimerda committed
692 693 694
}

/**
695
 * nm_platform_link_dummy_add:
696
 * @self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
697
 * @name: New interface name
698
 * @out_link: on success, the link object
Pavel Šimerda's avatar
Pavel Šimerda committed
699 700 701
 *
 * Create a software ethernet-like interface
 */
702
NMPlatformError
703 704 705
nm_platform_link_dummy_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLink **out_link)
Pavel Šimerda's avatar
Pavel Šimerda committed
706
{
707
	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, 0, out_link);
Pavel Šimerda's avatar
Pavel Šimerda committed
708 709 710 711
}

/**
 * nm_platform_link_delete:
712
 * @self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
713 714 715
 * @ifindex: Interface index
 */
gboolean
716
nm_platform_link_delete (NMPlatform *self, int ifindex)
Pavel Šimerda's avatar
Pavel Šimerda committed
717
{
718
	const NMPlatformLink *pllink;
Pavel Šimerda's avatar
Pavel Šimerda committed
719

720
	_CHECK_SELF (self, klass, FALSE);
Pavel Šimerda's avatar
Pavel Šimerda committed
721

722 723
	pllink = nm_platform_link_get (self, ifindex);
	if (!pllink)
Pavel Šimerda's avatar
Pavel Šimerda committed
724 725
		return FALSE;

726
	_LOGD ("link: deleting '%s' (%d)", pllink->name, ifindex);
727
	return klass->link_delete (self, ifindex);
Pavel Šimerda's avatar
Pavel Šimerda committed
728 729
}

730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
/**
 * nm_platform_link_set_netns:
 * @self: platform instance
 * @ifindex: Interface index
 * @netns_fd: the file descriptor for the new netns.
 *
 * Returns: %TRUE on success.
 */
gboolean
nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
{
	const NMPlatformLink *pllink;

	_CHECK_SELF (self, klass, FALSE);

	g_return_val_if_fail (ifindex > 0, FALSE);
	g_return_val_if_fail (netns_fd > 0, FALSE);

	pllink = nm_platform_link_get (self, ifindex);
	if (!pllink)
		return FALSE;

	_LOGD ("link: ifindex %d changing network namespace to %d", ifindex, netns_fd);
	return klass->link_set_netns (self, ifindex, netns_fd);
}

Pavel Šimerda's avatar
Pavel Šimerda committed
756 757
/**
 * nm_platform_link_get_index:
758
 * @self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
759 760 761 762 763 764
 * @name: Interface name
 *
 * Returns: The interface index corresponding to the given interface name
 * or 0. Inteface name is owned by #NMPlatform, don't free it.
 */
int
765
nm_platform_link_get_ifindex (NMPlatform *self, const char *name)
Pavel Šimerda's avatar
Pavel Šimerda committed
766
{
767
	const NMPlatformLink *pllink;
Pavel Šimerda's avatar
Pavel Šimerda committed
768

769 770
	pllink = nm_platform_link_get_by_ifname (self, name);
	return pllink ? pllink->ifindex : 0;
Pavel Šimerda's avatar
Pavel Šimerda committed
771 772
}

773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
const char *
nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */)
{
	_CHECK_SELF_NETNS (self, klass, netns, FALSE);

	return nmp_utils_if_indextoname (ifindex, out_ifname);
}

int
nm_platform_if_nametoindex (NMPlatform *self, const char *ifname)
{
	_CHECK_SELF_NETNS (self, klass, netns, FALSE);

	return nmp_utils_if_nametoindex (ifname);
}

Pavel Šimerda's avatar
Pavel Šimerda committed
789 790
/**
 * nm_platform_link_get_name:
791
 * @self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
792 793 794
 * @name: Interface name
 *
 * Returns: The interface name corresponding to the given interface index
Martin Pitt's avatar
Martin Pitt committed
795
 * or %NULL.
Pavel Šimerda's avatar
Pavel Šimerda committed
796 797
 */
const char *
798
nm_platform_link_get_name (NMPlatform *self, int ifindex)
Pavel Šimerda's avatar
Pavel Šimerda committed
799
{
800
	const NMPlatformLink *pllink;
Pavel Šimerda's avatar
Pavel Šimerda committed
801

802
	_CHECK_SELF (self, klass, NULL);
Pavel Šimerda's avatar
Pavel Šimerda committed
803

804 805
	pllink = nm_platform_link_get (self, ifindex);
	return pllink ? pllink->name : NULL;
Pavel Šimerda's avatar
Pavel Šimerda committed
806 807 808 809
}

/**
 * nm_platform_link_get_type:
810
 * @self: platform instance
Pavel Šimerda's avatar
Pavel Šimerda committed
811 812 813 814 815 816
 * @ifindex: Interface index.
 *
 * Returns: Link type constant as defined in nm-platform.h. On error,
 * NM_LINK_TYPE_NONE is returned.
 */
NMLinkType
817
nm_platform_link_get_type (NMPlatform *self, int ifindex)
Pavel Šimerda's avatar
Pavel Šimerda committed
818
{
819
	const NMPlatformLink *pllink;
Pavel Šimerda's avatar
Pavel Šimerda committed
820

821
	_CHECK_SELF (self, klass, NM_LINK_TYPE_NONE);
Pavel Šimerda's avatar
Pavel Šimerda committed
822

823 824
	pllink = nm_platform_link_get (self, ifindex);
	return pllink ? pllink->type : NM_LINK_TYPE_NONE;
Pavel Šimerda's avatar
Pavel Šimerda committed
825 826
}

827 828
/**
 * nm_platform_link_get_type_name:
829
 * @self: platform instance
830 831 832 833 834 835 836
 * @ifindex: Interface index.
 *
 * Returns: A string describing the type of link. In some cases this
 * may be more specific than nm_platform_link_get_type(), but in
 * other cases it may not. On error, %NULL is returned.
 */
const char *
837
nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
838
{
839
	_CHECK_SELF (self, klass, NULL);
840

841
	return klass->link_get_type_name (self, ifindex);
842 843
}

844 845
/**
 * nm_platform_link_get_unmanaged:
846
 * @self: platform instance
847 848
 * @ifindex: interface index
 * @unmanaged: management status (in case %TRUE is returned)
849
 *
850 851
 * Returns: %TRUE if platform overrides NM default-unmanaged status,
 * %FALSE otherwise (with @unmanaged unmodified).
852 853
 */
gboolean
854
nm_platform_link_get_unmanaged (NMPlatform *self, int ifindex, gboolean *unmanaged)
855
{
856
	_CHECK_SELF (self, klass, FALSE);
857

858
	if (klass->link_get_unmanaged)
859
		return klass->link_get_unmanaged (self, ifindex, unmanaged);
860
	return FALSE;
861 862
}

863 864
/**
 * nm_platform_link_is_software:
865
 * @self: platform instance
866 867 868 869 870 871
 * @ifindex: Interface index.
 *
 * Returns: %TRUE if ifindex belongs to a software interface, not backed by
 * a physical device.
 */
gboolean
872
nm_platform_link_is_software (NMPlatform *self, int ifindex)
873
{
874
	return (nm_platform_link_get_type (self, ifindex) & 0x10000);
875 876 877 878
}

/**
 * nm_platform_link_supports_slaves:
879
 * @self: platform instance
880 881 882 883 884 885
 * @ifindex: Interface index.
 *
 * Returns: %TRUE if ifindex belongs to an interface capable of enslaving
 * other interfaces.
 */
gboolean
886
nm_platform_link_supports_slaves (NMPlatform *self, int ifindex)
887
{
888
	return (nm_platform_link_get_type (self, ifindex) & 0x20000);
889
}
890

891 892
/**
 * nm_platform_link_refresh:
893
 * @self: platform instance
894 895 896 897 898
 * @ifindex: Interface index
 *
 * Reload the cache for ifindex synchronously.
 */
gboolean
899
nm_platform_link_refresh (NMPlatform *self, int ifindex)
900
{
901
	_CHECK_SELF (self, klass, FALSE);
902 903 904 905

	g_return_val_if_fail (ifindex > 0, FALSE);

	if (klass->link_refresh)
906
		return klass->link_refresh (self, ifindex);
907 908 909 910

	return TRUE;
}

911
static guint
912 913 914 915 916
_link_get_flags (NMPlatform *self, int ifindex)
{
	const NMPlatformLink *pllink;

	pllink = nm_platform_link_get (self, ifindex);
917
	return pllink ? pllink->n_ifi_flags : IFF_NOARP;
918 919
}

920 921
/**
 * nm_platform_link_is_up:
922
 * @self: platform instance
923 924 925 926 927
 * @ifindex: Interface index
 *
 * Check if the interface is up.
 */
gboolean
928
nm_platform_link_is_up (NMPlatform *self, int ifindex)
929
{
930
	_CHECK_SELF (self, klass, FALSE);
931

932
	return NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_UP);
933 934 935 936
}

/**
 * nm_platform_link_is_connected:
937
 * @self: platform instance
938 939 940 941 942
 * @ifindex: Interface index
 *
 * Check if the interface is connected.
 */
gboolean
943
nm_platform_link_is_connected (NMPlatform *self, int ifindex)
944
{
945
	const NMPlatformLink *pllink;
946

947
	_CHECK_SELF (self, klass, FALSE);
948

949 950
	pllink = nm_platform_link_get (self, ifindex);
	return pllink ? pllink->connected : FALSE;
951 952 953 954
}

/**
 * nm_platform_link_uses_arp:
955
 * @self: platform instance
956 957 958 959 960
 * @ifindex: Interface index
 *
 * Check if the interface is configured to use ARP.
 */
gboolean
961
nm_platform_link_uses_arp (NMPlatform *self, int ifindex)
962
{
963
	_CHECK_SELF (self, klass, FALSE);
964

965
	return !NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_NOARP);
966 967
}

968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
/**
 * nm_platform_link_set_ipv6_token:
 * @self: platform instance
 * @ifindex: Interface index
 * @iid: Tokenized interface identifier
 *
 * Sets then IPv6 tokenized interface identifier.
 *
 * Returns: %TRUE a tokenized identifier was available
 */
gboolean
nm_platform_link_set_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid)
{
	_CHECK_SELF (self, klass, FALSE);

	g_return_val_if_fail (ifindex >= 0, FALSE);
	g_return_val_if_fail (iid.id, FALSE);

	if (klass->link_set_token)
		return klass->link_set_token (self, ifindex, iid);
	return FALSE;
}

991 992 993 994 995 996 997 998 999 1000 1001
const char *
nm_platform_link_get_udi (NMPlatform *self, int ifindex)
{
	_CHECK_SELF (self, klass, FALSE);

	g_return_val_if_fail (ifindex >= 0, NULL);

	if (klass->link_get_udi)
		return klass->link_get_udi (self, ifindex);
	return NULL;
}
1002

1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
GObject *
nm_platform_link_get_udev_device (NMPlatform *self, int ifindex)
{
	_CHECK_SELF (self, klass, FALSE);

	g_return_val_if_fail (ifindex >= 0, NULL);

	if (klass->link_get_udev_device)
		return klass->link_get_udev_device (self, ifindex);
	return NULL;
}

1015 1016
/**
 * nm_platform_link_get_user_ip6vll_enabled:
1017
 * @self: platform instance
1018 1019 1020 1021 1022 1023 1024 1025 1026
 * @ifindex: Interface index
 *
 * Check whether NM handles IPv6LL address creation for the link.  If the
 * platform or OS doesn't support changing the IPv6LL address mode, this call
 * will fail and return %FALSE.
 *
 * Returns: %TRUE if NM handles the IPv6LL address for @ifindex
 */
gboolean
1027
nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex)
1028
{
1029 1030
	const NMPlatformLink *pllink;

1031
	_CHECK_SELF (self, klass, FALSE);
1032 1033 1034

	g_return_val_if_fail (ifindex >= 0, FALSE);

1035 1036 1037
	pllink = nm_platform_link_get (self, ifindex);
	if (pllink && pllink->inet6_addr_gen_mode_inv)
		return _nm_platform_uint8_inv (pllink->inet6_addr_gen_mode_inv) == NM_IN6_ADDR_GEN_MODE_NONE;
1038 1039 1040 1041 1042
	return FALSE;
}

/**
 * nm_platform_link_set_user_ip6vll_enabled:
1043
 * @self: platform instance
1044 1045 1046 1047 1048 1049
 * @ifindex: Interface index
 *
 * Set whether NM handles IPv6LL address creation for the link.  If the
 * platform or OS doesn't support changing the IPv6LL address mode, this call
 * will fail and return %FALSE.
 *
1050
 * Returns: %NM_PLATFORM_ERROR_SUCCESS if the operation was successful or an error code otherwise.
1051
 */
1052
NMPlatformError
1053
nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolean enabled)
1054
{
1055
	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
1056

1057
	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
1058

1059
	return klass->link_set_user_ipv6ll_enabled (self, ifindex, enabled);
1060 1061
}

1062 1063
/**
 * nm_platform_link_set_address:
1064
 * @self: platform instance
1065 1066 1067 1068 1069
 * @ifindex: Interface index
 * @address: The new MAC address
 *
 * Set interface MAC address.
 */
1070
NMPlatformError
1071
nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer address, size_t length)
1072
{
1073
	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
1074

1075 1076 1077
	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
	g_return_val_if_fail (length > 0, NM_PLATFORM_ERROR_BUG);
1078

1079 1080 1081
	_LOGD ("link: setting %s (%d) hardware address",
	       nm_strquote_a (20, nm_platform_link_get_name (self, ifindex)),
	       ifindex);
1082
	return klass->link_set_address (self, ifindex, address, length);
1083 1084 1085 1086
}

/**
 * nm_platform_link_get_address:
1087
 * @self: platform instance
1088 1089 1090
 * @ifindex: Interface index
 * @length: Pointer to a variable to store address length
 *
1091 1092
 * Returns: the interface hardware address as an array of bytes of
 * length @length.
1093 1094
 */
gconstpointer
1095
nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
1096
{
1097 1098 1099 1100
	const NMPlatformLink *pllink;
	gconstpointer a = NULL;
	guint8 l = 0;

1101
	_CHECK_SELF (self, klass, NULL);
1102 1103 1104 1105 1106 1107

	if (length)
		*length = 0;

	g_return_val_if_fail (ifindex > 0, NULL);

1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
	pllink = nm_platform_link_get (self, ifindex);
	if (pllink && pllink->addr.len > 0) {
		if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) {
			if (length)
				*length = 0;
			g_return_val_if_reached (NULL);
		}
		a = pllink->addr.data;
		l = pllink->addr.len;
	}

	if (length)
		*length = l;
	return a;
1122 1123
}

1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
/**
 * nm_platform_link_get_permanent_address:
 * @self: platform instance
 * @ifindex: Interface index
 * @buf: buffer of at least %NM_UTILS_HWADDR_LEN_MAX bytes, on success
 * the permanent hardware address
 * @length: Pointer to a variable to store address length
 *
 * Returns: %TRUE on success, %FALSE on failure to read the permanent hardware
 * address.
 */
gboolean
nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex, guint8 *buf, size_t *length)
{
	_CHECK_SELF (self, klass, FALSE);

	if (length)
		*length = 0;

	g_return_val_if_fail (ifindex > 0, FALSE);
	g_return_val_if_fail (buf, FALSE);
	g_return_val_if_fail (length, FALSE);

1147 1148 1149
	if (klass->link_get_permanent_address)
		return klass->link_get_permanent_address (self, ifindex, buf, length);
	return FALSE;
1150 1151
}

1152
gboolean
1153
nm_platform_link_supports_carrier_detect (NMPlatform *self, int ifindex)
1154
{
1155 1156
	_CHECK_SELF (self, klass, FALSE);

1157 1158
	g_return_val_if_fail (ifindex >= 0, FALSE);

1159
	return klass->link_supports_carrier_detect (self, ifindex);
1160 1161 1162
}

gboolean
1163
nm_platform_link_supports_vlans (NMPlatform *self, int ifindex)