about summary refs log tree commit diff
path: root/src/nm-netlink-utils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2011-10-28 23:04:16 +0200
committerMichael Biebl <biebl@debian.org>2011-10-28 23:04:16 +0200
commit485d149fe80915d94ed49ea6c2c0552cf7a3e79a (patch)
tree6a48492b46b8c1e3df1c58626c28f05a978c61f7 /src/nm-netlink-utils.c
parent263bf4c0c89bb88dc995acd9a6a2de9095fbd461 (diff)
Imported Upstream version 0.9.1.95 upstream/0.9.1.95
Diffstat (limited to 'src/nm-netlink-utils.c')
-rw-r--r--src/nm-netlink-utils.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/src/nm-netlink-utils.c b/src/nm-netlink-utils.c
new file mode 100644
index 00000000..4a62aa55
--- /dev/null
+++ b/src/nm-netlink-utils.c
@@ -0,0 +1,426 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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 of the License, 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) 2011 Red Hat, Inc.
+ */
+
+#include "logging/nm-logging.h"
+#include "nm-netlink-utils.h"
+#include "nm-netlink-monitor.h"
+#include "nm-netlink-compat.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netlink/netlink.h>
+#include <netlink/addr.h>
+#include <netlink/route/addr.h>
+
+#include <errno.h>
+
+typedef struct {
+	int ifindex;
+	int family;
+	void *addr;
+	int addrlen;
+	int prefix;
+	gboolean found;
+} FindAddrInfo;
+
+static void
+find_one_address (struct nl_object *object, void *user_data)
+{
+	FindAddrInfo *info = user_data;
+	struct rtnl_addr *addr = (struct rtnl_addr *) object;
+	struct nl_addr *local;
+	void *binaddr;
+
+	if (info->found)
+		return;
+
+	if (rtnl_addr_get_ifindex (addr) != info->ifindex)
+		return;
+	if (rtnl_addr_get_family (addr) != info->family)
+		return;
+
+	if (rtnl_addr_get_prefixlen (addr) != info->prefix)
+		return;
+
+	local = rtnl_addr_get_local (addr);
+	if (nl_addr_get_family (local) != info->family)
+		return;
+	if (nl_addr_get_len (local) != info->addrlen)
+		return;
+	binaddr = nl_addr_get_binary_addr (local);
+	if (binaddr) {
+		if (memcmp (binaddr, info->addr, info->addrlen) == 0)
+			info->found = TRUE; /* Yay, found it */
+	}
+}
+
+/**
+ * nm_netlink_find_address:
+ * @ifindex: interface index
+ * @family: address family, either AF_INET or AF_INET6
+ * @addr: binary address, either struct in_addr* or struct in6_addr*
+ * @prefix: prefix length
+ *
+ * Searches for a matching address on the given interface.
+ *
+ * Returns: %TRUE if the given address was found on the interface, %FALSE if it
+ * was not found or an error occurred.
+ **/
+gboolean
+nm_netlink_find_address (int ifindex,
+                         int family,
+                         void *addr,  /* struct in_addr or struct in6_addr */
+                         int prefix)
+{
+	struct nl_sock *nlh = NULL;
+	struct nl_cache *cache = NULL;
+	FindAddrInfo info;
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (family == AF_INET || family == AF_INET6, FALSE);
+	g_return_val_if_fail (addr != NULL, FALSE);
+	g_return_val_if_fail (prefix >= 0, FALSE);
+
+	memset (&info, 0, sizeof (info));
+	info.ifindex = ifindex;
+	info.family = family;
+	info.prefix = prefix;
+	info.addr = addr;
+	if (family == AF_INET)
+		info.addrlen = sizeof (struct in_addr);
+	else if (family == AF_INET6)
+		info.addrlen = sizeof (struct in6_addr);
+	else
+		g_assert_not_reached ();
+
+	nlh = nm_netlink_get_default_handle ();
+	if (nlh) {
+		rtnl_addr_alloc_cache(nlh, &cache);
+		if (cache) {
+			nl_cache_mngt_provide (cache);
+			nl_cache_foreach (cache, find_one_address, &info);
+			nl_cache_free (cache);
+		}
+	}
+	return info.found;
+}
+
+struct rtnl_route *
+nm_netlink_route_new (int ifindex,
+                      int family,
+                      int mss,
+                      ...)
+{
+	va_list var_args;
+	struct rtnl_route *route;
+	NmNlProp prop = NMNL_PROP_INVALID;
+	int value;
+
+	route = rtnl_route_alloc ();
+	g_return_val_if_fail (route != NULL, NULL);
+
+	if (ifindex >= 0)
+		rtnl_route_set_oif (route, ifindex);
+	if (family != AF_UNSPEC)
+		rtnl_route_set_family (route, family);
+	if (mss > 0)
+		rtnl_route_set_metric (route, RTAX_ADVMSS, mss);
+
+	va_start (var_args, mss);
+	prop = va_arg (var_args, NmNlProp);
+	while (prop != NMNL_PROP_INVALID) {
+		value = va_arg (var_args, int);
+
+		if (prop == NMNL_PROP_PROT && value != RTPROT_UNSPEC)
+			rtnl_route_set_protocol (route, value);
+		else if (prop == NMNL_PROP_TABLE && value != RT_TABLE_UNSPEC)
+			rtnl_route_set_table (route, value);
+		else if (prop == NMNL_PROP_SCOPE && value != RT_SCOPE_NOWHERE)
+			rtnl_route_set_scope (route, value);
+		else if (prop == NMNL_PROP_PRIO && value > 0)
+			rtnl_route_set_priority (route, value);
+
+		prop = va_arg (var_args, NmNlProp);
+	}
+	va_end (var_args);
+
+	return route;
+}
+
+/**
+ * nm_netlink_route_add:
+ * @route: the route to add
+ *
+ * Returns: zero if succeeded or the netlink error otherwise.
+ **/
+int nm_netlink_route_add(struct rtnl_route * route,
+			 int family,
+			 const void * dest, /* in_addr or in6_addr */
+			 int dest_prefix,
+			 const void * gateway, /* in_addr or in6_addr */
+			 int flags)
+{
+	struct nl_sock * sk;
+	struct nl_addr * dest_addr, * gw_addr;
+	void * tmp_addr;
+	int addrlen, err, log;
+
+	if(family == AF_INET) {
+		addrlen = sizeof(struct in_addr);
+		log = LOGD_IP4;
+	}
+	else if (family == AF_INET6) {
+		addrlen = sizeof(struct in6_addr);
+		log = LOGD_IP6;
+	} else {
+		g_assert_not_reached ();
+	}
+
+
+	sk = nm_netlink_get_default_handle();
+
+	/* Build up the destination address */
+	if (dest) {
+		/* Copy to preserve const */
+		tmp_addr = g_malloc0(addrlen);
+		memcpy(tmp_addr, dest, addrlen);
+
+		dest_addr = nl_addr_build (family, tmp_addr, addrlen);
+		g_free(tmp_addr);
+
+		g_return_val_if_fail (dest_addr != NULL, -NLE_INVAL);
+		nl_addr_set_prefixlen (dest_addr, dest_prefix);
+
+		rtnl_route_set_dst (route, dest_addr);
+		nl_addr_put (dest_addr);
+	}
+
+	/* Build up the gateway address */
+	if (gateway) {
+		tmp_addr = g_malloc0(addrlen);
+		memcpy(tmp_addr, gateway, addrlen);
+
+		gw_addr = nl_addr_build (family, tmp_addr, addrlen);
+		g_free(tmp_addr);
+
+		if (gw_addr) {
+			nl_addr_set_prefixlen (gw_addr, 0);
+			rtnl_route_set_gateway (route, gw_addr);
+			rtnl_route_set_scope (route, RT_SCOPE_UNIVERSE);
+			nl_addr_put(gw_addr);
+		} else {
+			nm_log_err (LOGD_DEVICE | log, "Invalid gateway");
+		}
+	}
+
+	err = rtnl_route_add (sk, route, flags);
+
+	/* LIBNL Bug: Aliased ESRCH */
+	if (err == -NLE_FAILURE)
+		err = -NLE_OBJ_NOTFOUND;
+
+	if (err)
+		nm_log_warn (LOGD_DEVICE | log,
+                             "Failed to add route %s",
+                             nl_geterror(err));
+
+	return err;
+}
+
+/**
+ * nm_netlink_route_delete:
+ * @route: the route to delete
+ *
+ * Returns: %TRUE if the request was successful, %FALSE if it failed
+ **/
+gboolean
+nm_netlink_route_delete (struct rtnl_route *route)
+{
+	struct nl_sock *nlh;
+	int err = 0;
+
+	g_return_val_if_fail (route != NULL, FALSE);
+
+	nlh = nm_netlink_get_default_handle ();
+	err = rtnl_route_delete (nlh, route, 0);
+
+	if (err)
+		nm_log_dbg (LOGD_IP4 | LOGD_IP6, "%s (%d)", nl_geterror(err), err);
+
+	/* Workaround libnl BUG: ESRCH is aliased to generic NLE_FAILURE
+	 * See: http://git.kernel.org/?p=libs/netlink/libnl.git;a=commit;h=7e9d5f */
+	if (err == -NLE_FAILURE)
+		err = -NLE_OBJ_NOTFOUND;
+
+	return (err && (err != -NLE_OBJ_NOTFOUND) && (err != -NLE_RANGE) ) ? FALSE : TRUE;
+}
+
+
+static void
+dump_route (struct rtnl_route *route)
+{
+	char buf6[INET6_ADDRSTRLEN];
+	char buf4[INET_ADDRSTRLEN];
+	struct nl_addr *nl;
+	struct in6_addr *addr6 = NULL;
+	struct in_addr *addr4 = NULL;
+	int prefixlen = 0;
+	const char *sf = "UNSPEC";
+	int family = rtnl_route_get_family (route);
+	guint32 log_level = LOGD_IP4 | LOGD_IP6;
+
+	memset (buf6, 0, sizeof (buf6));
+	memset (buf4, 0, sizeof (buf4));
+	nl = rtnl_route_get_dst (route);
+	if (nl) {
+		if (nl_addr_get_family (nl) == AF_INET) {
+			addr4 = nl_addr_get_binary_addr (nl);
+			if (addr4)
+				inet_ntop (AF_INET, addr4, &buf4[0], sizeof (buf4));
+		} else if (nl_addr_get_family (nl) == AF_INET6) {
+			addr6 = nl_addr_get_binary_addr (nl);
+			if (addr6)
+				inet_ntop (AF_INET6, addr6, &buf6[0], sizeof (buf6));
+		}
+		prefixlen = nl_addr_get_prefixlen (nl);
+	}
+
+	if (family == AF_INET) {
+		sf = "INET";
+		log_level = LOGD_IP4;
+	} else if (family == AF_INET6) {
+		sf = "INET6";
+		log_level = LOGD_IP6;
+	}
+
+	nm_log_dbg (log_level, "  route idx %d family %s (%d) addr %s/%d",
+	            rtnl_route_get_oif (route),
+	            sf, family,
+	            strlen (buf4) ? buf4 : (strlen (buf6) ? buf6 : "<unknown>"),
+	            prefixlen);
+}
+
+
+typedef struct {
+	int ifindex;
+	int family;
+	int scope;
+	gboolean ignore_inet6_ll_mc;
+	char *iface;
+	NlRouteForeachFunc callback;
+	gpointer user_data;
+	struct rtnl_route *out_route;
+} ForeachRouteInfo;
+
+static void
+foreach_route_cb (struct nl_object *object, void *user_data)
+{
+	ForeachRouteInfo *info = user_data;
+	struct rtnl_route *route = (struct rtnl_route *) object;
+	struct nl_addr *dst;
+
+	if (info->out_route)
+		return;
+
+	if (nm_logging_level_enabled (LOGL_DEBUG))
+		dump_route (route);
+
+	if (   info->ifindex >= 0
+	    && rtnl_route_get_oif (route) != info->ifindex)
+		return;
+
+	if (   info->scope != RT_SCOPE_UNIVERSE
+	    && rtnl_route_get_scope (route) != info->scope)
+		return;
+
+	if (   info->family != AF_UNSPEC
+	    && rtnl_route_get_family (route) != info->family)
+		return;
+
+	dst = rtnl_route_get_dst (route);
+
+	/* Check for IPv6 LL and MC routes that might need to be ignored */
+	if (   (info->family == AF_INET6 || info->family == AF_UNSPEC)
+	    && (rtnl_route_get_family (route) == AF_INET6)) {
+		struct in6_addr *addr = NULL;
+
+		if (dst)
+			addr = nl_addr_get_binary_addr (dst);
+		if (addr) {
+			if (   IN6_IS_ADDR_LINKLOCAL (addr)
+			    || IN6_IS_ADDR_MC_LINKLOCAL (addr)
+			    || (IN6_IS_ADDR_MULTICAST (addr) && (nl_addr_get_prefixlen (dst) == 8)))
+				return;
+		}
+	}
+
+	info->out_route = info->callback (route, dst, info->iface, info->user_data);
+	if (info->out_route) {
+		/* Ref the route so it sticks around after the cache is cleared */
+		rtnl_route_get (info->out_route);
+	}
+}
+
+/**
+ * nm_netlink_foreach_route:
+ * @ifindex: the interface index to filter routes for
+ * @family: the address family to filter routes for
+ * @scope: route scope, eg RT_SCOPE_LINK
+ * @ignore_inet6_ll_mc: if %TRUE ignore IPv6 link-local and multi-cast routes
+ * @callback: function called when a route matches the filter
+ * @user_data: data passed to @callback
+ *
+ * Filters each route in the routing table against the given @ifindex and
+ * @family (if given) and calls @callback for each matching route.
+ *
+ * Returns: a route if @callback returned one; the caller must dispose of the
+ * route using rtnl_route_put() when it is no longer required.
+ **/
+struct rtnl_route *
+nm_netlink_foreach_route (int ifindex,
+                          int family,
+                          int scope,
+                          gboolean ignore_inet6_ll_mc,
+                          NlRouteForeachFunc callback,
+                          gpointer user_data)
+{
+	struct nl_cache *cache;
+	ForeachRouteInfo info;
+
+	memset (&info, 0, sizeof (info));
+	info.ifindex = ifindex;
+	info.family = family;
+	info.scope = scope;
+	info.ignore_inet6_ll_mc = ignore_inet6_ll_mc;
+	info.callback = callback;
+	info.user_data = user_data;
+	info.iface = nm_netlink_index_to_iface (ifindex);
+
+	rtnl_route_alloc_cache (nm_netlink_get_default_handle (), family, NL_AUTO_PROVIDE, &cache);
+	g_warn_if_fail (cache != NULL);
+	if (cache) {
+		nl_cache_foreach (cache, foreach_route_cb, &info);
+		nl_cache_free (cache);
+	}
+	g_free (info.iface);
+	return info.out_route;
+}
+
+