From a8d3cf55c4886c8df2a61fb8cb9197bfe1e8f57e Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 30 Apr 2015 12:34:45 +0200 Subject: [PATCH 1/8] test: add EXIT_SKIP define --- include/nm-test-utils.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/nm-test-utils.h b/include/nm-test-utils.h index ccbb415e9e..1d046484fc 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -91,6 +91,9 @@ #include "gsystem-local-alloc.h" +/* Analog to EXIT_SUCCESS and EXIT_FAILURE. */ +#define EXIT_SKIP (77) + /*******************************************************************************/ /* general purpose functions that have no dependency on other nmtst functions */ From 1bd99ff273490a65140e88b5c48f6356d8041a51 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 30 Apr 2015 12:12:22 +0200 Subject: [PATCH 2/8] test: add nmtst_test_quick() Utility function for long-running tests to check whether they should run. It has the same use as g_test_quick(), but it can also be set via NMTST_DEBUG environment variable. You can set the compile time default via CFLAGS='-DNMTST_TEST_QUICK=FALSE' ./configure and overwrite it at runtime via NMTST_DEBUG=no-default,quick ./tests/test-my --- include/nm-test-utils.h | 48 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/include/nm-test-utils.h b/include/nm-test-utils.h index 1d046484fc..409df00ca6 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -74,6 +74,12 @@ * For example * NMTST_DEBUG="sudo-cmd=$PWD/tools/test-sudo-wrapper.sh" make -C src/platform/tests/ check * + * "slow|quick|thorough": enable/disable long-running tests. This sets nmtst_test_quick(). + * Whether long-running tests are enabled is determined as follows (highest priority first): + * - specifying the value in NMTST_DEBUG has highest priority + * - respect g_test_quick(), if the command line contains '-mslow', '-mquick', '-mthorough'. + * - use compile time default + * *******************************************************************************/ #include @@ -126,6 +132,7 @@ struct __nmtst_internal gboolean is_debug; gboolean assert_logging; gboolean no_expect_message; + gboolean test_quick; char *sudo_cmd; char **orig_argv; }; @@ -237,6 +244,9 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ int i; gboolean no_expect_message = FALSE; gboolean _out_set_logging; + gboolean test_quick = FALSE; + gboolean test_quick_set = FALSE; + gboolean test_quick_argv = FALSE; if (!out_set_logging) out_set_logging = &_out_set_logging; @@ -301,6 +311,12 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ sudo_cmd = g_strdup (&debug[strlen ("sudo-cmd=")]); } else if (!g_ascii_strcasecmp (debug, "no-expect-message")) { no_expect_message = TRUE; + } else if (!g_ascii_strcasecmp (debug, "slow") || !g_ascii_strcasecmp (debug, "thorough")) { + test_quick = FALSE; + test_quick_set = TRUE; + } else if (!g_ascii_strcasecmp (debug, "quick")) { + test_quick = TRUE; + test_quick_set = TRUE; } else { char *msg = g_strdup_printf (">>> nmtst: ignore unrecognized NMTST_DEBUG option \"%s\"", debug); @@ -313,14 +329,33 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ } if (argv && *argv) { - char **a = *argv; + char **a = *argv; - for (; *a; a++) { + for (; *a; a++) { if (!g_ascii_strcasecmp (*a, "--debug")) is_debug = TRUE; else if (!g_ascii_strcasecmp (*a, "--no-debug")) is_debug = FALSE; - } + else if ( !strcmp (*a, "-mslow") + || !strcmp (*a, "-m=slow") + || !strcmp (*a, "-mthorough") + || !strcmp (*a, "-m=thorough") + || !strcmp (*a, "-mquick") + || !strcmp (*a, "-m=quick")) + test_quick_argv = TRUE; + } + } + + if (test_quick_set) + __nmtst_internal.test_quick = test_quick; + else if (test_quick_argv) + __nmtst_internal.test_quick = g_test_quick (); + else { +#ifdef NMTST_TEST_QUICK + __nmtst_internal.test_quick = NMTST_TEST_QUICK; +#else + __nmtst_internal.test_quick = FALSE; +#endif } __nmtst_internal.is_debug = is_debug; @@ -432,6 +467,13 @@ nmtst_is_debug (void) return __nmtst_internal.is_debug; } +inline static gboolean +nmtst_test_quick (void) +{ + g_assert (nmtst_initialized ()); + return __nmtst_internal.test_quick; +} + #if GLIB_CHECK_VERSION(2,34,0) #undef g_test_expect_message #define g_test_expect_message(...) \ From 5955f82f01f2da4b0107be6bc4f7c65d26d77b24 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 9 Apr 2015 09:27:50 -0500 Subject: [PATCH 3/8] rdisc: add missing chain up to parent finalize/dispose --- src/rdisc/nm-lndp-rdisc.c | 2 ++ src/rdisc/nm-rdisc.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/rdisc/nm-lndp-rdisc.c b/src/rdisc/nm-lndp-rdisc.c index 922cb3dd76..28fef1fd81 100644 --- a/src/rdisc/nm-lndp-rdisc.c +++ b/src/rdisc/nm-lndp-rdisc.c @@ -724,6 +724,8 @@ dispose (GObject *object) ndp_close (priv->ndp); priv->ndp = NULL; } + + G_OBJECT_CLASS (nm_lndp_rdisc_parent_class)->dispose (object); } static void diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index 8729fa63df..dd19eed557 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -169,6 +169,8 @@ nm_rdisc_finalize (GObject *object) g_array_unref (rdisc->routes); g_array_unref (rdisc->dns_servers); g_array_unref (rdisc->dns_domains); + + G_OBJECT_CLASS (nm_rdisc_parent_class)->finalize (object); } static void From 0be367846614de8e9678934e7be7c70871f7c983 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 9 Apr 2015 13:28:09 -0500 Subject: [PATCH 4/8] rdisc: move most RA processing logic into base class Instead of having it all in the Linux implementation, move all the timeout logic and most of the processing logic into the NMRDisc base class so that it can be used by NMFakeRDisc as well. This will help increase testability since now we can test the timeout and expiry logic from the fake plugin too. --- src/Makefile.am | 1 + src/rdisc/nm-fake-rdisc.c | 190 +++++--- src/rdisc/nm-lndp-rdisc.c | 863 ++++++++++------------------------- src/rdisc/nm-rdisc-private.h | 36 ++ src/rdisc/nm-rdisc.c | 413 ++++++++++++++++- src/rdisc/nm-rdisc.h | 2 + 6 files changed, 818 insertions(+), 687 deletions(-) create mode 100644 src/rdisc/nm-rdisc-private.h diff --git a/src/Makefile.am b/src/Makefile.am index 808e59eb33..a24ac6c4f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -229,6 +229,7 @@ nm_sources = \ rdisc/nm-lndp-rdisc.h \ rdisc/nm-rdisc.c \ rdisc/nm-rdisc.h \ + rdisc/nm-rdisc-private.h \ \ ppp-manager/nm-ppp-manager.c \ ppp-manager/nm-ppp-manager.h \ diff --git a/src/rdisc/nm-fake-rdisc.c b/src/rdisc/nm-fake-rdisc.c index e631f28d65..f2efb785fa 100644 --- a/src/rdisc/nm-fake-rdisc.c +++ b/src/rdisc/nm-fake-rdisc.c @@ -24,6 +24,7 @@ #include #include "nm-fake-rdisc.h" +#include "nm-rdisc-private.h" #include "nm-logging.h" @@ -31,12 +32,118 @@ #define warning(...) nm_log_warn (LOGD_IP6, __VA_ARGS__) #define error(...) nm_log_err (LOGD_IP6, __VA_ARGS__) +typedef struct { + guint ra_received_id; +} NMFakeRDiscPrivate; + #define NM_FAKE_RDISC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_FAKE_RDISC, NMFakeRDiscPrivate)) G_DEFINE_TYPE (NMFakeRDisc, nm_fake_rdisc, NM_TYPE_RDISC) /******************************************************************/ +static gboolean +ra_received (gpointer user_data) +{ + NMFakeRDisc *self = NM_FAKE_RDISC (user_data); + NMRDisc *rdisc = NM_RDISC (self); + NMRDiscConfigMap changed = 0; + guint32 now = nm_utils_get_monotonic_timestamp_s (); + NMRDiscGateway gateway; + NMRDiscAddress address; + NMRDiscRoute route; + NMRDiscDNSServer dns_server; + NMRDiscDNSDomain dns_domain; + + NM_FAKE_RDISC_GET_PRIVATE (self)->ra_received_id = 0; + + debug ("(%s): received router advertisement at %u", NM_RDISC (self)->ifname, now); + + rdisc->dhcp_level = NM_RDISC_DHCP_LEVEL_NONE; + + memset (&gateway, 0, sizeof (gateway)); + inet_pton (AF_INET6, "fe80::1", &gateway.address); + if (nm_rdisc_add_gateway (rdisc, &gateway)) + changed |= NM_RDISC_CONFIG_GATEWAYS; + inet_pton (AF_INET6, "fe80::2", &gateway.address); + if (nm_rdisc_add_gateway (rdisc, &gateway)) + changed |= NM_RDISC_CONFIG_GATEWAYS; + inet_pton (AF_INET6, "fe80::3", &gateway.address); + if (nm_rdisc_add_gateway (rdisc, &gateway)) + changed |= NM_RDISC_CONFIG_GATEWAYS; + + memset (&address, 0, sizeof (address)); + inet_pton (AF_INET6, "2001:db8:a:a::1", &address.address); + if (nm_rdisc_add_address (rdisc, &address)) + changed |= NM_RDISC_CONFIG_ADDRESSES; + inet_pton (AF_INET6, "2001:db8:a:a::2", &address.address); + if (nm_rdisc_add_address (rdisc, &address)) + changed |= NM_RDISC_CONFIG_ADDRESSES; + inet_pton (AF_INET6, "2001:db8:f:f::1", &address.address); + if (nm_rdisc_add_address (rdisc, &address)) + changed |= NM_RDISC_CONFIG_ADDRESSES; + + memset (&route, 0, sizeof (route)); + route.plen = 64; + inet_pton (AF_INET6, "2001:db8:a:a::", &route.network); + if (nm_rdisc_add_route (rdisc, &route)) + changed |= NM_RDISC_CONFIG_ROUTES; + inet_pton (AF_INET6, "2001:db8:b:b::", &route.network); + if (nm_rdisc_add_route (rdisc, &route)) + changed |= NM_RDISC_CONFIG_ROUTES; + + memset (&dns_server, 0, sizeof (dns_server)); + inet_pton (AF_INET6, "2001:db8:c:c::1", &dns_server.address); + if (nm_rdisc_add_dns_server (rdisc, &dns_server)) + changed |= NM_RDISC_CONFIG_DNS_SERVERS; + inet_pton (AF_INET6, "2001:db8:c:c::2", &dns_server.address); + if (nm_rdisc_add_dns_server (rdisc, &dns_server)) + changed |= NM_RDISC_CONFIG_DNS_SERVERS; + inet_pton (AF_INET6, "2001:db8:c:c::3", &dns_server.address); + if (nm_rdisc_add_dns_server (rdisc, &dns_server)) + changed |= NM_RDISC_CONFIG_DNS_SERVERS; + inet_pton (AF_INET6, "2001:db8:c:c::4", &dns_server.address); + if (nm_rdisc_add_dns_server (rdisc, &dns_server)) + changed |= NM_RDISC_CONFIG_DNS_SERVERS; + inet_pton (AF_INET6, "2001:db8:c:c::5", &dns_server.address); + if (nm_rdisc_add_dns_server (rdisc, &dns_server)) + changed |= NM_RDISC_CONFIG_DNS_SERVERS; + + memset (&dns_domain, 0, sizeof (dns_domain)); + dns_domain.domain = g_strdup ("example.net"); + if (nm_rdisc_add_dns_domain (rdisc, &dns_domain)) + changed |= NM_RDISC_CONFIG_DNS_DOMAINS; + dns_domain.domain = g_strdup ("example.com"); + if (nm_rdisc_add_dns_domain (rdisc, &dns_domain)) + changed |= NM_RDISC_CONFIG_DNS_DOMAINS; + dns_domain.domain = g_strdup ("example.org"); + if (nm_rdisc_add_dns_domain (rdisc, &dns_domain)) + changed |= NM_RDISC_CONFIG_DNS_DOMAINS; + + nm_rdisc_ra_received (NM_RDISC (self), now, changed); + return G_SOURCE_REMOVE; +} + +static gboolean +send_rs (NMRDisc *rdisc) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (rdisc); + + if (priv->ra_received_id) + g_source_remove (priv->ra_received_id); + priv->ra_received_id = g_timeout_add_seconds (3, ra_received, rdisc); + + return TRUE; +} + +static void +start (NMRDisc *rdisc) +{ + nm_rdisc_solicit (rdisc); +} + +/******************************************************************/ + NMRDisc * nm_fake_rdisc_new (int ifindex, const char *ifname) { @@ -51,86 +158,33 @@ nm_fake_rdisc_new (int ifindex, const char *ifname) return rdisc; } -static void -delayed_start (NMRDisc *rdisc) -{ - int changed = - NM_RDISC_CONFIG_GATEWAYS | NM_RDISC_CONFIG_ADDRESSES | NM_RDISC_CONFIG_ROUTES | - NM_RDISC_CONFIG_DNS_SERVERS | NM_RDISC_CONFIG_DNS_DOMAINS; - debug ("%d", rdisc->dhcp_level); - - g_signal_emit_by_name ( - rdisc, NM_RDISC_CONFIG_CHANGED, changed); -} - -static void -start (NMRDisc *rdisc) -{ - g_idle_add ((GSourceFunc) (delayed_start), rdisc); -} - -/******************************************************************/ - static void nm_fake_rdisc_init (NMFakeRDisc *fake_rdisc) { - NMRDisc *rdisc = NM_RDISC (fake_rdisc); - NMRDiscGateway gateway; - NMRDiscAddress address; - NMRDiscRoute route; - NMRDiscDNSServer dns_server; - NMRDiscDNSDomain dns_domain; +} - rdisc->dhcp_level = NM_RDISC_DHCP_LEVEL_NONE; +static void +dispose (GObject *object) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (object); - memset (&gateway, 0, sizeof (gateway)); - inet_pton (AF_INET6, "fe80::1", &gateway.address); - g_array_append_val (rdisc->gateways, gateway); - inet_pton (AF_INET6, "fe80::2", &gateway.address); - g_array_append_val (rdisc->gateways, gateway); - inet_pton (AF_INET6, "fe80::3", &gateway.address); - g_array_append_val (rdisc->gateways, gateway); + if (priv->ra_received_id) { + g_source_remove (priv->ra_received_id); + priv->ra_received_id = 0; + } - memset (&address, 0, sizeof (address)); - inet_pton (AF_INET6, "2001:db8:a:a::1", &address.address); - g_array_append_val (rdisc->addresses, address); - inet_pton (AF_INET6, "2001:db8:a:a::2", &address.address); - g_array_append_val (rdisc->addresses, address); - inet_pton (AF_INET6, "2001:db8:f:f::1", &address.address); - g_array_append_val (rdisc->addresses, address); - - memset (&route, 0, sizeof (route)); - route.plen = 64; - inet_pton (AF_INET6, "2001:db8:a:a::", &route.network); - g_array_append_val (rdisc->routes, route); - inet_pton (AF_INET6, "2001:db8:b:b::", &route.network); - g_array_append_val (rdisc->routes, route); - - memset (&dns_server, 0, sizeof (dns_server)); - inet_pton (AF_INET6, "2001:db8:c:c::1", &dns_server.address); - g_array_append_val (rdisc->dns_servers, dns_server); - inet_pton (AF_INET6, "2001:db8:c:c::2", &dns_server.address); - g_array_append_val (rdisc->dns_servers, dns_server); - inet_pton (AF_INET6, "2001:db8:c:c::3", &dns_server.address); - g_array_append_val (rdisc->dns_servers, dns_server); - inet_pton (AF_INET6, "2001:db8:c:c::4", &dns_server.address); - g_array_append_val (rdisc->dns_servers, dns_server); - inet_pton (AF_INET6, "2001:db8:c:c::5", &dns_server.address); - g_array_append_val (rdisc->dns_servers, dns_server); - - memset (&dns_domain, 0, sizeof (dns_domain)); - dns_domain.domain = g_strdup ("example.net"); - g_array_append_val (rdisc->dns_domains, dns_domain); - dns_domain.domain = g_strdup ("example.com"); - g_array_append_val (rdisc->dns_domains, dns_domain); - dns_domain.domain = g_strdup ("example.org"); - g_array_append_val (rdisc->dns_domains, dns_domain); + G_OBJECT_CLASS (nm_fake_rdisc_parent_class)->dispose (object); } static void nm_fake_rdisc_class_init (NMFakeRDiscClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); NMRDiscClass *rdisc_class = NM_RDISC_CLASS (klass); + g_type_class_add_private (klass, sizeof (NMFakeRDiscPrivate)); + + object_class->dispose = dispose; rdisc_class->start = start; + rdisc_class->send_rs = send_rs; } diff --git a/src/rdisc/nm-lndp-rdisc.c b/src/rdisc/nm-lndp-rdisc.c index 28fef1fd81..cadbfdfabc 100644 --- a/src/rdisc/nm-lndp-rdisc.c +++ b/src/rdisc/nm-lndp-rdisc.c @@ -27,6 +27,7 @@ #include #include "nm-lndp-rdisc.h" +#include "nm-rdisc-private.h" #include "NetworkManagerUtils.h" #include "nm-logging.h" @@ -39,13 +40,9 @@ typedef struct { struct ndp *ndp; - guint send_rs_id; GIOChannel *event_channel; guint event_id; - guint timeout_id; /* prefix/dns/etc lifetime timeout */ guint ra_timeout_id; /* first RA timeout */ - - int solicitations_left; } NMLNDPRDiscPrivate; #define NM_LNDP_RDISC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LNDP_RDISC, NMLNDPRDiscPrivate)) @@ -54,6 +51,250 @@ G_DEFINE_TYPE (NMLNDPRDisc, nm_lndp_rdisc, NM_TYPE_RDISC) /******************************************************************/ +static gboolean +send_rs (NMRDisc *rdisc) +{ + NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); + struct ndp_msg *msg; + int error; + + error = ndp_msg_new (&msg, NDP_MSG_RS); + g_assert (!error); + ndp_msg_ifindex_set (msg, rdisc->ifindex); + + error = ndp_msg_send (priv->ndp, msg); + ndp_msg_destroy (msg); + if (error) { + error ("(%s): cannot send router solicitation: %d.", rdisc->ifname, error); + return FALSE; + } + + return TRUE; +} + +static NMRDiscPreference +translate_preference (enum ndp_route_preference preference) +{ + switch (preference) { + case NDP_ROUTE_PREF_LOW: + return NM_RDISC_PREFERENCE_LOW; + case NDP_ROUTE_PREF_MEDIUM: + return NM_RDISC_PREFERENCE_MEDIUM; + case NDP_ROUTE_PREF_HIGH: + return NM_RDISC_PREFERENCE_HIGH; + default: + return NM_RDISC_PREFERENCE_INVALID; + } +} + +static int +receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) +{ + NMRDisc *rdisc = (NMRDisc *) user_data; + NMRDiscConfigMap changed = 0; + struct ndp_msgra *msgra = ndp_msgra (msg); + NMRDiscGateway gateway; + 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. + */ + debug ("(%s): received router advertisement at %u", rdisc->ifname, 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; + + if (dhcp_level != rdisc->dhcp_level) { + rdisc->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. + */ + memset (&gateway, 0, sizeof (gateway)); + gateway.address = *ndp_msg_addrto (msg); + gateway.timestamp = now; + gateway.lifetime = ndp_msgra_router_lifetime (msgra); + gateway.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) { + NMRDiscRoute route; + NMRDiscAddress address; + + /* Device route */ + memset (&route, 0, sizeof (route)); + route.plen = ndp_msg_opt_prefix_len (msg, offset); + nm_utils_ip6_address_clear_host_address (&route.network, ndp_msg_opt_prefix (msg, offset), route.plen); + route.timestamp = now; + if (ndp_msg_opt_prefix_flag_on_link (msg, offset)) { + route.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset); + if (nm_rdisc_add_route (rdisc, &route)) + changed |= NM_RDISC_CONFIG_ROUTES; + } + + /* Address */ + if (ndp_msg_opt_prefix_flag_auto_addr_conf (msg, offset)) { + if (route.plen == 64 && rdisc->iid.id) { + memset (&address, 0, sizeof (address)); + address.address = route.network; + address.timestamp = now; + address.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset); + address.preferred = ndp_msg_opt_prefix_preferred_time (msg, offset); + if (address.preferred > address.lifetime) + address.preferred = address.lifetime; + + /* Add the Interface Identifier to the lower 64 bits */ + nm_utils_ipv6_addr_set_interface_identfier (&address.address, rdisc->iid); + + if (nm_rdisc_add_address (rdisc, &address)) + changed |= NM_RDISC_CONFIG_ADDRESSES; + } + } + } + ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_ROUTE) { + NMRDiscRoute route; + + /* Routers through this particular gateway */ + memset (&route, 0, sizeof (route)); + route.gateway = gateway.address; + route.plen = ndp_msg_opt_route_prefix_len (msg, offset); + nm_utils_ip6_address_clear_host_address (&route.network, ndp_msg_opt_route_prefix (msg, offset), route.plen); + route.timestamp = now; + route.lifetime = ndp_msg_opt_route_lifetime (msg, offset); + route.preference = translate_preference (ndp_msg_opt_route_preference (msg, offset)); + 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; + + memset (&dns_server, 0, sizeof (dns_server)); + dns_server.address = *addr; + dns_server.timestamp = now; + dns_server.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; + + memset (&dns_domain, 0, sizeof (dns_domain)); + dns_domain.domain = domain; + dns_domain.timestamp = now; + dns_domain.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 (rdisc->hop_limit != hop_limit) { + rdisc->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) { + rdisc->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. */ + warning ("(%s): MTU too small for IPv6 ignored: %d", rdisc->ifname, mtu); + } + } + + nm_rdisc_ra_received (rdisc, now, changed); + return 0; +} + +static gboolean +event_ready (GIOChannel *source, GIOCondition condition, NMRDisc *rdisc) +{ + NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); + + debug ("(%s): processing libndp events.", rdisc->ifname); + ndp_callall_eventfd_handler (priv->ndp); + return G_SOURCE_CONTINUE; +} + +static void +start (NMRDisc *rdisc) +{ + NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (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, rdisc->ifindex, rdisc); +} + +/******************************************************************/ + static inline gint32 ipv6_sysctl_get (const char *ifname, const char *property, gint32 defval) { @@ -89,611 +330,6 @@ nm_lndp_rdisc_new (int ifindex, const char *ifname) return rdisc; } -static gboolean -add_gateway (NMRDisc *rdisc, const NMRDiscGateway *new) -{ - int i; - - for (i = 0; i < rdisc->gateways->len; i++) { - NMRDiscGateway *item = &g_array_index (rdisc->gateways, NMRDiscGateway, i); - - if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { - if (item->preference != new->preference) { - g_array_remove_index (rdisc->gateways, i--); - continue; - } - memcpy (item, new, sizeof (*new)); - return FALSE; - } - - /* Put before less preferable gateways. */ - if (item->preference < new->preference) - break; - } - - g_array_insert_val (rdisc->gateways, i, *new); - return TRUE; -} - -static gboolean -add_address (NMRDisc *rdisc, const NMRDiscAddress *new) -{ - int i; - - for (i = 0; i < rdisc->addresses->len; i++) { - NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i); - - if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { - gboolean changed = item->timestamp + item->lifetime != new->timestamp + new->lifetime || - item->timestamp + item->preferred != new->timestamp + new->preferred; - - *item = *new; - return changed; - } - } - - /* we create at most max_addresses autoconf addresses. This is different from - * what the kernel does, because it considers *all* addresses (including - * static and other temporary addresses). - **/ - if (rdisc->max_addresses && rdisc->addresses->len >= rdisc->max_addresses) - return FALSE; - - g_array_insert_val (rdisc->addresses, i, *new); - return TRUE; -} - -static gboolean -add_route (NMRDisc *rdisc, const NMRDiscRoute *new) -{ - int i; - - for (i = 0; i < rdisc->routes->len; i++) { - NMRDiscRoute *item = &g_array_index (rdisc->routes, NMRDiscRoute, i); - - if (IN6_ARE_ADDR_EQUAL (&item->network, &new->network) && item->plen == new->plen) { - if (item->preference != new->preference) { - g_array_remove_index (rdisc->routes, i--); - continue; - } - memcpy (item, new, sizeof (*new)); - return FALSE; - } - - /* Put before less preferable routes. */ - if (item->preference < new->preference) - break; - } - - g_array_insert_val (rdisc->routes, i, *new); - return TRUE; -} - -static gboolean -add_dns_server (NMRDisc *rdisc, const NMRDiscDNSServer *new) -{ - int i; - - for (i = 0; i < rdisc->dns_servers->len; i++) { - NMRDiscDNSServer *item = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i); - - if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { - gboolean changed; - - if (new->lifetime == 0) { - g_array_remove_index (rdisc->dns_servers, i); - return TRUE; - } - - changed = (item->timestamp != new->timestamp || - item->lifetime != new->lifetime); - if (changed) { - item->timestamp = new->timestamp; - item->lifetime = new->lifetime; - } - return changed; - } - } - - g_array_insert_val (rdisc->dns_servers, i, *new); - return TRUE; -} - -/* Copies new->domain if 'new' is added to the dns_domains list */ -static gboolean -add_dns_domain (NMRDisc *rdisc, const NMRDiscDNSDomain *new) -{ - NMRDiscDNSDomain *item; - int i; - - for (i = 0; i < rdisc->dns_domains->len; i++) { - item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); - - if (!g_strcmp0 (item->domain, new->domain)) { - gboolean changed; - - if (new->lifetime == 0) { - g_array_remove_index (rdisc->dns_domains, i); - return TRUE; - } - - changed = (item->timestamp != new->timestamp || - item->lifetime != new->lifetime); - if (changed) { - item->timestamp = new->timestamp; - item->lifetime = new->lifetime; - } - return changed; - } - } - - g_array_insert_val (rdisc->dns_domains, i, *new); - item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); - item->domain = g_strdup (new->domain); - return TRUE; -} - -static gboolean -send_rs (NMRDisc *rdisc) -{ - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - struct ndp_msg *msg; - int error; - - error = ndp_msg_new (&msg, NDP_MSG_RS); - g_assert (!error); - ndp_msg_ifindex_set (msg, rdisc->ifindex); - - debug ("(%s): sending router solicitation", rdisc->ifname); - - error = ndp_msg_send (priv->ndp, msg); - if (error) - error ("(%s): cannot send router solicitation: %d.", rdisc->ifname, error); - else - priv->solicitations_left--; - - ndp_msg_destroy (msg); - - if (priv->solicitations_left > 0) { - debug ("(%s): scheduling router solicitation retry in %d seconds.", - rdisc->ifname, rdisc->rtr_solicitation_interval); - priv->send_rs_id = g_timeout_add_seconds (rdisc->rtr_solicitation_interval, - (GSourceFunc) send_rs, rdisc); - } else { - debug ("(%s): did not receive a router advertisement after %d solicitations.", - rdisc->ifname, rdisc->rtr_solicitations); - priv->send_rs_id = 0; - } - - return G_SOURCE_REMOVE; -} - -static void -solicit (NMRDisc *rdisc) -{ - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - - if (!priv->send_rs_id) { - debug ("(%s): scheduling router solicitation.", rdisc->ifname); - priv->send_rs_id = g_idle_add ((GSourceFunc) send_rs, rdisc); - priv->solicitations_left = rdisc->rtr_solicitations; - } -} - -static void -clean_gateways (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) -{ - int i; - - for (i = 0; i < rdisc->gateways->len; i++) { - NMRDiscGateway *item = &g_array_index (rdisc->gateways, NMRDiscGateway, i); - guint64 expiry = (guint64) item->timestamp + item->lifetime; - - if (item->lifetime == G_MAXUINT32) - continue; - - if (now >= expiry) { - g_array_remove_index (rdisc->gateways, i--); - *changed |= NM_RDISC_CONFIG_GATEWAYS; - } else if (*nextevent > expiry) - *nextevent = expiry; - } -} - -static void -clean_addresses (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) -{ - int i; - - for (i = 0; i < rdisc->addresses->len; i++) { - NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i); - guint64 expiry = (guint64) item->timestamp + item->lifetime; - - if (item->lifetime == G_MAXUINT32) - continue; - - if (now >= expiry) { - g_array_remove_index (rdisc->addresses, i--); - *changed |= NM_RDISC_CONFIG_ADDRESSES; - } else if (*nextevent > expiry) - *nextevent = expiry; - } -} - -static void -clean_routes (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) -{ - int i; - - for (i = 0; i < rdisc->routes->len; i++) { - NMRDiscRoute *item = &g_array_index (rdisc->routes, NMRDiscRoute, i); - guint64 expiry = (guint64) item->timestamp + item->lifetime; - - if (item->lifetime == G_MAXUINT32) - continue; - - if (now >= expiry) { - g_array_remove_index (rdisc->routes, i--); - *changed |= NM_RDISC_CONFIG_ROUTES; - } else if (*nextevent > expiry) - *nextevent = expiry; - } -} - -static void -clean_dns_servers (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) -{ - int i; - - for (i = 0; i < rdisc->dns_servers->len; i++) { - NMRDiscDNSServer *item = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i); - guint64 expiry = (guint64) item->timestamp + item->lifetime; - guint64 refresh = (guint64) item->timestamp + item->lifetime / 2; - - if (item->lifetime == G_MAXUINT32) - continue; - - if (now >= expiry) { - g_array_remove_index (rdisc->dns_servers, i--); - *changed |= NM_RDISC_CONFIG_DNS_SERVERS; - } else if (now >= refresh) - solicit (rdisc); - else if (*nextevent > refresh) - *nextevent = refresh; - } -} - -static void -clean_dns_domains (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) -{ - int i; - - for (i = 0; i < rdisc->dns_domains->len; i++) { - NMRDiscDNSDomain *item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); - guint64 expiry = (guint64) item->timestamp + item->lifetime; - guint64 refresh = (guint64) item->timestamp + item->lifetime / 2; - - if (item->lifetime == G_MAXUINT32) - continue; - - if (now >= expiry) { - g_free (item->domain); - g_array_remove_index (rdisc->dns_domains, i--); - *changed |= NM_RDISC_CONFIG_DNS_DOMAINS; - } else if (now >= refresh) - solicit (rdisc); - else if (*nextevent > refresh) - *nextevent = refresh; - } -} - -static gboolean timeout_cb (gpointer user_data); - -static void -check_timestamps (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap changed) -{ - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - /* Use a magic date in the distant future (~68 years) */ - guint32 never = G_MAXINT32; - guint32 nextevent = never; - - if (priv->timeout_id) { - g_source_remove (priv->timeout_id); - priv->timeout_id = 0; - } - - clean_gateways (rdisc, now, &changed, &nextevent); - clean_addresses (rdisc, now, &changed, &nextevent); - clean_routes (rdisc, now, &changed, &nextevent); - clean_dns_servers (rdisc, now, &changed, &nextevent); - clean_dns_domains (rdisc, now, &changed, &nextevent); - - if (changed) - g_signal_emit_by_name (rdisc, NM_RDISC_CONFIG_CHANGED, changed); - - if (nextevent != never) { - g_return_if_fail (nextevent > now); - debug ("(%s): scheduling next now/lifetime check: %u seconds", - rdisc->ifname, nextevent - now); - priv->timeout_id = g_timeout_add_seconds (nextevent - now, timeout_cb, rdisc); - } -} - -static gboolean -timeout_cb (gpointer user_data) -{ - check_timestamps (user_data, nm_utils_get_monotonic_timestamp_s (), 0); - - return TRUE; -} - -static NMRDiscPreference -translate_preference (enum ndp_route_preference preference) -{ - switch (preference) { - case NDP_ROUTE_PREF_LOW: - return NM_RDISC_PREFERENCE_LOW; - case NDP_ROUTE_PREF_MEDIUM: - return NM_RDISC_PREFERENCE_MEDIUM; - case NDP_ROUTE_PREF_HIGH: - return NM_RDISC_PREFERENCE_HIGH; - default: - return NM_RDISC_PREFERENCE_INVALID; - } -} - -static void -clear_rs_timeout (NMLNDPRDisc *rdisc) -{ - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - - if (priv->send_rs_id) { - g_source_remove (priv->send_rs_id); - priv->send_rs_id = 0; - } -} - -static void -clear_ra_timeout (NMLNDPRDisc *rdisc) -{ - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - - if (priv->ra_timeout_id) { - g_source_remove (priv->ra_timeout_id); - priv->ra_timeout_id = 0; - } -} - -static int -receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data) -{ - NMRDisc *rdisc = (NMRDisc *) user_data; - NMRDiscConfigMap changed = 0; - struct ndp_msgra *msgra = ndp_msgra (msg); - NMRDiscGateway gateway; - 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. - */ - debug ("(%s): received router advertisement at %u", rdisc->ifname, now); - - clear_ra_timeout (NM_LNDP_RDISC (rdisc)); - clear_rs_timeout (NM_LNDP_RDISC (rdisc)); - - /* 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; - - if (dhcp_level != rdisc->dhcp_level) { - rdisc->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. - */ - memset (&gateway, 0, sizeof (gateway)); - gateway.address = *ndp_msg_addrto (msg); - gateway.timestamp = now; - gateway.lifetime = ndp_msgra_router_lifetime (msgra); - gateway.preference = translate_preference (ndp_msgra_route_preference (msgra)); - if (add_gateway (rdisc, &gateway)) - changed |= NM_RDISC_CONFIG_GATEWAYS; - - /* Addresses & Routes */ - ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREFIX) { - NMRDiscRoute route; - NMRDiscAddress address; - - /* Device route */ - memset (&route, 0, sizeof (route)); - route.plen = ndp_msg_opt_prefix_len (msg, offset); - nm_utils_ip6_address_clear_host_address (&route.network, ndp_msg_opt_prefix (msg, offset), route.plen); - route.timestamp = now; - if (ndp_msg_opt_prefix_flag_on_link (msg, offset)) { - route.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset); - if (add_route (rdisc, &route)) - changed |= NM_RDISC_CONFIG_ROUTES; - } - - /* Address */ - if (ndp_msg_opt_prefix_flag_auto_addr_conf (msg, offset)) { - if (route.plen == 64 && rdisc->iid.id) { - memset (&address, 0, sizeof (address)); - address.address = route.network; - address.timestamp = now; - address.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset); - address.preferred = ndp_msg_opt_prefix_preferred_time (msg, offset); - if (address.preferred > address.lifetime) - address.preferred = address.lifetime; - - /* Add the Interface Identifier to the lower 64 bits */ - nm_utils_ipv6_addr_set_interface_identfier (&address.address, rdisc->iid); - - if (add_address (rdisc, &address)) - changed |= NM_RDISC_CONFIG_ADDRESSES; - } - } - } - ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_ROUTE) { - NMRDiscRoute route; - - /* Routers through this particular gateway */ - memset (&route, 0, sizeof (route)); - route.gateway = gateway.address; - route.plen = ndp_msg_opt_route_prefix_len (msg, offset); - nm_utils_ip6_address_clear_host_address (&route.network, ndp_msg_opt_route_prefix (msg, offset), route.plen); - route.timestamp = now; - route.lifetime = ndp_msg_opt_route_lifetime (msg, offset); - route.preference = translate_preference (ndp_msg_opt_route_preference (msg, offset)); - if (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; - - memset (&dns_server, 0, sizeof (dns_server)); - dns_server.address = *addr; - dns_server.timestamp = now; - dns_server.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 (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; - - memset (&dns_domain, 0, sizeof (dns_domain)); - dns_domain.domain = domain; - dns_domain.timestamp = now; - dns_domain.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 (add_dns_domain (rdisc, &dns_domain)) - changed |= NM_RDISC_CONFIG_DNS_DOMAINS; - } - } - - hop_limit = ndp_msgra_curhoplimit (msgra); - if (rdisc->hop_limit != hop_limit) { - rdisc->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) { - rdisc->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. */ - warning ("(%s): MTU too small for IPv6 ignored: %d", rdisc->ifname, mtu); - } - } - - check_timestamps (rdisc, now, changed); - - return 0; -} - -static gboolean -event_ready (GIOChannel *source, GIOCondition condition, NMRDisc *rdisc) -{ - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - - debug ("(%s): processing libndp events.", rdisc->ifname); - ndp_callall_eventfd_handler (priv->ndp); - return G_SOURCE_CONTINUE; -} - -static gboolean -rdisc_ra_timeout_cb (gpointer user_data) -{ - NMLNDPRDisc *rdisc = NM_LNDP_RDISC (user_data); - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - - priv->ra_timeout_id = 0; - g_signal_emit_by_name (rdisc, NM_RDISC_RA_TIMEOUT); - return G_SOURCE_REMOVE; -} - -static void -start (NMRDisc *rdisc) -{ - NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - int fd = ndp_get_eventfd (priv->ndp); - guint ra_wait_secs; - - 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); - - clear_ra_timeout (NM_LNDP_RDISC (rdisc)); - ra_wait_secs = CLAMP (rdisc->rtr_solicitations * rdisc->rtr_solicitation_interval, 30, 120); - priv->ra_timeout_id = g_timeout_add_seconds (ra_wait_secs, rdisc_ra_timeout_cb, rdisc); - debug ("(%s): scheduling RA timeout in %d seconds", rdisc->ifname, ra_wait_secs); - - /* 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, rdisc->ifindex, rdisc); - solicit (rdisc); -} - -/******************************************************************/ - static void nm_lndp_rdisc_init (NMLNDPRDisc *lndp_rdisc) { @@ -705,14 +341,6 @@ dispose (GObject *object) NMLNDPRDisc *rdisc = NM_LNDP_RDISC (object); NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc); - clear_rs_timeout (rdisc); - clear_ra_timeout (rdisc); - - if (priv->timeout_id) { - g_source_remove (priv->timeout_id); - priv->timeout_id = 0; - } - if (priv->event_id) { g_source_remove (priv->event_id); priv->event_id = 0; @@ -738,4 +366,5 @@ nm_lndp_rdisc_class_init (NMLNDPRDiscClass *klass) object_class->dispose = dispose; rdisc_class->start = start; + rdisc_class->send_rs = send_rs; } diff --git a/src/rdisc/nm-rdisc-private.h b/src/rdisc/nm-rdisc-private.h new file mode 100644 index 0000000000..f95949def7 --- /dev/null +++ b/src/rdisc/nm-rdisc-private.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-rdisc.h - Perform IPv6 router discovery + * + * 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 2015 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_RDISC_PRIVATE_H__ +#define __NETWORKMANAGER_RDISC_PRIVATE_H__ + +#include "nm-rdisc.h" + +/* Functions only used by rdisc implementations */ + +void nm_rdisc_ra_received (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap changed); + +gboolean nm_rdisc_add_gateway (NMRDisc *rdisc, const NMRDiscGateway *new); +gboolean nm_rdisc_add_address (NMRDisc *rdisc, const NMRDiscAddress *new); +gboolean nm_rdisc_add_route (NMRDisc *rdisc, const NMRDiscRoute *new); +gboolean nm_rdisc_add_dns_server (NMRDisc *rdisc, const NMRDiscDNSServer *new); +gboolean nm_rdisc_add_dns_domain (NMRDisc *rdisc, const NMRDiscDNSDomain *new); + +#endif /* __NETWORKMANAGER_RDISC_PRIVATE_H__ */ diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index dd19eed557..27adb5869e 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -22,14 +22,25 @@ #include #include +#include #include "nm-rdisc.h" +#include "nm-rdisc-private.h" #include "nm-logging.h" #include "nm-utils.h" #define debug(...) nm_log_dbg (LOGD_IP6, __VA_ARGS__) +typedef struct { + int solicitations_left; + guint send_rs_id; + guint ra_timeout_id; /* first RA timeout */ + guint timeout_id; /* prefix/dns/etc lifetime timeout */ +} NMRDiscPrivate; + +#define NM_RDISC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_RDISC, NMRDiscPrivate)) + G_DEFINE_TYPE (NMRDisc, nm_rdisc, G_TYPE_OBJECT) enum { @@ -42,6 +53,174 @@ static guint signals[LAST_SIGNAL] = { 0 }; /******************************************************************/ +gboolean +nm_rdisc_add_gateway (NMRDisc *rdisc, const NMRDiscGateway *new) +{ + int i; + + for (i = 0; i < rdisc->gateways->len; i++) { + NMRDiscGateway *item = &g_array_index (rdisc->gateways, NMRDiscGateway, i); + + if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { + if (item->preference != new->preference) { + g_array_remove_index (rdisc->gateways, i--); + continue; + } + memcpy (item, new, sizeof (*new)); + return FALSE; + } + + /* Put before less preferable gateways. */ + if (item->preference < new->preference) + break; + } + + g_array_insert_val (rdisc->gateways, i, *new); + return TRUE; +} + +gboolean +nm_rdisc_add_address (NMRDisc *rdisc, const NMRDiscAddress *new) +{ + int i; + + for (i = 0; i < rdisc->addresses->len; i++) { + NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i); + + if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { + gboolean changed = item->timestamp + item->lifetime != new->timestamp + new->lifetime || + item->timestamp + item->preferred != new->timestamp + new->preferred; + + *item = *new; + return changed; + } + } + + /* we create at most max_addresses autoconf addresses. This is different from + * what the kernel does, because it considers *all* addresses (including + * static and other temporary addresses). + **/ + if (rdisc->max_addresses && rdisc->addresses->len >= rdisc->max_addresses) + return FALSE; + + g_array_insert_val (rdisc->addresses, i, *new); + return TRUE; +} + +gboolean +nm_rdisc_add_route (NMRDisc *rdisc, const NMRDiscRoute *new) +{ + int i; + + for (i = 0; i < rdisc->routes->len; i++) { + NMRDiscRoute *item = &g_array_index (rdisc->routes, NMRDiscRoute, i); + + if (IN6_ARE_ADDR_EQUAL (&item->network, &new->network) && item->plen == new->plen) { + if (item->preference != new->preference) { + g_array_remove_index (rdisc->routes, i--); + continue; + } + memcpy (item, new, sizeof (*new)); + return FALSE; + } + + /* Put before less preferable routes. */ + if (item->preference < new->preference) + break; + } + + g_array_insert_val (rdisc->routes, i, *new); + return TRUE; +} + +gboolean +nm_rdisc_add_dns_server (NMRDisc *rdisc, const NMRDiscDNSServer *new) +{ + int i; + + for (i = 0; i < rdisc->dns_servers->len; i++) { + NMRDiscDNSServer *item = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i); + + if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { + gboolean changed; + + if (new->lifetime == 0) { + g_array_remove_index (rdisc->dns_servers, i); + return TRUE; + } + + changed = (item->timestamp != new->timestamp || + item->lifetime != new->lifetime); + if (changed) { + item->timestamp = new->timestamp; + item->lifetime = new->lifetime; + } + return changed; + } + } + + g_array_insert_val (rdisc->dns_servers, i, *new); + return TRUE; +} + +/* Copies new->domain if 'new' is added to the dns_domains list */ +gboolean +nm_rdisc_add_dns_domain (NMRDisc *rdisc, const NMRDiscDNSDomain *new) +{ + NMRDiscDNSDomain *item; + int i; + + for (i = 0; i < rdisc->dns_domains->len; i++) { + item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); + + if (!g_strcmp0 (item->domain, new->domain)) { + gboolean changed; + + if (new->lifetime == 0) { + g_array_remove_index (rdisc->dns_domains, i); + return TRUE; + } + + changed = (item->timestamp != new->timestamp || + item->lifetime != new->lifetime); + if (changed) { + item->timestamp = new->timestamp; + item->lifetime = new->lifetime; + } + return changed; + } + } + + g_array_insert_val (rdisc->dns_domains, i, *new); + item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); + item->domain = g_strdup (new->domain); + return TRUE; +} + +/******************************************************************/ + +static void +clear_ra_timeout (NMRDisc *rdisc) +{ + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + + if (priv->ra_timeout_id) { + g_source_remove (priv->ra_timeout_id); + priv->ra_timeout_id = 0; + } +} + +static void +clear_rs_timeout (NMRDisc *rdisc) +{ + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + + if (priv->send_rs_id) { + g_source_remove (priv->send_rs_id); + priv->send_rs_id = 0; + } +} + void nm_rdisc_set_iid (NMRDisc *rdisc, const NMUtilsIPv6IfaceId iid) { @@ -50,17 +229,73 @@ nm_rdisc_set_iid (NMRDisc *rdisc, const NMUtilsIPv6IfaceId iid) rdisc->iid = iid; } +static gboolean +send_rs (NMRDisc *rdisc) +{ + NMRDiscClass *klass = NM_RDISC_GET_CLASS (rdisc); + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + + debug ("(%s): sending router solicitation", rdisc->ifname); + + if (klass->send_rs (rdisc)) + priv->solicitations_left--; + + if (priv->solicitations_left > 0) { + debug ("(%s): scheduling router solicitation retry in %d seconds.", + rdisc->ifname, rdisc->rtr_solicitation_interval); + priv->send_rs_id = g_timeout_add_seconds (rdisc->rtr_solicitation_interval, + (GSourceFunc) send_rs, rdisc); + } else { + debug ("(%s): did not receive a router advertisement after %d solicitations.", + rdisc->ifname, rdisc->rtr_solicitations); + priv->send_rs_id = 0; + } + + return G_SOURCE_REMOVE; +} + +static void +solicit (NMRDisc *rdisc) +{ + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + + if (!priv->send_rs_id) { + debug ("(%s): scheduling router solicitation.", rdisc->ifname); + priv->send_rs_id = g_idle_add ((GSourceFunc) send_rs, rdisc); + priv->solicitations_left = rdisc->rtr_solicitations; + } +} + +static gboolean +rdisc_ra_timeout_cb (gpointer user_data) +{ + NMRDisc *rdisc = NM_RDISC (user_data); + + NM_RDISC_GET_PRIVATE (rdisc)->ra_timeout_id = 0; + g_signal_emit_by_name (rdisc, NM_RDISC_RA_TIMEOUT); + return G_SOURCE_REMOVE; +} + void nm_rdisc_start (NMRDisc *rdisc) { + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); NMRDiscClass *klass = NM_RDISC_GET_CLASS (rdisc); + guint ra_wait_secs; g_assert (klass->start); debug ("(%s): starting router discovery: %d", rdisc->ifname, rdisc->ifindex); + clear_ra_timeout (rdisc); + ra_wait_secs = CLAMP (rdisc->rtr_solicitations * rdisc->rtr_solicitation_interval, 30, 120); + priv->ra_timeout_id = g_timeout_add_seconds (ra_wait_secs, rdisc_ra_timeout_cb, rdisc); + debug ("(%s): scheduling RA timeout in %d seconds", rdisc->ifname, ra_wait_secs); + if (klass->start) klass->start (rdisc); + + solicit (rdisc); } #define CONFIG_MAP_MAX_STR 7 @@ -145,6 +380,161 @@ config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed) } } +static void +clean_gateways (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) +{ + int i; + + for (i = 0; i < rdisc->gateways->len; i++) { + NMRDiscGateway *item = &g_array_index (rdisc->gateways, NMRDiscGateway, i); + guint64 expiry = (guint64) item->timestamp + item->lifetime; + + if (item->lifetime == G_MAXUINT32) + continue; + + if (now >= expiry) { + g_array_remove_index (rdisc->gateways, i--); + *changed |= NM_RDISC_CONFIG_GATEWAYS; + } else if (*nextevent > expiry) + *nextevent = expiry; + } +} + +static void +clean_addresses (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) +{ + int i; + + for (i = 0; i < rdisc->addresses->len; i++) { + NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i); + guint64 expiry = (guint64) item->timestamp + item->lifetime; + + if (item->lifetime == G_MAXUINT32) + continue; + + if (now >= expiry) { + g_array_remove_index (rdisc->addresses, i--); + *changed |= NM_RDISC_CONFIG_ADDRESSES; + } else if (*nextevent > expiry) + *nextevent = expiry; + } +} + +static void +clean_routes (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) +{ + int i; + + for (i = 0; i < rdisc->routes->len; i++) { + NMRDiscRoute *item = &g_array_index (rdisc->routes, NMRDiscRoute, i); + guint64 expiry = (guint64) item->timestamp + item->lifetime; + + if (item->lifetime == G_MAXUINT32) + continue; + + if (now >= expiry) { + g_array_remove_index (rdisc->routes, i--); + *changed |= NM_RDISC_CONFIG_ROUTES; + } else if (*nextevent > expiry) + *nextevent = expiry; + } +} + +static void +clean_dns_servers (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) +{ + int i; + + for (i = 0; i < rdisc->dns_servers->len; i++) { + NMRDiscDNSServer *item = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i); + guint64 expiry = (guint64) item->timestamp + item->lifetime; + guint64 refresh = (guint64) item->timestamp + item->lifetime / 2; + + if (item->lifetime == G_MAXUINT32) + continue; + + if (now >= expiry) { + g_array_remove_index (rdisc->dns_servers, i--); + *changed |= NM_RDISC_CONFIG_DNS_SERVERS; + } else if (now >= refresh) + solicit (rdisc); + else if (*nextevent > refresh) + *nextevent = refresh; + } +} + +static void +clean_dns_domains (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent) +{ + int i; + + for (i = 0; i < rdisc->dns_domains->len; i++) { + NMRDiscDNSDomain *item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); + guint64 expiry = (guint64) item->timestamp + item->lifetime; + guint64 refresh = (guint64) item->timestamp + item->lifetime / 2; + + if (item->lifetime == G_MAXUINT32) + continue; + + if (now >= expiry) { + g_free (item->domain); + g_array_remove_index (rdisc->dns_domains, i--); + *changed |= NM_RDISC_CONFIG_DNS_DOMAINS; + } else if (now >= refresh) + solicit (rdisc); + else if (*nextevent > refresh) + *nextevent = refresh; + } +} + +static gboolean timeout_cb (gpointer user_data); + +static void +check_timestamps (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap changed) +{ + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + /* Use a magic date in the distant future (~68 years) */ + guint32 never = G_MAXINT32; + guint32 nextevent = never; + + if (priv->timeout_id) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + clean_gateways (rdisc, now, &changed, &nextevent); + clean_addresses (rdisc, now, &changed, &nextevent); + clean_routes (rdisc, now, &changed, &nextevent); + clean_dns_servers (rdisc, now, &changed, &nextevent); + clean_dns_domains (rdisc, now, &changed, &nextevent); + + if (changed) + g_signal_emit_by_name (rdisc, NM_RDISC_CONFIG_CHANGED, changed); + + if (nextevent != never) { + g_return_if_fail (nextevent > now); + debug ("(%s): scheduling next now/lifetime check: %u seconds", + rdisc->ifname, nextevent - now); + priv->timeout_id = g_timeout_add_seconds (nextevent - now, timeout_cb, rdisc); + } +} + +static gboolean +timeout_cb (gpointer user_data) +{ + NM_RDISC_GET_PRIVATE (user_data)->timeout_id = 0; + check_timestamps (NM_RDISC (user_data), nm_utils_get_monotonic_timestamp_s (), 0); + return G_SOURCE_REMOVE; +} + +void +nm_rdisc_ra_received (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap changed) +{ + clear_ra_timeout (rdisc); + clear_rs_timeout (rdisc); + check_timestamps (rdisc, now, changed); +} + /******************************************************************/ static void @@ -159,7 +549,24 @@ nm_rdisc_init (NMRDisc *rdisc) } static void -nm_rdisc_finalize (GObject *object) +dispose (GObject *object) +{ + NMRDisc *rdisc = NM_RDISC (object); + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + + clear_ra_timeout (rdisc); + clear_rs_timeout (rdisc); + + if (priv->timeout_id) { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + + G_OBJECT_CLASS (nm_rdisc_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) { NMRDisc *rdisc = NM_RDISC (object); @@ -178,8 +585,10 @@ nm_rdisc_class_init (NMRDiscClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->finalize = nm_rdisc_finalize; + g_type_class_add_private (klass, sizeof (NMRDiscPrivate)); + object_class->dispose = dispose; + object_class->finalize = finalize; klass->config_changed = config_changed; signals[CONFIG_CHANGED] = g_signal_new ( diff --git a/src/rdisc/nm-rdisc.h b/src/rdisc/nm-rdisc.h index 2d83f0f020..a79e6c9690 100644 --- a/src/rdisc/nm-rdisc.h +++ b/src/rdisc/nm-rdisc.h @@ -133,7 +133,9 @@ typedef struct { GObjectClass parent; void (*start) (NMRDisc *rdisc); + gboolean (*send_rs) (NMRDisc *rdisc); void (*config_changed) (NMRDisc *rdisc, NMRDiscConfigMap changed); + void (*ra_process) (NMRDisc *rdisc); void (*ra_timeout) (NMRDisc *rdisc); } NMRDiscClass; From 272943db4ba28a1681282332861d273e4bcc6e13 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 9 Apr 2015 15:38:20 -0500 Subject: [PATCH 5/8] rdisc: fix leak of DNS domains --- src/rdisc/nm-rdisc.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index 27adb5869e..41a11615d5 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -477,7 +477,6 @@ clean_dns_domains (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint continue; if (now >= expiry) { - g_free (item->domain); g_array_remove_index (rdisc->dns_domains, i--); *changed |= NM_RDISC_CONFIG_DNS_DOMAINS; } else if (now >= refresh) @@ -537,6 +536,12 @@ nm_rdisc_ra_received (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap changed) /******************************************************************/ +static void +dns_domain_free (gpointer data) +{ + g_free (((NMRDiscDNSDomain *)(data))->domain); +} + static void nm_rdisc_init (NMRDisc *rdisc) { @@ -545,6 +550,7 @@ nm_rdisc_init (NMRDisc *rdisc) rdisc->routes = g_array_new (FALSE, FALSE, sizeof (NMRDiscRoute)); rdisc->dns_servers = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSServer)); rdisc->dns_domains = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSDomain)); + g_array_set_clear_func (rdisc->dns_domains, dns_domain_free); rdisc->hop_limit = 64; } From 39fd8f7683d9bbda09a3b69ef7a3e7927b96b851 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 9 Apr 2015 15:39:00 -0500 Subject: [PATCH 6/8] rdisc: split fake & linux test code; add testcases --- src/rdisc/nm-fake-rdisc.c | 379 ++++++++++++++---- src/rdisc/nm-fake-rdisc.h | 45 ++- src/rdisc/tests/Makefile.am | 17 +- src/rdisc/tests/test-rdisc-fake.c | 283 +++++++++++++ .../tests/{rdisc.c => test-rdisc-linux.c} | 42 +- 5 files changed, 648 insertions(+), 118 deletions(-) create mode 100644 src/rdisc/tests/test-rdisc-fake.c rename src/rdisc/tests/{rdisc.c => test-rdisc-linux.c} (69%) diff --git a/src/rdisc/nm-fake-rdisc.c b/src/rdisc/nm-fake-rdisc.c index f2efb785fa..a281b72252 100644 --- a/src/rdisc/nm-fake-rdisc.c +++ b/src/rdisc/nm-fake-rdisc.c @@ -33,113 +33,304 @@ #define error(...) nm_log_err (LOGD_IP6, __VA_ARGS__) typedef struct { - guint ra_received_id; + guint id; + guint when; + + NMRDiscDHCPLevel dhcp_level; + GArray *gateways; + GArray *addresses; + GArray *routes; + GArray *dns_servers; + GArray *dns_domains; + int hop_limit; + guint32 mtu; +} FakeRa; + +typedef struct { + guint receive_ra_id; + GSList *ras; } NMFakeRDiscPrivate; #define NM_FAKE_RDISC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_FAKE_RDISC, NMFakeRDiscPrivate)) G_DEFINE_TYPE (NMFakeRDisc, nm_fake_rdisc, NM_TYPE_RDISC) +enum { + RS_SENT, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + /******************************************************************/ -static gboolean -ra_received (gpointer user_data) +static void +fake_ra_free (gpointer data) { - NMFakeRDisc *self = NM_FAKE_RDISC (user_data); - NMRDisc *rdisc = NM_RDISC (self); - NMRDiscConfigMap changed = 0; - guint32 now = nm_utils_get_monotonic_timestamp_s (); - NMRDiscGateway gateway; - NMRDiscAddress address; - NMRDiscRoute route; - NMRDiscDNSServer dns_server; - NMRDiscDNSDomain dns_domain; + FakeRa *ra = data; - NM_FAKE_RDISC_GET_PRIVATE (self)->ra_received_id = 0; - - debug ("(%s): received router advertisement at %u", NM_RDISC (self)->ifname, now); - - rdisc->dhcp_level = NM_RDISC_DHCP_LEVEL_NONE; - - memset (&gateway, 0, sizeof (gateway)); - inet_pton (AF_INET6, "fe80::1", &gateway.address); - if (nm_rdisc_add_gateway (rdisc, &gateway)) - changed |= NM_RDISC_CONFIG_GATEWAYS; - inet_pton (AF_INET6, "fe80::2", &gateway.address); - if (nm_rdisc_add_gateway (rdisc, &gateway)) - changed |= NM_RDISC_CONFIG_GATEWAYS; - inet_pton (AF_INET6, "fe80::3", &gateway.address); - if (nm_rdisc_add_gateway (rdisc, &gateway)) - changed |= NM_RDISC_CONFIG_GATEWAYS; - - memset (&address, 0, sizeof (address)); - inet_pton (AF_INET6, "2001:db8:a:a::1", &address.address); - if (nm_rdisc_add_address (rdisc, &address)) - changed |= NM_RDISC_CONFIG_ADDRESSES; - inet_pton (AF_INET6, "2001:db8:a:a::2", &address.address); - if (nm_rdisc_add_address (rdisc, &address)) - changed |= NM_RDISC_CONFIG_ADDRESSES; - inet_pton (AF_INET6, "2001:db8:f:f::1", &address.address); - if (nm_rdisc_add_address (rdisc, &address)) - changed |= NM_RDISC_CONFIG_ADDRESSES; - - memset (&route, 0, sizeof (route)); - route.plen = 64; - inet_pton (AF_INET6, "2001:db8:a:a::", &route.network); - if (nm_rdisc_add_route (rdisc, &route)) - changed |= NM_RDISC_CONFIG_ROUTES; - inet_pton (AF_INET6, "2001:db8:b:b::", &route.network); - if (nm_rdisc_add_route (rdisc, &route)) - changed |= NM_RDISC_CONFIG_ROUTES; - - memset (&dns_server, 0, sizeof (dns_server)); - inet_pton (AF_INET6, "2001:db8:c:c::1", &dns_server.address); - if (nm_rdisc_add_dns_server (rdisc, &dns_server)) - changed |= NM_RDISC_CONFIG_DNS_SERVERS; - inet_pton (AF_INET6, "2001:db8:c:c::2", &dns_server.address); - if (nm_rdisc_add_dns_server (rdisc, &dns_server)) - changed |= NM_RDISC_CONFIG_DNS_SERVERS; - inet_pton (AF_INET6, "2001:db8:c:c::3", &dns_server.address); - if (nm_rdisc_add_dns_server (rdisc, &dns_server)) - changed |= NM_RDISC_CONFIG_DNS_SERVERS; - inet_pton (AF_INET6, "2001:db8:c:c::4", &dns_server.address); - if (nm_rdisc_add_dns_server (rdisc, &dns_server)) - changed |= NM_RDISC_CONFIG_DNS_SERVERS; - inet_pton (AF_INET6, "2001:db8:c:c::5", &dns_server.address); - if (nm_rdisc_add_dns_server (rdisc, &dns_server)) - changed |= NM_RDISC_CONFIG_DNS_SERVERS; - - memset (&dns_domain, 0, sizeof (dns_domain)); - dns_domain.domain = g_strdup ("example.net"); - if (nm_rdisc_add_dns_domain (rdisc, &dns_domain)) - changed |= NM_RDISC_CONFIG_DNS_DOMAINS; - dns_domain.domain = g_strdup ("example.com"); - if (nm_rdisc_add_dns_domain (rdisc, &dns_domain)) - changed |= NM_RDISC_CONFIG_DNS_DOMAINS; - dns_domain.domain = g_strdup ("example.org"); - if (nm_rdisc_add_dns_domain (rdisc, &dns_domain)) - changed |= NM_RDISC_CONFIG_DNS_DOMAINS; - - nm_rdisc_ra_received (NM_RDISC (self), now, changed); - return G_SOURCE_REMOVE; + g_array_free (ra->gateways, TRUE); + g_array_free (ra->addresses, TRUE); + g_array_free (ra->routes, TRUE); + g_array_free (ra->dns_servers, TRUE); + g_array_free (ra->dns_domains, TRUE); + g_free (ra); } +static void +ra_dns_domain_free (gpointer data) +{ + g_free (((NMRDiscDNSDomain *)(data))->domain); +} + +static FakeRa * +find_ra (GSList *ras, guint id) +{ + GSList *iter; + + for (iter = ras; iter; iter = iter->next) { + if (((FakeRa *) iter->data)->id == id) + return iter->data; + } + return NULL; +} + +guint +nm_fake_rdisc_add_ra (NMFakeRDisc *self, + guint seconds_after_previous, + NMRDiscDHCPLevel dhcp_level, + int hop_limit, + guint32 mtu) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (self); + static guint counter = 1; + FakeRa *ra; + + ra = g_malloc0 (sizeof (*ra)); + ra->id = counter++; + ra->when = seconds_after_previous; + ra->dhcp_level = dhcp_level; + ra->hop_limit = hop_limit; + ra->mtu = mtu; + ra->gateways = g_array_new (FALSE, FALSE, sizeof (NMRDiscGateway)); + ra->addresses = g_array_new (FALSE, FALSE, sizeof (NMRDiscAddress)); + ra->routes = g_array_new (FALSE, FALSE, sizeof (NMRDiscRoute)); + ra->dns_servers = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSServer)); + ra->dns_domains = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSDomain)); + g_array_set_clear_func (ra->dns_domains, ra_dns_domain_free); + + priv->ras = g_slist_append (priv->ras, ra); + return ra->id; +} + +void +nm_fake_rdisc_add_gateway (NMFakeRDisc *self, + guint ra_id, + const char *addr, + guint32 timestamp, + guint32 lifetime, + NMRDiscPreference preference) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (self); + FakeRa *ra = find_ra (priv->ras, ra_id); + NMRDiscGateway *gw; + + g_assert (ra); + g_array_set_size (ra->gateways, ra->gateways->len + 1); + gw = &g_array_index (ra->gateways, NMRDiscGateway, ra->gateways->len - 1); + g_assert (inet_pton (AF_INET6, addr, &gw->address) == 1); + gw->timestamp = timestamp; + gw->lifetime = lifetime; + gw->preference = preference; +} + +void +nm_fake_rdisc_add_address (NMFakeRDisc *self, + guint ra_id, + const char *addr, + guint32 timestamp, + guint32 lifetime, + guint32 preferred) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (self); + FakeRa *ra = find_ra (priv->ras, ra_id); + NMRDiscAddress *a; + + g_assert (ra); + g_array_set_size (ra->addresses, ra->addresses->len + 1); + a = &g_array_index (ra->addresses, NMRDiscAddress, ra->addresses->len - 1); + g_assert (inet_pton (AF_INET6, addr, &a->address) == 1); + a->timestamp = timestamp; + a->lifetime = lifetime; + a->preferred = preferred; +} + +void +nm_fake_rdisc_add_route (NMFakeRDisc *self, + guint ra_id, + const char *network, + guint plen, + const char *gateway, + guint32 timestamp, + guint32 lifetime, + NMRDiscPreference preference) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (self); + FakeRa *ra = find_ra (priv->ras, ra_id); + NMRDiscRoute *route; + + g_assert (ra); + g_array_set_size (ra->routes, ra->routes->len + 1); + route = &g_array_index (ra->routes, NMRDiscRoute, ra->routes->len - 1); + g_assert (inet_pton (AF_INET6, network, &route->network) == 1); + g_assert (inet_pton (AF_INET6, gateway, &route->gateway) == 1); + route->plen = plen; + route->timestamp = timestamp; + route->lifetime = lifetime; + route->preference = preference; +} + +void +nm_fake_rdisc_add_dns_server (NMFakeRDisc *self, + guint ra_id, + const char *address, + guint32 timestamp, + guint32 lifetime) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (self); + FakeRa *ra = find_ra (priv->ras, ra_id); + NMRDiscDNSServer *dns; + + g_assert (ra); + g_array_set_size (ra->dns_servers, ra->dns_servers->len + 1); + dns = &g_array_index (ra->dns_servers, NMRDiscDNSServer, ra->dns_servers->len - 1); + g_assert (inet_pton (AF_INET6, address, &dns->address) == 1); + dns->timestamp = timestamp; + dns->lifetime = lifetime; +} + +void +nm_fake_rdisc_add_dns_domain (NMFakeRDisc *self, + guint ra_id, + const char *domain, + guint32 timestamp, + guint32 lifetime) +{ + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (self); + FakeRa *ra = find_ra (priv->ras, ra_id); + NMRDiscDNSDomain *dns; + + g_assert (ra); + g_array_set_size (ra->dns_domains, ra->dns_domains->len + 1); + dns = &g_array_index (ra->dns_domains, NMRDiscDNSDomain, ra->dns_domains->len - 1); + dns->domain = g_strdup (domain); + dns->timestamp = timestamp; + dns->lifetime = lifetime; +} + +gboolean +nm_fake_rdisc_done (NMFakeRDisc *self) +{ + return !NM_FAKE_RDISC_GET_PRIVATE (self)->ras; +} + +/******************************************************************/ + static gboolean send_rs (NMRDisc *rdisc) { - NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (rdisc); - - if (priv->ra_received_id) - g_source_remove (priv->ra_received_id); - priv->ra_received_id = g_timeout_add_seconds (3, ra_received, rdisc); - + g_signal_emit (rdisc, signals[RS_SENT], 0); return TRUE; } +static gboolean +receive_ra (gpointer user_data) +{ + NMFakeRDisc *self = user_data; + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (self); + NMRDisc *rdisc = NM_RDISC (self); + FakeRa *ra = priv->ras->data; + NMRDiscConfigMap changed = 0; + guint32 now = nm_utils_get_monotonic_timestamp_s (); + guint i; + + priv->receive_ra_id = 0; + + if (rdisc->dhcp_level != ra->dhcp_level) { + rdisc->dhcp_level = ra->dhcp_level; + changed |= NM_RDISC_CONFIG_DHCP_LEVEL; + } + + for (i = 0; i < ra->gateways->len; i++) { + NMRDiscGateway *item = &g_array_index (ra->gateways, NMRDiscGateway, i); + + if (nm_rdisc_add_gateway (rdisc, item)) + changed |= NM_RDISC_CONFIG_GATEWAYS; + } + + for (i = 0; i < ra->addresses->len; i++) { + NMRDiscAddress *item = &g_array_index (ra->addresses, NMRDiscAddress, i); + + if (nm_rdisc_add_address (rdisc, item)) + changed |= NM_RDISC_CONFIG_ADDRESSES; + } + + for (i = 0; i < ra->routes->len; i++) { + NMRDiscRoute *item = &g_array_index (ra->routes, NMRDiscRoute, i); + + if (nm_rdisc_add_route (rdisc, item)) + changed |= NM_RDISC_CONFIG_ROUTES; + } + + for (i = 0; i < ra->dns_servers->len; i++) { + NMRDiscDNSServer *item = &g_array_index (ra->dns_servers, NMRDiscDNSServer, i); + + if (nm_rdisc_add_dns_server (rdisc, item)) + changed |= NM_RDISC_CONFIG_DNS_SERVERS; + } + + for (i = 0; i < ra->dns_domains->len; i++) { + NMRDiscDNSDomain *item = &g_array_index (ra->dns_domains, NMRDiscDNSDomain, i); + + if (nm_rdisc_add_dns_domain (rdisc, item)) + changed |= NM_RDISC_CONFIG_DNS_DOMAINS; + } + + if (rdisc->mtu != ra->mtu) { + rdisc->mtu = ra->mtu; + changed |= NM_RDISC_CONFIG_MTU; + } + + if (rdisc->hop_limit != ra->hop_limit) { + rdisc->hop_limit = ra->hop_limit; + changed |= NM_RDISC_CONFIG_HOP_LIMIT; + } + + priv->ras = g_slist_remove (priv->ras, priv->ras->data); + fake_ra_free (ra); + + nm_rdisc_ra_received (NM_RDISC (self), now, changed); + + /* Schedule next RA */ + if (priv->ras) { + ra = priv->ras->data; + priv->receive_ra_id = g_timeout_add_seconds (ra->when, receive_ra, self); + } + + return G_SOURCE_REMOVE; +} + static void start (NMRDisc *rdisc) { - nm_rdisc_solicit (rdisc); + NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (rdisc); + FakeRa *ra; + + /* Queue up the first fake RA */ + g_assert (priv->ras); + ra = NM_FAKE_RDISC_GET_PRIVATE (rdisc)->ras->data; + + g_assert (!priv->receive_ra_id); + priv->receive_ra_id = g_timeout_add_seconds (ra->when, receive_ra, rdisc); } /******************************************************************/ @@ -168,11 +359,14 @@ dispose (GObject *object) { NMFakeRDiscPrivate *priv = NM_FAKE_RDISC_GET_PRIVATE (object); - if (priv->ra_received_id) { - g_source_remove (priv->ra_received_id); - priv->ra_received_id = 0; + if (priv->receive_ra_id) { + g_source_remove (priv->receive_ra_id); + priv->receive_ra_id = 0; } + g_slist_free_full (priv->ras, fake_ra_free); + priv->ras = NULL; + G_OBJECT_CLASS (nm_fake_rdisc_parent_class)->dispose (object); } @@ -187,4 +381,11 @@ nm_fake_rdisc_class_init (NMFakeRDiscClass *klass) object_class->dispose = dispose; rdisc_class->start = start; rdisc_class->send_rs = send_rs; + + signals[RS_SENT] = g_signal_new ( + NM_FAKE_RDISC_RS_SENT, + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); } diff --git a/src/rdisc/nm-fake-rdisc.h b/src/rdisc/nm-fake-rdisc.h index b4eff7ecf9..5a8daefb21 100644 --- a/src/rdisc/nm-fake-rdisc.h +++ b/src/rdisc/nm-fake-rdisc.h @@ -30,7 +30,7 @@ #define NM_IS_FAKE_RDISC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_FAKE_RDISC)) #define NM_FAKE_RDISC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_FAKE_RDISC, NMFakeRDiscClass)) -/******************************************************************/ +#define NM_FAKE_RDISC_RS_SENT "rs-sent" typedef struct { NMRDisc parent; @@ -46,4 +46,47 @@ GType nm_fake_rdisc_get_type (void); NMRDisc *nm_fake_rdisc_new (int ifindex, const char *ifname); +guint nm_fake_rdisc_add_ra (NMFakeRDisc *self, + guint seconds, + NMRDiscDHCPLevel dhcp_level, + int hop_limit, + guint32 mtu); + +void nm_fake_rdisc_add_gateway (NMFakeRDisc *self, + guint ra_id, + const char *addr, + guint32 timestamp, + guint32 lifetime, + NMRDiscPreference preference); + +void nm_fake_rdisc_add_address (NMFakeRDisc *self, + guint ra_id, + const char *addr, + guint32 timestamp, + guint32 lifetime, + guint32 preferred); + +void nm_fake_rdisc_add_route (NMFakeRDisc *self, + guint ra_id, + const char *network, + guint plen, + const char *gateway, + guint32 timestamp, + guint32 lifetime, + NMRDiscPreference preference); + +void nm_fake_rdisc_add_dns_server (NMFakeRDisc *self, + guint ra_id, + const char *address, + guint32 timestamp, + guint32 lifetime); + +void nm_fake_rdisc_add_dns_domain (NMFakeRDisc *self, + guint ra_id, + const char *domain, + guint32 timestamp, + guint32 lifetime); + +gboolean nm_fake_rdisc_done (NMFakeRDisc *self); + #endif /* __NETWORKMANAGER_FAKE_RDISC_H__ */ diff --git a/src/rdisc/tests/Makefile.am b/src/rdisc/tests/Makefile.am index e6fe136968..5d6f8b5bcb 100644 --- a/src/rdisc/tests/Makefile.am +++ b/src/rdisc/tests/Makefile.am @@ -20,11 +20,18 @@ AM_LDFLAGS = \ @GNOME_CODE_COVERAGE_RULES@ -noinst_PROGRAMS = \ - rdisc +noinst_PROGRAMS = test-rdisc-linux test-rdisc-fake -rdisc_SOURCES = \ - rdisc.c -rdisc_LDADD = \ +test_rdisc_linux_SOURCES = \ + test-rdisc-linux.c +test_rdisc_linux_LDADD = \ $(top_builddir)/src/libNetworkManager.la +test_rdisc_fake_SOURCES = \ + test-rdisc-fake.c +test_rdisc_fake_LDADD = \ + $(top_builddir)/src/libNetworkManager.la + +@VALGRIND_RULES@ +TESTS = test-rdisc-fake + diff --git a/src/rdisc/tests/test-rdisc-fake.c b/src/rdisc/tests/test-rdisc-fake.c new file mode 100644 index 0000000000..c8db7f1a6c --- /dev/null +++ b/src/rdisc/tests/test-rdisc-fake.c @@ -0,0 +1,283 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* rdisc.c - test program + * + * 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) 2015 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include + +#include "nm-rdisc.h" +#include "nm-fake-rdisc.h" +#include "nm-logging.h" + +#include "nm-fake-platform.h" + +#include "nm-test-utils.h" + +static NMFakeRDisc * +rdisc_new (void) +{ + NMRDisc *rdisc; + const int ifindex = 1; + const char *ifname = nm_platform_link_get_name (ifindex); + + rdisc = nm_fake_rdisc_new (ifindex, ifname); + g_assert (rdisc); + return NM_FAKE_RDISC (rdisc); +} + +static void +match_gateway (GArray *array, guint idx, const char *addr, guint32 ts, guint32 lt, NMRDiscPreference pref) +{ + NMRDiscGateway *gw = &g_array_index (array, NMRDiscGateway, idx); + char buf[INET6_ADDRSTRLEN]; + + g_assert_cmpstr (inet_ntop (AF_INET6, &gw->address, buf, sizeof (buf)), ==, addr); + g_assert_cmpint (gw->timestamp, ==, ts); + g_assert_cmpint (gw->lifetime, ==, lt); + g_assert_cmpint (gw->preference, ==, pref); +} + +static void +match_address (GArray *array, guint idx, const char *addr, guint32 ts, guint32 lt, guint32 preferred) +{ + NMRDiscAddress *a = &g_array_index (array, NMRDiscAddress, idx); + char buf[INET6_ADDRSTRLEN]; + + g_assert_cmpstr (inet_ntop (AF_INET6, &a->address, buf, sizeof (buf)), ==, addr); + g_assert_cmpint (a->timestamp, ==, ts); + g_assert_cmpint (a->lifetime, ==, lt); + g_assert_cmpint (a->preferred, ==, preferred); +} + +static void +match_route (GArray *array, guint idx, const char *nw, int plen, const char *gw, guint32 ts, guint32 lt, NMRDiscPreference pref) +{ + NMRDiscRoute *route = &g_array_index (array, NMRDiscRoute, idx); + char buf[INET6_ADDRSTRLEN]; + + g_assert_cmpstr (inet_ntop (AF_INET6, &route->network, buf, sizeof (buf)), ==, nw); + g_assert_cmpint (route->plen, ==, plen); + g_assert_cmpstr (inet_ntop (AF_INET6, &route->gateway, buf, sizeof (buf)), ==, gw); + g_assert_cmpint (route->timestamp, ==, ts); + g_assert_cmpint (route->lifetime, ==, lt); + g_assert_cmpint (route->preference, ==, pref); +} + +static void +match_dns_server (GArray *array, guint idx, const char *addr, guint32 ts, guint32 lt) +{ + NMRDiscDNSServer *dns = &g_array_index (array, NMRDiscDNSServer, idx); + char buf[INET6_ADDRSTRLEN]; + + g_assert_cmpstr (inet_ntop (AF_INET6, &dns->address, buf, sizeof (buf)), ==, addr); + g_assert_cmpint (dns->timestamp, ==, ts); + g_assert_cmpint (dns->lifetime, ==, lt); +} + +static void +match_dns_domain (GArray *array, guint idx, const char *domain, guint32 ts, guint32 lt) +{ + NMRDiscDNSDomain *dns = &g_array_index (array, NMRDiscDNSDomain, idx); + + g_assert_cmpstr (dns->domain, ==, domain); + g_assert_cmpint (dns->timestamp, ==, ts); + g_assert_cmpint (dns->lifetime, ==, lt); +} + +typedef struct { + GMainLoop *loop; + guint counter; + guint rs_counter; + guint32 timestamp1; +} TestData; + +static void +test_simple_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, TestData *data) +{ + g_assert_cmpint (changed, ==, NM_RDISC_CONFIG_DHCP_LEVEL | + NM_RDISC_CONFIG_GATEWAYS | + NM_RDISC_CONFIG_ADDRESSES | + NM_RDISC_CONFIG_ROUTES | + NM_RDISC_CONFIG_DNS_SERVERS | + NM_RDISC_CONFIG_DNS_DOMAINS | + NM_RDISC_CONFIG_HOP_LIMIT | + NM_RDISC_CONFIG_MTU); + g_assert_cmpint (rdisc->dhcp_level, ==, NM_RDISC_DHCP_LEVEL_OTHERCONF); + match_gateway (rdisc->gateways, 0, "fe80::1", data->timestamp1, 10, NM_RDISC_PREFERENCE_MEDIUM); + match_address (rdisc->addresses, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10); + match_route (rdisc->routes, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10); + match_dns_server (rdisc->dns_servers, 0, "2001:db8:c:c::1", data->timestamp1, 10); + match_dns_domain (rdisc->dns_domains, 0, "foobar.com", data->timestamp1, 10); + + g_assert (nm_fake_rdisc_done (NM_FAKE_RDISC (rdisc))); + data->counter++; + g_main_loop_quit (data->loop); +} + +static void +test_simple (void) +{ + NMFakeRDisc *rdisc = rdisc_new (); + guint32 now = nm_utils_get_monotonic_timestamp_s (); + TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now }; + guint id; + + id = nm_fake_rdisc_add_ra (rdisc, 1, NM_RDISC_DHCP_LEVEL_OTHERCONF, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::1", now, 10, NM_RDISC_PREFERENCE_MEDIUM); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::1", now, 10, 10); + nm_fake_rdisc_add_route (rdisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10); + nm_fake_rdisc_add_dns_server (rdisc, id, "2001:db8:c:c::1", now, 10); + nm_fake_rdisc_add_dns_domain (rdisc, id, "foobar.com", now, 10); + + g_signal_connect (rdisc, + NM_RDISC_CONFIG_CHANGED, + G_CALLBACK (test_simple_changed), + &data); + + nm_rdisc_start (NM_RDISC (rdisc)); + g_main_loop_run (data.loop); + g_assert_cmpint (data.counter, ==, 1); + + g_object_unref (rdisc); + g_main_loop_unref (data.loop); +} + +static void +test_everything_rs_sent (NMRDisc *rdisc, TestData *data) +{ + g_assert_cmpint (data->rs_counter, ==, 0); + data->rs_counter++; +} + +static void +test_everything_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, TestData *data) +{ + if (data->counter == 0) { + g_assert_cmpint (data->rs_counter, ==, 1); + g_assert_cmpint (changed, ==, NM_RDISC_CONFIG_DHCP_LEVEL | + NM_RDISC_CONFIG_GATEWAYS | + NM_RDISC_CONFIG_ADDRESSES | + NM_RDISC_CONFIG_ROUTES | + NM_RDISC_CONFIG_DNS_SERVERS | + NM_RDISC_CONFIG_DNS_DOMAINS | + NM_RDISC_CONFIG_HOP_LIMIT | + NM_RDISC_CONFIG_MTU); + match_gateway (rdisc->gateways, 0, "fe80::1", data->timestamp1, 10, NM_RDISC_PREFERENCE_MEDIUM); + match_address (rdisc->addresses, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10); + match_route (rdisc->routes, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10); + match_dns_server (rdisc->dns_servers, 0, "2001:db8:c:c::1", data->timestamp1, 10); + match_dns_domain (rdisc->dns_domains, 0, "foobar.com", data->timestamp1, 10); + } else if (data->counter == 1) { + g_assert_cmpint (changed, ==, NM_RDISC_CONFIG_GATEWAYS | + NM_RDISC_CONFIG_ADDRESSES | + NM_RDISC_CONFIG_ROUTES | + NM_RDISC_CONFIG_DNS_SERVERS | + NM_RDISC_CONFIG_DNS_DOMAINS); + + g_assert_cmpint (rdisc->gateways->len, ==, 1); + match_gateway (rdisc->gateways, 0, "fe80::2", data->timestamp1, 10, NM_RDISC_PREFERENCE_MEDIUM); + g_assert_cmpint (rdisc->addresses->len, ==, 1); + match_address (rdisc->addresses, 0, "2001:db8:a:a::2", data->timestamp1, 10, 10); + g_assert_cmpint (rdisc->routes->len, ==, 1); + match_route (rdisc->routes, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10); + g_assert_cmpint (rdisc->dns_servers->len, ==, 1); + match_dns_server (rdisc->dns_servers, 0, "2001:db8:c:c::2", data->timestamp1, 10); + g_assert_cmpint (rdisc->dns_domains->len, ==, 1); + match_dns_domain (rdisc->dns_domains, 0, "foobar2.com", data->timestamp1, 10); + + g_assert (nm_fake_rdisc_done (NM_FAKE_RDISC (rdisc))); + g_main_loop_quit (data->loop); + } else + g_assert_not_reached (); + + data->counter++; +} + +static void +test_everything (void) +{ + NMFakeRDisc *rdisc = rdisc_new (); + guint32 now = nm_utils_get_monotonic_timestamp_s (); + TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now }; + guint id; + + id = nm_fake_rdisc_add_ra (rdisc, 1, NM_RDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::1", now, 10, NM_RDISC_PREFERENCE_MEDIUM); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::1", now, 10, 10); + nm_fake_rdisc_add_route (rdisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10); + nm_fake_rdisc_add_dns_server (rdisc, id, "2001:db8:c:c::1", now, 10); + nm_fake_rdisc_add_dns_domain (rdisc, id, "foobar.com", now, 10); + + /* expire everything from the first RA in the second */ + id = nm_fake_rdisc_add_ra (rdisc, 1, NM_RDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::1", now, 0, NM_RDISC_PREFERENCE_MEDIUM); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::1", now, 0, 0); + nm_fake_rdisc_add_route (rdisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 0, 0); + nm_fake_rdisc_add_dns_server (rdisc, id, "2001:db8:c:c::1", now, 0); + nm_fake_rdisc_add_dns_domain (rdisc, id, "foobar.com", now, 0); + + /* and add some new stuff */ + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::2", now, 10, NM_RDISC_PREFERENCE_MEDIUM); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::2", now, 10, 10); + nm_fake_rdisc_add_route (rdisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10); + nm_fake_rdisc_add_dns_server (rdisc, id, "2001:db8:c:c::2", now, 10); + nm_fake_rdisc_add_dns_domain (rdisc, id, "foobar2.com", now, 10); + + g_signal_connect (rdisc, + NM_RDISC_CONFIG_CHANGED, + G_CALLBACK (test_everything_changed), + &data); + g_signal_connect (rdisc, + NM_FAKE_RDISC_RS_SENT, + G_CALLBACK (test_everything_rs_sent), + &data); + + nm_rdisc_start (NM_RDISC (rdisc)); + g_main_loop_run (data.loop); + g_assert_cmpint (data.counter, ==, 2); + g_assert_cmpint (data.rs_counter, ==, 1); + + g_object_unref (rdisc); + g_main_loop_unref (data.loop); +} + +NMTST_DEFINE (); + +int +main (int argc, char **argv) +{ + nmtst_init_with_logging (&argc, &argv, NULL, "DEFAULT"); + + if (nmtst_test_quick ()) { + g_print ("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", str_if_set (g_get_prgname (), "test-rdisc-fake")); + return EXIT_SKIP; + } + + nm_fake_platform_setup (); + + g_test_add_func ("/rdisc/simple", test_simple); + g_test_add_func ("/rdisc/everything-changed", test_everything); + + return g_test_run (); +} diff --git a/src/rdisc/tests/rdisc.c b/src/rdisc/tests/test-rdisc-linux.c similarity index 69% rename from src/rdisc/tests/rdisc.c rename to src/rdisc/tests/test-rdisc-linux.c index d577a63ae4..9b2ccafd67 100644 --- a/src/rdisc/tests/rdisc.c +++ b/src/rdisc/tests/test-rdisc-linux.c @@ -24,51 +24,47 @@ #include #include "nm-rdisc.h" -#include "nm-fake-rdisc.h" #include "nm-lndp-rdisc.h" #include "nm-logging.h" -#include "nm-fake-platform.h" #include "nm-linux-platform.h" +#include "nm-test-utils.h" + +NMTST_DEFINE (); + int main (int argc, char **argv) { GMainLoop *loop; NMRDisc *rdisc; - NMRDisc *(*new) (int ifindex, const char *ifname); int ifindex = 1; const char *ifname; -#if !GLIB_CHECK_VERSION (2, 35, 0) - g_type_init (); -#endif + nmtst_init_with_logging (&argc, &argv, NULL, "DEFAULT"); + + if (getuid () != 0) { + g_print ("Missing permission: must run as root\n"); + return EXIT_FAILURE; + } loop = g_main_loop_new (NULL, FALSE); - nm_logging_setup ("debug", "ip6", NULL, NULL); - openlog (G_LOG_DOMAIN, LOG_CONS | LOG_PERROR, LOG_DAEMON); - argv++; - if (!g_strcmp0 (argv[0], "--fake")) { - new = nm_fake_rdisc_new; - nm_fake_platform_setup (); - argv++; - } else { - new = nm_lndp_rdisc_new; - nm_linux_platform_setup (); - } + nm_linux_platform_setup (); - if (argv[0]) { - ifname = argv[0]; + if (argv[1]) { + ifname = argv[1]; ifindex = nm_platform_link_get_ifindex (ifname); } else { - ifindex = 1; - ifname = nm_platform_link_get_name (ifindex); + g_print ("Missing command line argument \"interface-name\"\n"); + return EXIT_FAILURE; } - rdisc = new (ifindex, ifname); - if (!rdisc) + rdisc = nm_lndp_rdisc_new (ifindex, ifname); + if (!rdisc) { + g_print ("Failed to create NMRDisc instance\n"); return EXIT_FAILURE; + } nm_rdisc_start (rdisc); g_main_loop_run (loop); From 415b7b3e257c5d5526618b0d6a89a6d7fb235f98 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 9 Apr 2015 17:46:31 -0500 Subject: [PATCH 7/8] rdisc: fix double-addition of gateways & routes if priority increases If a route or gateway's priority increased, the item would be added to the array again without removing the older entry. At the same time don't bother adding an item with a zero lifetime, since it will just be removed again by the clean_* functions. --- src/rdisc/nm-rdisc.c | 77 +++++++++++++++++----------- src/rdisc/tests/test-rdisc-fake.c | 83 +++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 29 deletions(-) diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index 41a11615d5..cc7bc23316 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -56,27 +56,34 @@ static guint signals[LAST_SIGNAL] = { 0 }; gboolean nm_rdisc_add_gateway (NMRDisc *rdisc, const NMRDiscGateway *new) { - int i; + int i, insert_idx = -1; for (i = 0; i < rdisc->gateways->len; i++) { NMRDiscGateway *item = &g_array_index (rdisc->gateways, NMRDiscGateway, i); if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { + if (new->lifetime == 0) { + g_array_remove_index (rdisc->gateways, i--); + return TRUE; + } + if (item->preference != new->preference) { g_array_remove_index (rdisc->gateways, i--); continue; } + memcpy (item, new, sizeof (*new)); return FALSE; } /* Put before less preferable gateways. */ - if (item->preference < new->preference) - break; + if (item->preference < new->preference && insert_idx < 0) + insert_idx = i; } - g_array_insert_val (rdisc->gateways, i, *new); - return TRUE; + if (new->lifetime) + g_array_insert_val (rdisc->gateways, CLAMP (insert_idx, 0, G_MAXINT), *new); + return !!new->lifetime; } gboolean @@ -88,9 +95,15 @@ nm_rdisc_add_address (NMRDisc *rdisc, const NMRDiscAddress *new) NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i); if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { - gboolean changed = item->timestamp + item->lifetime != new->timestamp + new->lifetime || - item->timestamp + item->preferred != new->timestamp + new->preferred; + gboolean changed; + if (new->lifetime == 0) { + g_array_remove_index (rdisc->addresses, i--); + return TRUE; + } + + changed = item->timestamp + item->lifetime != new->timestamp + new->lifetime || + item->timestamp + item->preferred != new->timestamp + new->preferred; *item = *new; return changed; } @@ -103,34 +116,42 @@ nm_rdisc_add_address (NMRDisc *rdisc, const NMRDiscAddress *new) if (rdisc->max_addresses && rdisc->addresses->len >= rdisc->max_addresses) return FALSE; - g_array_insert_val (rdisc->addresses, i, *new); - return TRUE; + if (new->lifetime) + g_array_insert_val (rdisc->addresses, i, *new); + return !!new->lifetime; } gboolean nm_rdisc_add_route (NMRDisc *rdisc, const NMRDiscRoute *new) { - int i; + int i, insert_idx = -1; for (i = 0; i < rdisc->routes->len; i++) { NMRDiscRoute *item = &g_array_index (rdisc->routes, NMRDiscRoute, i); if (IN6_ARE_ADDR_EQUAL (&item->network, &new->network) && item->plen == new->plen) { + if (new->lifetime == 0) { + g_array_remove_index (rdisc->routes, i--); + return TRUE; + } + if (item->preference != new->preference) { g_array_remove_index (rdisc->routes, i--); continue; } + memcpy (item, new, sizeof (*new)); return FALSE; } /* Put before less preferable routes. */ - if (item->preference < new->preference) - break; + if (item->preference < new->preference && insert_idx < 0) + insert_idx = i; } - g_array_insert_val (rdisc->routes, i, *new); - return TRUE; + if (new->lifetime) + g_array_insert_val (rdisc->routes, CLAMP (insert_idx, 0, G_MAXINT), *new); + return !!new->lifetime; } gboolean @@ -142,25 +163,21 @@ nm_rdisc_add_dns_server (NMRDisc *rdisc, const NMRDiscDNSServer *new) NMRDiscDNSServer *item = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i); if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) { - gboolean changed; - if (new->lifetime == 0) { g_array_remove_index (rdisc->dns_servers, i); return TRUE; } - - changed = (item->timestamp != new->timestamp || - item->lifetime != new->lifetime); - if (changed) { - item->timestamp = new->timestamp; - item->lifetime = new->lifetime; + if (item->timestamp != new->timestamp || item->lifetime != new->lifetime) { + *item = *new; + return TRUE; } - return changed; + return FALSE; } } - g_array_insert_val (rdisc->dns_servers, i, *new); - return TRUE; + if (new->lifetime) + g_array_insert_val (rdisc->dns_servers, i, *new); + return !!new->lifetime; } /* Copies new->domain if 'new' is added to the dns_domains list */ @@ -191,10 +208,12 @@ nm_rdisc_add_dns_domain (NMRDisc *rdisc, const NMRDiscDNSDomain *new) } } - g_array_insert_val (rdisc->dns_domains, i, *new); - item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); - item->domain = g_strdup (new->domain); - return TRUE; + if (new->lifetime) { + g_array_insert_val (rdisc->dns_domains, i, *new); + item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i); + item->domain = g_strdup (new->domain); + } + return !!new->lifetime; } /******************************************************************/ diff --git a/src/rdisc/tests/test-rdisc-fake.c b/src/rdisc/tests/test-rdisc-fake.c index c8db7f1a6c..0025ab6a51 100644 --- a/src/rdisc/tests/test-rdisc-fake.c +++ b/src/rdisc/tests/test-rdisc-fake.c @@ -262,6 +262,88 @@ test_everything (void) g_main_loop_unref (data.loop); } +static void +test_preference_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, TestData *data) +{ + if (data->counter == 1) { + g_assert_cmpint (changed, ==, NM_RDISC_CONFIG_GATEWAYS | + NM_RDISC_CONFIG_ADDRESSES | + NM_RDISC_CONFIG_ROUTES); + g_assert_cmpint (rdisc->gateways->len, ==, 2); + match_gateway (rdisc->gateways, 0, "fe80::2", data->timestamp1 + 1, 10, NM_RDISC_PREFERENCE_MEDIUM); + match_gateway (rdisc->gateways, 1, "fe80::1", data->timestamp1, 10, NM_RDISC_PREFERENCE_LOW); + g_assert_cmpint (rdisc->addresses->len, ==, 2); + match_address (rdisc->addresses, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10); + match_address (rdisc->addresses, 1, "2001:db8:a:a::2", data->timestamp1 + 1, 10, 10); + g_assert_cmpint (rdisc->routes->len, ==, 2); + match_route (rdisc->routes, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10); + match_route (rdisc->routes, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5); + } else if (data->counter == 2) { + g_assert_cmpint (changed, ==, NM_RDISC_CONFIG_GATEWAYS | + NM_RDISC_CONFIG_ADDRESSES | + NM_RDISC_CONFIG_ROUTES); + + g_assert_cmpint (rdisc->gateways->len, ==, 2); + match_gateway (rdisc->gateways, 0, "fe80::1", data->timestamp1 + 2, 10, NM_RDISC_PREFERENCE_HIGH); + match_gateway (rdisc->gateways, 1, "fe80::2", data->timestamp1 + 1, 10, NM_RDISC_PREFERENCE_MEDIUM); + g_assert_cmpint (rdisc->addresses->len, ==, 2); + match_address (rdisc->addresses, 0, "2001:db8:a:a::1", data->timestamp1 + 2, 10, 10); + match_address (rdisc->addresses, 1, "2001:db8:a:a::2", data->timestamp1 + 1, 10, 10); + g_assert_cmpint (rdisc->routes->len, ==, 2); + match_route (rdisc->routes, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1 + 2, 10, 15); + match_route (rdisc->routes, 1, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10); + + g_assert (nm_fake_rdisc_done (NM_FAKE_RDISC (rdisc))); + g_main_loop_quit (data->loop); + } + + data->counter++; +} + +static void +test_preference (void) +{ + NMFakeRDisc *rdisc = rdisc_new (); + guint32 now = nm_utils_get_monotonic_timestamp_s (); + TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now }; + guint id; + + /* Test that when a low-preference and medium gateway send advertisements, + * that if the low-preference gateway switches to high-preference, we do + * not get duplicates in the gateway list. + */ + + id = nm_fake_rdisc_add_ra (rdisc, 1, NM_RDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::1", now, 10, NM_RDISC_PREFERENCE_LOW); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::1", now, 10, 10); + nm_fake_rdisc_add_route (rdisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 5); + + id = nm_fake_rdisc_add_ra (rdisc, 1, NM_RDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::2", ++now, 10, NM_RDISC_PREFERENCE_MEDIUM); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::2", now, 10, 10); + nm_fake_rdisc_add_route (rdisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10); + + id = nm_fake_rdisc_add_ra (rdisc, 1, NM_RDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::1", ++now, 10, NM_RDISC_PREFERENCE_HIGH); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::1", now, 10, 10); + nm_fake_rdisc_add_route (rdisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 15); + + g_signal_connect (rdisc, + NM_RDISC_CONFIG_CHANGED, + G_CALLBACK (test_preference_changed), + &data); + + nm_rdisc_start (NM_RDISC (rdisc)); + g_main_loop_run (data.loop); + g_assert_cmpint (data.counter, ==, 3); + + g_object_unref (rdisc); + g_main_loop_unref (data.loop); +} + NMTST_DEFINE (); int @@ -278,6 +360,7 @@ main (int argc, char **argv) g_test_add_func ("/rdisc/simple", test_simple); g_test_add_func ("/rdisc/everything-changed", test_everything); + g_test_add_func ("/rdisc/preference-changed", test_preference); return g_test_run (); } From d96b05bd364a334f20b10f5322aabf01ba28423d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 9 Apr 2015 18:46:25 -0500 Subject: [PATCH 8/8] rdisc: prevent solicitation loop for expiring DNS information (rh #1207730) (rh #1151665) A solicitation loop could result for two cases: 1) a router sent DNS information, then removed that information without sending it with lifetime=0 2) two routers exist, one sending DNS information and the other not, and the first router which sends DNS information disappears In these cases a solicitation would be generated when the DNS information reached 1/2 its lifetime. A router would then reply to the solicitation without DNS information, which would then trigger another lifetime check, which finds that the DNS info is still 1/2 lifetime. Which triggers another solicitation, etc. Fix this by ensuring that a solicitation is never sent less than rtr_solicitation_interval seconds after the last one. --- src/rdisc/nm-fake-rdisc.c | 7 +++ src/rdisc/nm-fake-rdisc.h | 2 + src/rdisc/nm-rdisc.c | 18 ++++++- src/rdisc/tests/test-rdisc-fake.c | 87 +++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/rdisc/nm-fake-rdisc.c b/src/rdisc/nm-fake-rdisc.c index a281b72252..68f43c0e52 100644 --- a/src/rdisc/nm-fake-rdisc.c +++ b/src/rdisc/nm-fake-rdisc.c @@ -333,6 +333,13 @@ start (NMRDisc *rdisc) priv->receive_ra_id = g_timeout_add_seconds (ra->when, receive_ra, rdisc); } +void +nm_fake_rdisc_emit_new_ras (NMFakeRDisc *self) +{ + if (!NM_FAKE_RDISC_GET_PRIVATE (self)->receive_ra_id) + start (NM_RDISC (self)); +} + /******************************************************************/ NMRDisc * diff --git a/src/rdisc/nm-fake-rdisc.h b/src/rdisc/nm-fake-rdisc.h index 5a8daefb21..74f74897fb 100644 --- a/src/rdisc/nm-fake-rdisc.h +++ b/src/rdisc/nm-fake-rdisc.h @@ -87,6 +87,8 @@ void nm_fake_rdisc_add_dns_domain (NMFakeRDisc *self, guint32 timestamp, guint32 lifetime); +void nm_fake_rdisc_emit_new_ras (NMFakeRDisc *self); + gboolean nm_fake_rdisc_done (NMFakeRDisc *self); #endif /* __NETWORKMANAGER_FAKE_RDISC_H__ */ diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index cc7bc23316..07271a89e4 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -35,6 +35,7 @@ typedef struct { int solicitations_left; guint send_rs_id; + gint64 last_rs; guint ra_timeout_id; /* first RA timeout */ guint timeout_id; /* prefix/dns/etc lifetime timeout */ } NMRDiscPrivate; @@ -259,6 +260,7 @@ send_rs (NMRDisc *rdisc) if (klass->send_rs (rdisc)) priv->solicitations_left--; + priv->last_rs = nm_utils_get_monotonic_timestamp_s (); if (priv->solicitations_left > 0) { debug ("(%s): scheduling router solicitation retry in %d seconds.", rdisc->ifname, rdisc->rtr_solicitation_interval); @@ -277,11 +279,16 @@ static void solicit (NMRDisc *rdisc) { NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + guint32 now = nm_utils_get_monotonic_timestamp_s (); + gint64 next; if (!priv->send_rs_id) { - debug ("(%s): scheduling router solicitation.", rdisc->ifname); - priv->send_rs_id = g_idle_add ((GSourceFunc) send_rs, rdisc); priv->solicitations_left = rdisc->rtr_solicitations; + + next = CLAMP (priv->last_rs + rdisc->rtr_solicitation_interval - now, 0, G_MAXINT32); + debug ("(%s): scheduling explicit router solicitation request in %" G_GINT64_FORMAT " seconds.", + rdisc->ifname, next); + priv->send_rs_id = g_timeout_add_seconds ((guint32) next, (GSourceFunc) send_rs, rdisc); } } @@ -564,6 +571,8 @@ dns_domain_free (gpointer data) static void nm_rdisc_init (NMRDisc *rdisc) { + NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc); + rdisc->gateways = g_array_new (FALSE, FALSE, sizeof (NMRDiscGateway)); rdisc->addresses = g_array_new (FALSE, FALSE, sizeof (NMRDiscAddress)); rdisc->routes = g_array_new (FALSE, FALSE, sizeof (NMRDiscRoute)); @@ -571,6 +580,11 @@ nm_rdisc_init (NMRDisc *rdisc) rdisc->dns_domains = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSDomain)); g_array_set_clear_func (rdisc->dns_domains, dns_domain_free); rdisc->hop_limit = 64; + + /* Start at very low number so that last_rs - rtr_solicitation_interval + * is much lower than nm_utils_get_monotonic_timestamp_s() at startup. + */ + priv->last_rs = G_MININT32; } static void diff --git a/src/rdisc/tests/test-rdisc-fake.c b/src/rdisc/tests/test-rdisc-fake.c index 0025ab6a51..8b7bd37a34 100644 --- a/src/rdisc/tests/test-rdisc-fake.c +++ b/src/rdisc/tests/test-rdisc-fake.c @@ -107,6 +107,8 @@ typedef struct { guint counter; guint rs_counter; guint32 timestamp1; + guint32 first_solicit; + guint32 timeout_id; } TestData; static void @@ -344,6 +346,90 @@ test_preference (void) g_main_loop_unref (data.loop); } +static void +test_dns_solicit_loop_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, TestData *data) +{ + data->counter++; +} + +static gboolean +success_timeout (TestData *data) +{ + data->timeout_id = 0; + g_main_loop_quit (data->loop); + return G_SOURCE_REMOVE; +} + +static void +test_dns_solicit_loop_rs_sent (NMFakeRDisc *rdisc, TestData *data) +{ + guint32 now = nm_utils_get_monotonic_timestamp_s (); + guint id; + + if (data->rs_counter > 0 && data->rs_counter < 6) { + if (data->rs_counter == 1) { + data->first_solicit = now; + /* Kill the test after 10 seconds if it hasn't failed yet */ + data->timeout_id = g_timeout_add_seconds (10, (GSourceFunc) success_timeout, data); + } + + /* On all but the first solicitation, which should be triggered by the + * DNS servers reaching 1/2 lifetime, emit a new RA without the DNS + * servers again. + */ + id = nm_fake_rdisc_add_ra (rdisc, 0, NM_RDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::1", now, 10, NM_RDISC_PREFERENCE_MEDIUM); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::1", now, 10, 10); + + nm_fake_rdisc_emit_new_ras (rdisc); + } else if (data->rs_counter >= 6) { + /* Fail if we've sent too many solicitations in the past 4 seconds */ + g_assert_cmpint (now - data->first_solicit, >, 4); + g_source_remove (data->timeout_id); + g_main_loop_quit (data->loop); + } + data->rs_counter++; +} + +static void +test_dns_solicit_loop (void) +{ + NMFakeRDisc *rdisc = rdisc_new (); + guint32 now = nm_utils_get_monotonic_timestamp_s (); + TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now, 0 }; + guint id; + + /* Ensure that no solicitation loop happens when DNS servers or domains + * stop being sent in advertisements. This can happen if two routers + * send RAs, but the one sending DNS info stops responding, or if one + * router removes the DNS info from the RA without zero-lifetiming them + * first. + */ + + id = nm_fake_rdisc_add_ra (rdisc, 1, NM_RDISC_DHCP_LEVEL_NONE, 4, 1500); + g_assert (id); + nm_fake_rdisc_add_gateway (rdisc, id, "fe80::1", now, 10, NM_RDISC_PREFERENCE_LOW); + nm_fake_rdisc_add_address (rdisc, id, "2001:db8:a:a::1", now, 10, 10); + nm_fake_rdisc_add_dns_server (rdisc, id, "2001:db8:c:c::1", now, 6); + + g_signal_connect (rdisc, + NM_RDISC_CONFIG_CHANGED, + G_CALLBACK (test_dns_solicit_loop_changed), + &data); + g_signal_connect (rdisc, + NM_FAKE_RDISC_RS_SENT, + G_CALLBACK (test_dns_solicit_loop_rs_sent), + &data); + + nm_rdisc_start (NM_RDISC (rdisc)); + g_main_loop_run (data.loop); + g_assert_cmpint (data.counter, ==, 3); + + g_object_unref (rdisc); + g_main_loop_unref (data.loop); +} + NMTST_DEFINE (); int @@ -361,6 +447,7 @@ main (int argc, char **argv) g_test_add_func ("/rdisc/simple", test_simple); g_test_add_func ("/rdisc/everything-changed", test_everything); g_test_add_func ("/rdisc/preference-changed", test_preference); + g_test_add_func ("/rdisc/dns-solicit-loop", test_dns_solicit_loop); return g_test_run (); }