// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2013 Red Hat, Inc. */ #include "nm-default.h" #include "nm-lndp-ndisc.h" #include #include /* stdarg.h included because of a bug in ndp.h */ #include #include #include "nm-ndisc-private.h" #include "NetworkManagerUtils.h" #include "platform/nm-platform.h" #include "platform/nmp-netns.h" #define _NMLOG_PREFIX_NAME "ndisc-lndp" /*****************************************************************************/ typedef struct { struct ndp *ndp; GSource *event_source; } NMLndpNDiscPrivate; /*****************************************************************************/ struct _NMLndpNDisc { NMNDisc parent; NMLndpNDiscPrivate _priv; }; struct _NMLndpNDiscClass { NMNDiscClass parent; }; /*****************************************************************************/ G_DEFINE_TYPE (NMLndpNDisc, nm_lndp_ndisc, NM_TYPE_NDISC) #define NM_LNDP_NDISC_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMLndpNDisc, NM_IS_LNDP_NDISC, NMNDisc) /*****************************************************************************/ static gboolean send_rs (NMNDisc *ndisc, GError **error) { NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc); struct ndp_msg *msg; int errsv; errsv = ndp_msg_new (&msg, NDP_MSG_RS); if (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_ndisc_get_ifindex (ndisc)); errsv = ndp_msg_send (priv->ndp, msg); ndp_msg_destroy (msg); if (errsv) { errsv = nm_errno_native (errsv); g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "%s (%d)", nm_strerror_native (errsv), errsv); return FALSE; } return TRUE; } static NMIcmpv6RouterPref _route_preference_coerce (enum ndp_route_preference pref) { switch (pref) { case NDP_ROUTE_PREF_LOW: return NM_ICMPV6_ROUTER_PREF_LOW; case NDP_ROUTE_PREF_MEDIUM: return NM_ICMPV6_ROUTER_PREF_MEDIUM; case NDP_ROUTE_PREF_HIGH: return NM_ICMPV6_ROUTER_PREF_HIGH; } /* unexpected value must be treated as MEDIUM (RFC 4191). */ return NM_ICMPV6_ROUTER_PREF_MEDIUM; } static int receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) { NMNDisc *ndisc = (NMNDisc *) user_data; NMNDiscDataInternal *rdata = ndisc->rdata; NMNDiscConfigMap changed = 0; struct ndp_msgra *msgra = ndp_msgra (msg); struct in6_addr gateway_addr; gint32 now = nm_utils_get_monotonic_timestamp_sec (); int offset; int hop_limit; guint32 val; /* 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 %d", (int) now); gateway_addr = *ndp_msg_addrto (msg); if (IN6_IS_ADDR_UNSPECIFIED (&gateway_addr)) g_return_val_if_reached (0); /* 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. */ { NMNDiscDHCPLevel dhcp_level; if (ndp_msgra_flag_managed (msgra)) dhcp_level = NM_NDISC_DHCP_LEVEL_MANAGED; else if (ndp_msgra_flag_other (msgra)) dhcp_level = NM_NDISC_DHCP_LEVEL_OTHERCONF; else dhcp_level = NM_NDISC_DHCP_LEVEL_NONE; /* when receiving multiple RA (possibly from different routers), * let's keep the "most managed" level. */ G_STATIC_ASSERT_EXPR (NM_NDISC_DHCP_LEVEL_MANAGED > NM_NDISC_DHCP_LEVEL_OTHERCONF); G_STATIC_ASSERT_EXPR (NM_NDISC_DHCP_LEVEL_OTHERCONF > NM_NDISC_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_NDISC_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. */ { const NMNDiscGateway gateway = { .address = gateway_addr, .timestamp = now, .lifetime = ndp_msgra_router_lifetime (msgra), .preference = _route_preference_coerce (ndp_msgra_route_preference (msgra)), }; if (nm_ndisc_add_gateway (ndisc, &gateway)) changed |= NM_NDISC_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 ( IN6_IS_ADDR_UNSPECIFIED (&r_network) || IN6_IS_ADDR_LINKLOCAL (&r_network)) continue; if (ndp_msg_opt_prefix_flag_on_link (msg, offset)) { const NMNDiscRoute route = { .network = r_network, .plen = r_plen, .timestamp = now, .lifetime = ndp_msg_opt_prefix_valid_time (msg, offset), }; if (nm_ndisc_add_route (ndisc, &route)) changed |= NM_NDISC_CONFIG_ROUTES; } /* Address */ if ( r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf (msg, offset)) { NMNDiscAddress 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) { if (nm_ndisc_complete_and_add_address (ndisc, &address, now)) changed |= NM_NDISC_CONFIG_ADDRESSES; } } } ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_ROUTE) { NMNDiscRoute route = { .gateway = gateway_addr, .plen = ndp_msg_opt_route_prefix_len (msg, offset), .timestamp = now, .lifetime = ndp_msg_opt_route_lifetime (msg, offset), .preference = _route_preference_coerce (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_ndisc_add_route (ndisc, &route)) changed |= NM_NDISC_CONFIG_ROUTES; } /* DNS information */ ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_RDNSS) { struct in6_addr *addr; int addr_index; ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) { NMNDiscDNSServer 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 Wi-Fi 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_ndisc_add_dns_server (ndisc, &dns_server)) changed |= NM_NDISC_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) { NMNDiscDNSDomain dns_domain = { .domain = domain, .timestamp = now, .lifetime = ndp_msg_opt_dnssl_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 Wi-Fi 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_ndisc_add_dns_domain (ndisc, &dns_domain)) changed |= NM_NDISC_CONFIG_DNS_DOMAINS; } } hop_limit = ndp_msgra_curhoplimit (msgra); if (rdata->public.hop_limit != hop_limit) { rdata->public.hop_limit = hop_limit; changed |= NM_NDISC_CONFIG_HOP_LIMIT; } val = ndp_msgra_reachable_time (msgra); if (val && rdata->public.reachable_time_ms != val) { rdata->public.reachable_time_ms = val; changed |= NM_NDISC_CONFIG_REACHABLE_TIME; } val = ndp_msgra_retransmit_time (msgra); if (val && rdata->public.retrans_timer_ms != val) { rdata->public.retrans_timer_ms = val; changed |= NM_NDISC_CONFIG_RETRANS_TIMER; } /* 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_NDISC_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_ndisc_ra_received (ndisc, now, changed); return 0; } static void * _ndp_msg_add_option (struct ndp_msg *msg, int len) { void *ret = (uint8_t *)msg + ndp_msg_payload_len (msg); len += ndp_msg_payload_len (msg); if (len > ndp_msg_payload_maxlen (msg)) return NULL; ndp_msg_payload_len_set (msg, len); return ret; } #define NM_ND_OPT_RDNSS 25 typedef struct { struct nd_opt_hdr header; uint16_t reserved; uint32_t lifetime;; struct in6_addr addrs[0]; } NMLndpRdnssOption; #define NM_ND_OPT_DNSSL 31 typedef struct { struct nd_opt_hdr header; uint16_t reserved; uint32_t lifetime; char search_list[0]; } NMLndpDnsslOption; static gboolean send_ra (NMNDisc *ndisc, GError **error) { NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc); NMNDiscDataInternal *rdata = ndisc->rdata; gint32 now = nm_utils_get_monotonic_timestamp_sec (); int errsv; struct in6_addr *addr; struct ndp_msg *msg; struct nd_opt_prefix_info *prefix; int i; errsv = ndp_msg_new (&msg, NDP_MSG_RA); if (errsv) { g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "cannot create a router advertisement"); return FALSE; } ndp_msg_ifindex_set (msg, nm_ndisc_get_ifindex (ndisc)); /* Multicast to all nodes. */ addr = ndp_msg_addrto (msg); addr->s6_addr32[0] = htonl(0xff020000); addr->s6_addr32[1] = 0; addr->s6_addr32[2] = 0; addr->s6_addr32[3] = htonl(0x1); ndp_msgra_router_lifetime_set (ndp_msgra (msg), NM_NDISC_ROUTER_LIFETIME); /* The device let us know about all addresses that the device got * whose prefixes are suitable for delegating. Let's announce them. */ for (i = 0; i < rdata->addresses->len; i++) { NMNDiscAddress *address = &g_array_index (rdata->addresses, NMNDiscAddress, i); guint32 age = NM_CLAMP ((gint64) now - (gint64) address->timestamp, 0, G_MAXUINT32 - 1); guint32 lifetime = address->lifetime; guint32 preferred = address->preferred; /* Clamp the life times if they're not forever. */ if (lifetime != NM_NDISC_INFINITY) lifetime = lifetime > age ? lifetime - age : 0; if (preferred != NM_NDISC_INFINITY) preferred = preferred > age ? preferred - age : 0; prefix = _ndp_msg_add_option (msg, sizeof(*prefix)); if (!prefix) { /* Maybe we could sent separate RAs, but why bother... */ _LOGW ("The RA is too big, had to omit some some prefixes."); break; } prefix->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; prefix->nd_opt_pi_len = 4; prefix->nd_opt_pi_prefix_len = 64; prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK; prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; prefix->nd_opt_pi_valid_time = htonl(lifetime); prefix->nd_opt_pi_preferred_time = htonl(preferred); prefix->nd_opt_pi_prefix.s6_addr32[0] = address->address.s6_addr32[0]; prefix->nd_opt_pi_prefix.s6_addr32[1] = address->address.s6_addr32[1]; prefix->nd_opt_pi_prefix.s6_addr32[2] = 0; prefix->nd_opt_pi_prefix.s6_addr32[3] = 0; } if (rdata->dns_servers->len) { NMLndpRdnssOption *option; int len = sizeof(*option) + sizeof(option->addrs[0]) * rdata->dns_servers->len; option = _ndp_msg_add_option (msg, len); if (option) { option->header.nd_opt_type = NM_ND_OPT_RDNSS; option->header.nd_opt_len = len / 8; option->lifetime = htonl (900); for (i = 0; i < rdata->dns_servers->len; i++) { NMNDiscDNSServer *dns_server = &g_array_index (rdata->dns_servers, NMNDiscDNSServer, i); option->addrs[i] = dns_server->address; } } else { _LOGW ("The RA is too big, had to omit DNS information."); } } if (rdata->dns_domains->len) { NMLndpDnsslOption *option; NMNDiscDNSDomain *dns_server; int len = sizeof(*option); char *search_list; for (i = 0; i < rdata->dns_domains->len; i++) { dns_server = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i); len += strlen (dns_server->domain) + 2; } len = (len + 8) & ~0x7; option = _ndp_msg_add_option (msg, len); if (option) { option->header.nd_opt_type = NM_ND_OPT_DNSSL; option->header.nd_opt_len = len / 8; option->lifetime = htonl (900); search_list = option->search_list; for (i = 0; i < rdata->dns_domains->len; i++) { NMNDiscDNSDomain *dns_domain = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i); uint8_t domain_len = strlen (dns_domain->domain); *search_list++ = domain_len; memcpy (search_list, dns_domain->domain, domain_len); search_list += domain_len; *search_list++ = '\0'; } } else { _LOGW ("The RA is too big, had to omit DNS search list."); } } errsv = ndp_msg_send (priv->ndp, msg); ndp_msg_destroy (msg); if (errsv) { errsv = nm_errno_native (errsv); g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "%s (%d)", nm_strerror_native (errsv), errsv); return FALSE; } return TRUE; } static int receive_rs (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) { NMNDisc *ndisc = user_data; nm_ndisc_rs_received (ndisc); return 0; } static gboolean event_ready (int fd, GIOCondition condition, gpointer user_data) { gs_unref_object NMNDisc *ndisc = g_object_ref (NM_NDISC (user_data)); nm_auto_pop_netns NMPNetns *netns = NULL; NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc); _LOGD ("processing libndp events"); if (!nm_ndisc_netns_push (ndisc, &netns)) { /* something is very wrong. Stop handling events. */ nm_clear_g_source_inst (&priv->event_source); return G_SOURCE_REMOVE; } ndp_callall_eventfd_handler (priv->ndp); return G_SOURCE_CONTINUE; } static void start (NMNDisc *ndisc) { NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc); int fd; g_return_if_fail (!priv->event_source); fd = ndp_get_eventfd (priv->ndp); priv->event_source = nm_g_unix_fd_source_new (fd, G_IO_IN, G_PRIORITY_DEFAULT, event_ready, ndisc, NULL); g_source_attach (priv->event_source, NULL); /* Flush any pending messages to avoid using obsolete information */ event_ready (fd, 0, ndisc); switch (nm_ndisc_get_node_type (ndisc)) { case NM_NDISC_NODE_TYPE_HOST: ndp_msgrcv_handler_register (priv->ndp, receive_ra, NDP_MSG_RA, nm_ndisc_get_ifindex (ndisc), ndisc); break; case NM_NDISC_NODE_TYPE_ROUTER: ndp_msgrcv_handler_register (priv->ndp, receive_rs, NDP_MSG_RS, nm_ndisc_get_ifindex (ndisc), ndisc); break; default: g_assert_not_reached (); } } /*****************************************************************************/ static int ipv6_sysctl_get (NMPlatform *platform, const char *ifname, const char *property, int min, int max, int defval) { return nm_platform_sysctl_ip_conf_get_int_checked (platform, AF_INET6, ifname, property, 10, min, max, defval); } static void nm_lndp_ndisc_init (NMLndpNDisc *lndp_ndisc) { } NMNDisc * nm_lndp_ndisc_new (NMPlatform *platform, int ifindex, const char *ifname, NMUtilsStableType stable_type, const char *network_id, NMSettingIP6ConfigAddrGenMode addr_gen_mode, NMNDiscNodeType node_type, gint32 ra_timeout, GError **error) { nm_auto_pop_netns NMPNetns *netns = NULL; NMNDisc *ndisc; NMLndpNDiscPrivate *priv; int errsv; g_return_val_if_fail (NM_IS_PLATFORM (platform), NULL); g_return_val_if_fail (!error || !*error, NULL); g_return_val_if_fail (network_id, NULL); if (!nm_platform_netns_push (platform, &netns)) return NULL; ndisc = g_object_new (NM_TYPE_LNDP_NDISC, NM_NDISC_PLATFORM, platform, NM_NDISC_STABLE_TYPE, (int) stable_type, NM_NDISC_IFINDEX, ifindex, NM_NDISC_IFNAME, ifname, NM_NDISC_NETWORK_ID, network_id, NM_NDISC_ADDR_GEN_MODE, (int) addr_gen_mode, NM_NDISC_NODE_TYPE, (int) node_type, NM_NDISC_MAX_ADDRESSES, ipv6_sysctl_get (platform, ifname, "max_addresses", 0, G_MAXINT32, NM_NDISC_MAX_ADDRESSES_DEFAULT), NM_NDISC_RA_TIMEOUT, (int) ra_timeout, NM_NDISC_ROUTER_SOLICITATIONS, ipv6_sysctl_get (platform, ifname, "router_solicitations", 1, G_MAXINT32, NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT), NM_NDISC_ROUTER_SOLICITATION_INTERVAL, ipv6_sysctl_get (platform, ifname, "router_solicitation_interval", 1, G_MAXINT32, NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT), NULL); priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc); errsv = ndp_open (&priv->ndp); if (errsv != 0) { errsv = nm_errno_native (errsv); g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "failure creating libndp socket: %s (%d)", nm_strerror_native (errsv), errsv); g_object_unref (ndisc); return NULL; } return ndisc; } static void dispose (GObject *object) { NMNDisc *ndisc = NM_NDISC (object); NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc); nm_clear_g_source_inst (&priv->event_source); if (priv->ndp) { switch (nm_ndisc_get_node_type (ndisc)) { case NM_NDISC_NODE_TYPE_HOST: ndp_msgrcv_handler_unregister (priv->ndp, receive_ra, NDP_MSG_RA, nm_ndisc_get_ifindex (ndisc), ndisc); break; case NM_NDISC_NODE_TYPE_ROUTER: ndp_msgrcv_handler_unregister (priv->ndp, receive_rs, NDP_MSG_RS, nm_ndisc_get_ifindex (ndisc), ndisc); break; default: g_assert_not_reached (); } ndp_close (priv->ndp); priv->ndp = NULL; } G_OBJECT_CLASS (nm_lndp_ndisc_parent_class)->dispose (object); } static void nm_lndp_ndisc_class_init (NMLndpNDiscClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NMNDiscClass *ndisc_class = NM_NDISC_CLASS (klass); object_class->dispose = dispose; ndisc_class->start = start; ndisc_class->send_rs = send_rs; ndisc_class->send_ra = send_ra; }