diff options
author | Dan Williams <dcbw@redhat.com> | 2015-04-09 18:46:25 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2015-05-01 16:35:47 -0500 |
commit | 19fa547d5d150dc2ea281636c4ba619e9e78872d (patch) | |
tree | a0ace35fe3dc6f641a5240095de93d9883972d2a | |
parent | b324b970bc57bb8c7d3070919ca6e1d937d2721d (diff) | |
download | NetworkManager-19fa547d5d150dc2ea281636c4ba619e9e78872d.tar.gz |
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.
-rw-r--r-- | src/rdisc/nm-fake-rdisc.c | 7 | ||||
-rw-r--r-- | src/rdisc/nm-fake-rdisc.h | 2 | ||||
-rw-r--r-- | src/rdisc/nm-rdisc.c | 18 | ||||
-rw-r--r-- | 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 604b70ecc0..19043a61ea 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; @@ -285,6 +286,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); @@ -303,11 +305,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); } } @@ -590,6 +597,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)); @@ -597,6 +606,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 53fd989f95..3b525203a2 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 (); } |