/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2013 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-lndp-ndisc.h" #include #include #include #include #include "NetworkManagerUtils.h" #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-platform/nm-platform.h" #include "libnm-platform/nmp-netns.h" #include "libnm-systemd-shared/nm-sd-utils-shared.h" #include "nm-l3cfg.h" #include "nm-ndisc-private.h" #include "nm-core-utils.h" #define _NMLOG_PREFIX_NAME "ndisc-lndp" /*****************************************************************************/ typedef struct { struct ndp *ndp; GSource *event_source; struct { NMRateLimit pio_lft; NMRateLimit mtu; NMRateLimit omit_prefix; NMRateLimit omit_dns; NMRateLimit omit_dnssl; } msg_ratelimit; } 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) /*****************************************************************************/ /* * If we log a message about an invalid RA packet, don't repeat the same message * at every packet received or sent. Rate limit the message to 6 every 12 hours * per type and per ndisc instance. */ #define LOG_INV_RA_WINDOW (12 * 3600) #define LOG_INV_RA_BURST 6 #define _LOG_INVALID_RA(ndisc, rate_limit, ...) \ G_STMT_START \ { \ NMNDisc *__ndisc = (ndisc); \ NMRateLimit *__rl = (rate_limit); \ const char *__ifname = nm_ndisc_get_ifname(__ndisc); \ \ if (__ifname && nm_logging_enabled(LOGL_WARN, LOGD_IP6) \ && nm_rate_limit_check(__rl, LOG_INV_RA_WINDOW, LOG_INV_RA_BURST)) { \ nm_log(LOGL_WARN, \ LOGD_IP6, \ __ifname, \ NULL, \ "ndisc (%s): " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ __ifname _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ } \ G_STMT_END /*****************************************************************************/ 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) { #define _ASSERT_ENUM(v1, v2) \ G_STMT_START \ { \ G_STATIC_ASSERT((NMIcmpv6RouterPref) (v1) == (v2)); \ G_STATIC_ASSERT((enum ndp_route_preference)(v2) == (v1)); \ G_STATIC_ASSERT((gint64) (v1) == (v2)); \ G_STATIC_ASSERT((gint64) (v2) == (v1)); \ } \ G_STMT_END switch (pref) { case NDP_ROUTE_PREF_LOW: case NDP_ROUTE_PREF_MEDIUM: case NDP_ROUTE_PREF_HIGH: _ASSERT_ENUM(NDP_ROUTE_PREF_LOW, NM_ICMPV6_ROUTER_PREF_LOW); _ASSERT_ENUM(NDP_ROUTE_PREF_MEDIUM, NM_ICMPV6_ROUTER_PREF_MEDIUM); _ASSERT_ENUM(NDP_ROUTE_PREF_HIGH, NM_ICMPV6_ROUTER_PREF_HIGH); return (NMIcmpv6RouterPref) pref; } /* 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; NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); NMNDiscDataInternal *rdata = ndisc->rdata; NMNDiscConfigMap changed = 0; NMNDiscGateway gateway; struct ndp_msgra *msgra = ndp_msgra(msg); struct in6_addr gateway_addr; const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); 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 timestamp %" G_GINT64_FORMAT ".%03d seconds", now_msec / 1000, (int) (now_msec % 1000)); 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 = NM_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. * * https://tools.ietf.org/html/rfc2461#section-4.2 : * A Lifetime of 0 indicates that the router is not a default router and * SHOULD NOT appear on the default router list. * * We handle that by tracking a gateway that expires right now. */ gateway = (NMNDiscGateway) { .address = gateway_addr, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msgra_router_lifetime(msgra)), .preference = _route_preference_coerce(ndp_msgra_route_preference(msgra)), }; /* 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_ip6_addr_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, .on_link = TRUE, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_prefix_valid_time(msg, offset)), }; if (nm_ndisc_add_route(ndisc, &route, now_msec)) changed |= NM_NDISC_CONFIG_ROUTES; } /* Address */ if (r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf(msg, offset)) { const guint32 valid_time = ndp_msg_opt_prefix_valid_time(msg, offset); const guint32 preferred_time = ndp_msg_opt_prefix_preferred_time(msg, offset); NMNDiscAddress address; /* * RFC 4862 Section 5.5.3 states: * c) If the preferred lifetime is greater than the valid lifetime, * silently ignore the Prefix Information option. A node MAY wish to * log a system management error in this case. */ if (preferred_time > valid_time) { _LOG_INVALID_RA( ndisc, &priv->msg_ratelimit.pio_lft, "ignoring Prefix Information Option with invalid lifetimes in received IPv6 " "router advertisement"); continue; } address = (NMNDiscAddress) { .address = r_network, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, valid_time), .expiry_preferred_msec = _nm_ndisc_lifetime_to_expiry(now_msec, preferred_time), }; if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec)) changed |= NM_NDISC_CONFIG_ADDRESSES; } } ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_ROUTE) { guint8 plen = ndp_msg_opt_route_prefix_len(msg, offset); struct in6_addr network; if (plen > 128) continue; if (plen == 0) { /* https://tools.ietf.org/html/rfc4191#section-3.1 : * When processing a Router Advertisement, a type C host first updates a * ::/0 route based on the Router Lifetime and Default Router Preference * in the Router Advertisement message header. [...] The Router Preference * and Lifetime values in a ::/0 Route Information Option override the * preference and lifetime values in the Router Advertisement header. */ gateway.preference = _route_preference_coerce(ndp_msg_opt_route_preference(msg, offset)); gateway.expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_route_lifetime(msg, offset)); continue; } nm_ip6_addr_clear_host_address(&network, ndp_msg_opt_route_prefix(msg, offset), plen); { const NMNDiscRoute route = { .network = network, .gateway = gateway_addr, .plen = plen, .on_link = FALSE, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_route_lifetime(msg, offset)), .preference = _route_preference_coerce(ndp_msg_opt_route_preference(msg, offset)), }; /* Routers through this particular gateway */ if (nm_ndisc_add_route(ndisc, &route, now_msec)) changed |= NM_NDISC_CONFIG_ROUTES; } } if (nm_ndisc_add_gateway(ndisc, &gateway, now_msec)) changed |= NM_NDISC_CONFIG_GATEWAYS; 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) { const NMNDiscDNSServer dns_server = { .address = *addr, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_rdnss_lifetime(msg, offset)), }; if (nm_ndisc_add_dns_server(ndisc, &dns_server, now_msec)) 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) { const NMNDiscDNSDomain dns_domain = { .domain = domain, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_dnssl_lifetime(msg, offset)), }; if (nm_ndisc_add_dns_domain(ndisc, &dns_domain, now_msec)) 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. */ _LOG_INVALID_RA(ndisc, &priv->msg_ratelimit.mtu, "ignoring too small MTU %u in received IPv6 " "router advertisement", mtu); } } #if HAVE_CLAT /* PREF64 */ ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREF64) { NMNDiscPref64 pref64; pref64 = (NMNDiscPref64) { .prefix = *ndp_msg_opt_pref64_prefix(msg, offset), .plen = ndp_msg_opt_pref64_prefix_length(msg, offset), .gateway = gateway.address, .gateway_preference = gateway.preference, .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_pref64_lifetime(msg, offset)), .gateway_expiry_msec = gateway.expiry_msec, }; /* libndp should only return lengths defined in RFC 8781 */ nm_assert(NM_IN_SET(pref64.plen, 96, 64, 56, 48, 40, 32)); if (nm_ndisc_add_pref64(ndisc, &pref64, now_msec)) { changed |= NM_NDISC_CONFIG_PREF64; } } #endif nm_ndisc_ra_received(ndisc, now_msec, changed); return 0; } static void * _ndp_msg_add_option(struct ndp_msg *msg, gsize len) { gsize payload_len = ndp_msg_payload_len(msg); void *ret = &((uint8_t *) msg)[payload_len]; nm_assert(len <= G_MAXSIZE - payload_len); len += payload_len; if (len > ndp_msg_payload_maxlen(msg)) return NULL; ndp_msg_payload_len_set(msg, len); nm_assert(len == ndp_msg_payload_len(msg)); return ret; } /*****************************************************************************/ /* "Recursive DNS Server Option" at https://tools.ietf.org/html/rfc8106#section-5.1 */ #define NM_ND_OPT_RDNSS 25 typedef struct _nm_packed { struct nd_opt_hdr header; uint16_t reserved; uint32_t lifetime; struct in6_addr addrs[0]; } NMLndpRdnssOption; G_STATIC_ASSERT(sizeof(NMLndpRdnssOption) == 8u); /*****************************************************************************/ /* "DNS Search List Option" at https://tools.ietf.org/html/rfc8106#section-5.2 */ #define NM_ND_OPT_DNSSL 31 typedef struct _nm_packed { struct nd_opt_hdr header; uint16_t reserved; uint32_t lifetime; uint8_t search_list[0]; } NMLndpDnsslOption; G_STATIC_ASSERT(sizeof(NMLndpDnsslOption) == 8u); /*****************************************************************************/ static gboolean send_ra(NMNDisc *ndisc, GError **error) { NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); NMNDiscDataInternal *rdata = ndisc->rdata; int errsv; struct in6_addr *addr; struct ndp_msg *msg; guint i; nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE); gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); 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++) { const NMNDiscAddress *address = &nm_g_array_index(rdata->addresses, NMNDiscAddress, i); struct nd_opt_prefix_info *prefix; prefix = _ndp_msg_add_option(msg, sizeof(*prefix)); if (!prefix) { /* Maybe we could send separate RAs, but why bother... */ _LOG_INVALID_RA( ndisc, &priv->msg_ratelimit.omit_prefix, "the outgoing IPv6 router advertisement is too big: omitting 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(_nm_ndisc_lifetime_from_expiry(now_msec, address->expiry_msec, TRUE)); prefix->nd_opt_pi_preferred_time = htonl(_nm_ndisc_lifetime_from_expiry(now_msec, address->expiry_preferred_msec, TRUE)); 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 > 0u) { NMLndpRdnssOption *option; gsize len = sizeof(*option) + (sizeof(option->addrs[0]) * rdata->dns_servers->len); option = _ndp_msg_add_option(msg, len); if (!option) { _LOG_INVALID_RA( ndisc, &priv->msg_ratelimit.omit_dns, "the outgoing IPv6 router advertisement is too big: omitting DNS information"); goto dns_servers_done; } 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++) { const NMNDiscDNSServer *dns_server = &nm_g_array_index(rdata->dns_servers, NMNDiscDNSServer, i); option->addrs[i] = dns_server->address; } } dns_servers_done: if (rdata->dns_domains->len > 0u) { NMLndpDnsslOption *option; gsize padding; gsize len; nm_str_buf_reset(&sbuf); for (i = 0; i < rdata->dns_domains->len; i++) { const NMNDiscDNSDomain *dns_domain = &nm_g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i); const char *domain = dns_domain->domain; gsize domain_l; gsize n_reserved; int r; if (nm_str_is_empty(domain)) { nm_assert_not_reached(); continue; } domain_l = strlen(domain); nm_str_buf_maybe_expand(&sbuf, domain_l + 2u, FALSE); n_reserved = sbuf.allocated - sbuf.len; r = nm_sd_dns_name_to_wire_format( domain, (guint8 *) (&nm_str_buf_get_str_unsafe(&sbuf)[sbuf.len]), n_reserved, FALSE); if (r < 0 || ((gsize) r) > n_reserved) { nm_assert(r != -ENOBUFS); nm_assert(r < 0); /* we don't expect errors here, unless the domain name is invalid. * That should have been caught (and rejected) by upper layers, but * at this point it seems dangerous to assert (as it's hard to review * that all callers got it correct). So instead silently ignore the error. */ continue; } nm_str_buf_set_size(&sbuf, sbuf.len + ((gsize) r), TRUE, FALSE); } if (sbuf.len == 0) { /* no valid domains? */ goto dns_domains_done; } len = sizeof(*option) + sbuf.len; padding = len % 8u; if (padding != 0u) { padding = 8u - padding; len += padding; } nm_assert(len % 8u == 0u); nm_assert(len > 0u); nm_assert(len / 8u >= 2u); if (len / 8u >= 256u || !(option = _ndp_msg_add_option(msg, len))) { _LOG_INVALID_RA( ndisc, &priv->msg_ratelimit.omit_dnssl, "the outgoing IPv6 router advertisement is too big: omitting DNS search list"); goto dns_domains_done; } nm_str_buf_append_c_len(&sbuf, '\0', padding); option->header.nd_opt_type = NM_ND_OPT_DNSSL; option->header.nd_opt_len = len / 8u; option->reserved = 0; option->lifetime = htonl(900); memcpy(option->search_list, nm_str_buf_get_str_unsafe(&sbuf), sbuf.len); } dns_domains_done: 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_add_source(fd, G_IO_IN, event_ready, ndisc); /* 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 void nm_lndp_ndisc_init(NMLndpNDisc *lndp_ndisc) {} NMNDisc * nm_lndp_ndisc_new(const NMNDiscConfig *config) { nm_auto_pop_netns NMPNetns *netns = NULL; gs_unref_object NMNDisc *ndisc = NULL; NMLndpNDiscPrivate *priv; int errsv; g_return_val_if_fail(config, NULL); g_return_val_if_fail(NM_IS_L3CFG(config->l3cfg), NULL); g_return_val_if_fail(config->network_id, NULL); if (!nm_platform_netns_push(nm_l3cfg_get_platform(config->l3cfg), &netns)) { /* The inability to change the name space is also considered * a fatal error. We have a FD open to the file descriptor, and * it's unclear how to handle (or recover from) a failure to setns(). */ g_return_val_if_reached(NULL); } ndisc = g_object_new(NM_TYPE_LNDP_NDISC, NM_NDISC_CONFIG, config, NULL); priv = NM_LNDP_NDISC_GET_PRIVATE(ndisc); errsv = ndp_open(&priv->ndp); if (errsv != 0) { /* This is serious. It might be ENOMEM or the inability to open (or modify) * a file descriptor. In all cases there is not much reason trying to recover * from that. File descriptors are a basic resource, that we just require (just * like memory). */ g_return_val_if_reached(NULL); } return g_steal_pointer(&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; }