mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-25 09:30:16 +01:00
Add two helper functions, one for IPv4 and one for IPv6, to ensure that the core code benefits from compiler type checking when adding routes. Previously nm_netlink_route_add() took a void* which meant we messed up adding IPv6 routes sometimes due to confusion over what was supposed to be passed to it. Also fixes what appears to be a C&P error with add_ip6_route_to_gateway(). Reported by Tomáš Trnka <tomastrnka@gmx.com>
473 lines
13 KiB
C
473 lines
13 KiB
C
/* -*- 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;
|
|
}
|
|
|
|
/**
|
|
* _route_add:
|
|
* @route: the route to add
|
|
* @family: address family, either %AF_INET or %AF_INET6
|
|
* @dest: the route destination address, either a struct in_addr or a struct
|
|
* in6_addr depending on @family
|
|
* @dest_prefix: the CIDR prefix of @dest
|
|
* @gateway: the gateway through which to reach @dest, if any; given as a
|
|
* struct in_addr or struct in6_addr depending on @family
|
|
* @flags: flags to pass to rtnl_route_add(), eg %NLM_F_REPLACE
|
|
*
|
|
* Returns: zero if succeeded or the netlink error otherwise.
|
|
**/
|
|
static int
|
|
_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;
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* nm_netlink_route4_add:
|
|
* @route: the route to add
|
|
* @dest: the route destination address in network byte order
|
|
* @dest_prefix: the CIDR prefix of @dest
|
|
* @gateway: the gateway through which to reach @dest, if any, in network byte order
|
|
* @flags: flags to pass to rtnl_route_add(), eg %NLM_F_REPLACE
|
|
*
|
|
* Adds an IPv4 route with the given parameters.
|
|
*
|
|
* Returns: zero if succeeded or the netlink error otherwise.
|
|
**/
|
|
int
|
|
nm_netlink_route4_add (struct rtnl_route *route,
|
|
guint32 *dst,
|
|
int prefix,
|
|
guint32 *gw,
|
|
int flags)
|
|
{
|
|
return _route_add (route, AF_INET, dst, prefix, gw, flags);
|
|
}
|
|
|
|
/**
|
|
* nm_netlink_route6_add:
|
|
* @route: the route to add
|
|
* @dest: the route destination address
|
|
* @dest_prefix: the CIDR prefix of @dest
|
|
* @gateway: the gateway through which to reach @dest, if any
|
|
* @flags: flags to pass to rtnl_route_add(), eg %NLM_F_REPLACE
|
|
*
|
|
* Adds an IPv6 route with the given parameters.
|
|
*
|
|
* Returns: zero if succeeded or the netlink error otherwise.
|
|
**/
|
|
int
|
|
nm_netlink_route6_add (struct rtnl_route *route,
|
|
const struct in6_addr *dst,
|
|
int prefix,
|
|
const struct in6_addr *gw,
|
|
int flags)
|
|
{
|
|
return _route_add (route, AF_INET6, dst, prefix, gw, flags);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
|