/* 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 void _cleanup(NMNDisc *ndisc) { 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: nm_assert_not_reached(); break; } ndp_close(priv->ndp); priv->ndp = NULL; } } static void stop(NMNDisc *ndisc) { _cleanup(ndisc); } /*****************************************************************************/ 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); } void nm_lndp_ndisc_get_sysctl(NMPlatform *platform, const char *ifname, int * out_max_addresses, int * out_router_solicitations, int * out_router_solicitation_interval, guint32 * out_default_ra_timeout) { int router_solicitation_interval = 0; int router_solicitations = 0; if (out_max_addresses) { *out_max_addresses = ipv6_sysctl_get(platform, ifname, "max_addresses", 0, G_MAXINT32, NM_NDISC_MAX_ADDRESSES_DEFAULT); } if (out_router_solicitations || out_default_ra_timeout) { router_solicitations = ipv6_sysctl_get(platform, ifname, "router_solicitations", 1, G_MAXINT32, NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT); NM_SET_OUT(out_router_solicitations, router_solicitations); } if (out_router_solicitation_interval || out_default_ra_timeout) { router_solicitation_interval = ipv6_sysctl_get(platform, ifname, "router_solicitation_interval", 1, G_MAXINT32, NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT); NM_SET_OUT(out_router_solicitation_interval, router_solicitation_interval); } if (out_default_ra_timeout) { *out_default_ra_timeout = NM_MAX((((gint64) router_solicitations) * router_solicitation_interval) + 1, 30); } } /*****************************************************************************/ 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, int max_addresses, int router_solicitations, int router_solicitation_interval, guint32 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, max_addresses, NM_NDISC_ROUTER_SOLICITATIONS, router_solicitations, NM_NDISC_ROUTER_SOLICITATION_INTERVAL, router_solicitation_interval, NM_NDISC_RA_TIMEOUT, (guint) ra_timeout, 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); _cleanup(ndisc); 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->stop = stop; ndisc_class->send_rs = send_rs; ndisc_class->send_ra = send_ra; }