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.
This commit is contained in:
@@ -333,6 +333,13 @@ start (NMRDisc *rdisc)
|
|||||||
priv->receive_ra_id = g_timeout_add_seconds (ra->when, receive_ra, 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 *
|
NMRDisc *
|
||||||
|
@@ -87,6 +87,8 @@ void nm_fake_rdisc_add_dns_domain (NMFakeRDisc *self,
|
|||||||
guint32 timestamp,
|
guint32 timestamp,
|
||||||
guint32 lifetime);
|
guint32 lifetime);
|
||||||
|
|
||||||
|
void nm_fake_rdisc_emit_new_ras (NMFakeRDisc *self);
|
||||||
|
|
||||||
gboolean nm_fake_rdisc_done (NMFakeRDisc *self);
|
gboolean nm_fake_rdisc_done (NMFakeRDisc *self);
|
||||||
|
|
||||||
#endif /* __NETWORKMANAGER_FAKE_RDISC_H__ */
|
#endif /* __NETWORKMANAGER_FAKE_RDISC_H__ */
|
||||||
|
@@ -35,6 +35,7 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
int solicitations_left;
|
int solicitations_left;
|
||||||
guint send_rs_id;
|
guint send_rs_id;
|
||||||
|
gint64 last_rs;
|
||||||
guint ra_timeout_id; /* first RA timeout */
|
guint ra_timeout_id; /* first RA timeout */
|
||||||
guint timeout_id; /* prefix/dns/etc lifetime timeout */
|
guint timeout_id; /* prefix/dns/etc lifetime timeout */
|
||||||
} NMRDiscPrivate;
|
} NMRDiscPrivate;
|
||||||
@@ -285,6 +286,7 @@ send_rs (NMRDisc *rdisc)
|
|||||||
if (klass->send_rs (rdisc))
|
if (klass->send_rs (rdisc))
|
||||||
priv->solicitations_left--;
|
priv->solicitations_left--;
|
||||||
|
|
||||||
|
priv->last_rs = nm_utils_get_monotonic_timestamp_s ();
|
||||||
if (priv->solicitations_left > 0) {
|
if (priv->solicitations_left > 0) {
|
||||||
debug ("(%s): scheduling router solicitation retry in %d seconds.",
|
debug ("(%s): scheduling router solicitation retry in %d seconds.",
|
||||||
rdisc->ifname, rdisc->rtr_solicitation_interval);
|
rdisc->ifname, rdisc->rtr_solicitation_interval);
|
||||||
@@ -303,11 +305,16 @@ static void
|
|||||||
solicit (NMRDisc *rdisc)
|
solicit (NMRDisc *rdisc)
|
||||||
{
|
{
|
||||||
NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc);
|
NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc);
|
||||||
|
guint32 now = nm_utils_get_monotonic_timestamp_s ();
|
||||||
|
gint64 next;
|
||||||
|
|
||||||
if (!priv->send_rs_id) {
|
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;
|
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
|
static void
|
||||||
nm_rdisc_init (NMRDisc *rdisc)
|
nm_rdisc_init (NMRDisc *rdisc)
|
||||||
{
|
{
|
||||||
|
NMRDiscPrivate *priv = NM_RDISC_GET_PRIVATE (rdisc);
|
||||||
|
|
||||||
rdisc->gateways = g_array_new (FALSE, FALSE, sizeof (NMRDiscGateway));
|
rdisc->gateways = g_array_new (FALSE, FALSE, sizeof (NMRDiscGateway));
|
||||||
rdisc->addresses = g_array_new (FALSE, FALSE, sizeof (NMRDiscAddress));
|
rdisc->addresses = g_array_new (FALSE, FALSE, sizeof (NMRDiscAddress));
|
||||||
rdisc->routes = g_array_new (FALSE, FALSE, sizeof (NMRDiscRoute));
|
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));
|
rdisc->dns_domains = g_array_new (FALSE, FALSE, sizeof (NMRDiscDNSDomain));
|
||||||
g_array_set_clear_func (rdisc->dns_domains, dns_domain_free);
|
g_array_set_clear_func (rdisc->dns_domains, dns_domain_free);
|
||||||
rdisc->hop_limit = 64;
|
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
|
static void
|
||||||
|
@@ -107,6 +107,8 @@ typedef struct {
|
|||||||
guint counter;
|
guint counter;
|
||||||
guint rs_counter;
|
guint rs_counter;
|
||||||
guint32 timestamp1;
|
guint32 timestamp1;
|
||||||
|
guint32 first_solicit;
|
||||||
|
guint32 timeout_id;
|
||||||
} TestData;
|
} TestData;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -344,6 +346,90 @@ test_preference (void)
|
|||||||
g_main_loop_unref (data.loop);
|
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 ();
|
NMTST_DEFINE ();
|
||||||
|
|
||||||
int
|
int
|
||||||
@@ -361,6 +447,7 @@ main (int argc, char **argv)
|
|||||||
g_test_add_func ("/rdisc/simple", test_simple);
|
g_test_add_func ("/rdisc/simple", test_simple);
|
||||||
g_test_add_func ("/rdisc/everything-changed", test_everything);
|
g_test_add_func ("/rdisc/everything-changed", test_everything);
|
||||||
g_test_add_func ("/rdisc/preference-changed", test_preference);
|
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 ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user