/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* nm-lndp-rdisc.c - Router discovery implementation using libndp * * 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) 2013 Red Hat, Inc. */ #include "nm-default.h" #include "nm-lndp-rdisc.h" #include #include /* stdarg.h included because of a bug in ndp.h */ #include #include #include "nm-rdisc-private.h" #include "NetworkManagerUtils.h" #include "nm-platform.h" #include "nmp-netns.h" #define _NMLOG_PREFIX_NAME "rdisc-lndp" /*****************************************************************************/ typedef struct { struct ndp *ndp; GIOChannel *event_channel; guint event_id; guint ra_timeout_id; /* first RA timeout */ } NMLndpRDiscPrivate; /*****************************************************************************/ struct _NMLndpRDisc { NMRDisc parent; NMLndpRDiscPrivate _priv; }; struct _NMLndpRDiscClass { NMRDiscClass parent; }; /*****************************************************************************/ G_DEFINE_TYPE (NMLndpRDisc, nm_lndp_rdisc, NM_TYPE_RDISC) #define NM_LNDP_RDISC_GET_PRIVATE(self) \ ({ \ /* preserve the const-ness of self. Unfortunately, that * way, @self cannot be a void pointer */ \ typeof (self) _self = (self); \ \ /* Get compiler error if variable is of wrong type */ \ _nm_unused const NMLndpRDisc *_self2 = (_self); \ \ nm_assert (NM_IS_LNDP_RDISC (_self)); \ &_self->_priv; \ }) /*****************************************************************************/ static gboolean send_rs (NMRDisc *rdisc, GError **error) { NMLndpRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE ((NMLndpRDisc *) rdisc); struct ndp_msg *msg; int errsv; errsv = ndp_msg_new (&msg, NDP_MSG_RS); if (errsv) { errsv = errsv > 0 ? errsv : -errsv; g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "cannot create router solicitation"); return FALSE; } ndp_msg_ifindex_set (msg, nm_rdisc_get_ifindex (rdisc)); errsv = ndp_msg_send (priv->ndp, msg); ndp_msg_destroy (msg); if (errsv) { errsv = errsv > 0 ? errsv : -errsv; g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "%s (%d)", g_strerror (errsv), errsv); return FALSE; } return TRUE; } _NM_UTILS_LOOKUP_DEFINE (static, translate_preference, enum ndp_route_preference, NMRDiscPreference, NM_UTILS_LOOKUP_DEFAULT (NM_RDISC_PREFERENCE_INVALID), NM_UTILS_LOOKUP_ITEM (NDP_ROUTE_PREF_LOW, NM_RDISC_PREFERENCE_LOW), NM_UTILS_LOOKUP_ITEM (NDP_ROUTE_PREF_MEDIUM, NM_RDISC_PREFERENCE_MEDIUM), NM_UTILS_LOOKUP_ITEM (NDP_ROUTE_PREF_HIGH, NM_RDISC_PREFERENCE_HIGH), ); static int receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) { NMRDisc *rdisc = (NMRDisc *) user_data; NMRDiscDataInternal *rdata = rdisc->rdata; NMRDiscConfigMap changed = 0; struct ndp_msgra *msgra = ndp_msgra (msg); struct in6_addr gateway_addr; guint32 now = nm_utils_get_monotonic_timestamp_s (); int offset; int hop_limit; /* Router discovery is subject to the following RFC documents: * * http://tools.ietf.org/html/rfc4861 * http://tools.ietf.org/html/rfc4862 * * The biggest difference from good old DHCP is that all configuration * items have their own lifetimes and they are merged from various * sources. Router discovery is *not* contract-based, so there is *no* * single time when the configuration is finished and updates can * come at any time. */ _LOGD ("received router advertisement at %u", now); /* DHCP level: * * The problem with DHCP level is what to do if subsequent * router advertisements carry different flags. Currently we just * rewrite the flag with every inbound RA. */ { NMRDiscDHCPLevel dhcp_level; if (ndp_msgra_flag_managed (msgra)) dhcp_level = NM_RDISC_DHCP_LEVEL_MANAGED; else if (ndp_msgra_flag_other (msgra)) dhcp_level = NM_RDISC_DHCP_LEVEL_OTHERCONF; else dhcp_level = NM_RDISC_DHCP_LEVEL_NONE; /* when receiving multiple RA (possibly from different routers), * let's keep the "most managed" level. */ G_STATIC_ASSERT_EXPR (NM_RDISC_DHCP_LEVEL_MANAGED > NM_RDISC_DHCP_LEVEL_OTHERCONF); G_STATIC_ASSERT_EXPR (NM_RDISC_DHCP_LEVEL_OTHERCONF > NM_RDISC_DHCP_LEVEL_NONE); dhcp_level = MAX (dhcp_level, rdata->public.dhcp_level); if (dhcp_level != rdata->public.dhcp_level) { rdata->public.dhcp_level = dhcp_level; changed |= NM_RDISC_CONFIG_DHCP_LEVEL; } } /* Default gateway: * * Subsequent router advertisements can represent new default gateways * on the network. We should present all of them in router preference * order. */ gateway_addr = *ndp_msg_addrto (msg); { NMRDiscGateway gateway = { .address = gateway_addr, .timestamp = now, .lifetime = ndp_msgra_router_lifetime (msgra), .preference = translate_preference (ndp_msgra_route_preference (msgra)), }; if (nm_rdisc_add_gateway (rdisc, &gateway)) changed |= NM_RDISC_CONFIG_GATEWAYS; } /* Addresses & Routes */ ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREFIX) { guint8 r_plen; struct in6_addr r_network; /* Device route */ r_plen = ndp_msg_opt_prefix_len (msg, offset); if (r_plen == 0 || r_plen > 128) continue; nm_utils_ip6_address_clear_host_address (&r_network, ndp_msg_opt_prefix (msg, offset), r_plen); if (ndp_msg_opt_prefix_flag_on_link (msg, offset)) { NMRDiscRoute route = { .network = r_network, .plen = r_plen, .timestamp = now, .lifetime = ndp_msg_opt_prefix_valid_time (msg, offset), }; if (nm_rdisc_add_route (rdisc, &route)) changed |= NM_RDISC_CONFIG_ROUTES; } /* Address */ if ( r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf (msg, offset)) { NMRDiscAddress address = { .address = r_network, .timestamp = now, .lifetime = ndp_msg_opt_prefix_valid_time (msg, offset), .preferred = ndp_msg_opt_prefix_preferred_time (msg, offset), }; if (address.preferred > address.lifetime) address.preferred = address.lifetime; if (nm_rdisc_complete_and_add_address (rdisc, &address)) changed |= NM_RDISC_CONFIG_ADDRESSES; } } ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_ROUTE) { NMRDiscRoute route = { .gateway = gateway_addr, .plen = ndp_msg_opt_route_prefix_len (msg, offset), .timestamp = now, .lifetime = ndp_msg_opt_route_lifetime (msg, offset), .preference = translate_preference (ndp_msg_opt_route_preference (msg, offset)), }; if (route.plen == 0 || route.plen > 128) continue; /* Routers through this particular gateway */ nm_utils_ip6_address_clear_host_address (&route.network, ndp_msg_opt_route_prefix (msg, offset), route.plen); if (nm_rdisc_add_route (rdisc, &route)) changed |= NM_RDISC_CONFIG_ROUTES; } /* DNS information */ ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_RDNSS) { static struct in6_addr *addr; int addr_index; ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) { NMRDiscDNSServer dns_server = { .address = *addr, .timestamp = now, .lifetime = ndp_msg_opt_rdnss_lifetime (msg, offset), }; /* Pad the lifetime somewhat to give a bit of slack in cases * where one RA gets lost or something (which can happen on unreliable * links like WiFi where certain types of frames are not retransmitted). * Note that 0 has special meaning and is therefore not adjusted. */ if (dns_server.lifetime && dns_server.lifetime < 7200) dns_server.lifetime = 7200; if (nm_rdisc_add_dns_server (rdisc, &dns_server)) changed |= NM_RDISC_CONFIG_DNS_SERVERS; } } ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_DNSSL) { char *domain; int domain_index; ndp_msg_opt_dnssl_for_each_domain (domain, domain_index, msg, offset) { NMRDiscDNSDomain dns_domain = { .domain = domain, .timestamp = now, .lifetime = ndp_msg_opt_rdnss_lifetime (msg, offset), }; /* Pad the lifetime somewhat to give a bit of slack in cases * where one RA gets lost or something (which can happen on unreliable * links like WiFi where certain types of frames are not retransmitted). * Note that 0 has special meaning and is therefore not adjusted. */ if (dns_domain.lifetime && dns_domain.lifetime < 7200) dns_domain.lifetime = 7200; if (nm_rdisc_add_dns_domain (rdisc, &dns_domain)) changed |= NM_RDISC_CONFIG_DNS_DOMAINS; } } hop_limit = ndp_msgra_curhoplimit (msgra); if (rdata->public.hop_limit != hop_limit) { rdata->public.hop_limit = hop_limit; changed |= NM_RDISC_CONFIG_HOP_LIMIT; } /* MTU */ ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_MTU) { guint32 mtu = ndp_msg_opt_mtu(msg, offset); if (mtu >= 1280) { if (rdata->public.mtu != mtu) { rdata->public.mtu = mtu; changed |= NM_RDISC_CONFIG_MTU; } } else { /* All sorts of bad things would happen if we accepted this. * Kernel would set it, but would flush out all IPv6 addresses away * from the link, even the link-local, and we wouldn't be able to * listen for further RAs that could fix the MTU. */ _LOGW ("MTU too small for IPv6 ignored: %d", mtu); } } nm_rdisc_ra_received (rdisc, now, changed); return 0; } static gboolean event_ready (GIOChannel *source, GIOCondition condition, NMRDisc *rdisc) { nm_auto_pop_netns NMPNetns *netns = NULL; NMLndpRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE ((NMLndpRDisc *) rdisc); _LOGD ("processing libndp events"); if (!nm_rdisc_netns_push (rdisc, &netns)) return G_SOURCE_CONTINUE; ndp_callall_eventfd_handler (priv->ndp); return G_SOURCE_CONTINUE; } static void start (NMRDisc *rdisc) { NMLndpRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE ((NMLndpRDisc *) rdisc); int fd = ndp_get_eventfd (priv->ndp); priv->event_channel = g_io_channel_unix_new (fd); priv->event_id = g_io_add_watch (priv->event_channel, G_IO_IN, (GIOFunc) event_ready, rdisc); /* Flush any pending messages to avoid using obsolete information */ event_ready (priv->event_channel, 0, rdisc); ndp_msgrcv_handler_register (priv->ndp, receive_ra, NDP_MSG_RA, nm_rdisc_get_ifindex (rdisc), rdisc); } /*****************************************************************************/ static inline int ipv6_sysctl_get (NMPlatform *platform, const char *ifname, const char *property, int min, int max, int defval) { return (int) nm_platform_sysctl_get_int_checked (platform, nm_utils_ip6_property_path (ifname, property), 10, min, max, defval); } static void nm_lndp_rdisc_init (NMLndpRDisc *lndp_rdisc) { } NMRDisc * nm_lndp_rdisc_new (NMPlatform *platform, int ifindex, const char *ifname, NMUtilsStableType stable_type, const char *network_id, NMSettingIP6ConfigAddrGenMode addr_gen_mode, GError **error) { nm_auto_pop_netns NMPNetns *netns = NULL; NMRDisc *rdisc; NMLndpRDiscPrivate *priv; int errsv; g_return_val_if_fail (NM_IS_PLATFORM (platform), NULL); g_return_val_if_fail (!error || !*error, NULL); if (!nm_platform_netns_push (platform, &netns)) return NULL; rdisc = g_object_new (NM_TYPE_LNDP_RDISC, NM_RDISC_PLATFORM, platform, NM_RDISC_STABLE_TYPE, (int) stable_type, NM_RDISC_IFINDEX, ifindex, NM_RDISC_IFNAME, ifname, NM_RDISC_NETWORK_ID, network_id, NM_RDISC_ADDR_GEN_MODE, (int) addr_gen_mode, NM_RDISC_MAX_ADDRESSES, ipv6_sysctl_get (platform, ifname, "max_addresses", 0, G_MAXINT32, NM_RDISC_MAX_ADDRESSES_DEFAULT), NM_RDISC_ROUTER_SOLICITATIONS, ipv6_sysctl_get (platform, ifname, "router_solicitations", 1, G_MAXINT32, NM_RDISC_ROUTER_SOLICITATIONS_DEFAULT), NM_RDISC_ROUTER_SOLICITATION_INTERVAL, ipv6_sysctl_get (platform, ifname, "router_solicitation_interval", 1, G_MAXINT32, NM_RDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT), NULL); priv = NM_LNDP_RDISC_GET_PRIVATE ((NMLndpRDisc *) rdisc); errsv = ndp_open (&priv->ndp); if (errsv != 0) { errsv = errsv > 0 ? errsv : -errsv; g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "failure creating libndp socket: %s (%d)", g_strerror (errsv), errsv); g_object_unref (rdisc); return NULL; } return rdisc; } static void dispose (GObject *object) { NMRDisc *rdisc = (NMRDisc *) object; NMLndpRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE ((NMLndpRDisc *) rdisc); nm_clear_g_source (&priv->event_id); g_clear_pointer (&priv->event_channel, g_io_channel_unref); if (priv->ndp) { ndp_msgrcv_handler_unregister (priv->ndp, receive_ra, NDP_MSG_RA, nm_rdisc_get_ifindex (rdisc), rdisc); ndp_close (priv->ndp); priv->ndp = NULL; } G_OBJECT_CLASS (nm_lndp_rdisc_parent_class)->dispose (object); } static void nm_lndp_rdisc_class_init (NMLndpRDiscClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NMRDiscClass *rdisc_class = NM_RDISC_CLASS (klass); object_class->dispose = dispose; rdisc_class->start = start; rdisc_class->send_rs = send_rs; }