sd-ndisc: stop solicitations only after an RS and non-zero-lifetime RA

RFC 4861 section 6.3.7 states that a host should send at least one Router
Solicitation even if it receives an unsolicited Router Advertisement
before sending its first solicitation, and should stop soliciting only
after receiving a valid Router Advertisement with a non-zero Router Lifetime.

Keep the RS timer active until both conditions are met, and add a
regression test for a zero-lifetime RA arriving before the initial RS.

Fixes #43205.
This commit is contained in:
quanyeyang
2026-09-10 11:53:44 +09:00
committed by Yu Watanabe
parent 2ff84cf0a1
commit 5199e9618d
2 changed files with 48 additions and 1 deletions
+6 -1
View File
@@ -219,7 +219,10 @@ static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) {
if (r < 0)
return r;
(void) event_source_disable(nd->timeout_event_source);
/* Stop sending RS once we have sent at least one RS and received an RA with a non-zero lifetime. */
if (nd->retransmit_time > 0 && sd_ndisc_router_get_lifetime(rt, /* ret= */ NULL) > 0)
(void) event_source_disable(nd->timeout_event_source);
(void) event_source_disable(nd->timeout_no_ra);
if (DEBUG_LOGGING) {
@@ -466,6 +469,8 @@ static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata
log_ndisc(nd, "No RA received before link confirmation timeout");
/* Also stop sending RS here, by disabling timeout_event_source. */
(void) event_source_disable(nd->timeout_event_source);
(void) event_source_disable(nd->timeout_no_ra);
ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
+42
View File
@@ -295,6 +295,15 @@ static void test_callback_count(
(*count)++;
}
static int on_recv_rs_exit(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
ASSERT_OK(icmp6_packet_receive(fd, &packet));
ASSERT_EQ(icmp6_packet_get_type(packet), ND_ROUTER_SOLICIT);
return ASSERT_OK(sd_event_exit(ASSERT_PTR(userdata), 0));
}
static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
assert_se(icmp6_packet_receive(fd, &packet) >= 0);
@@ -323,6 +332,39 @@ TEST(rs_ifindex_mismatch) {
test_fd[1] = safe_close(test_fd[1]);
}
TEST(rs_after_zero_lifetime_ra) {
static const struct nd_router_advert advertisement = {
.nd_ra_type = ND_ROUTER_ADVERT,
};
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
unsigned count = 0;
ASSERT_OK(sd_event_new(&e));
ASSERT_OK(sd_ndisc_new(&nd));
ASSERT_OK(sd_ndisc_attach_event(nd, e, 0));
ASSERT_OK(sd_ndisc_set_ifindex(nd, 42));
ASSERT_OK(sd_ndisc_set_callback(nd, test_callback_count, &count));
ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME,
30 * USEC_PER_SEC, 0,
NULL, INT_TO_PTR(-ETIMEDOUT)));
ASSERT_OK(sd_ndisc_start(nd));
ASSERT_EQ(write(test_fd[1], &advertisement, sizeof(advertisement)), (ssize_t) sizeof(advertisement));
ASSERT_OK_POSITIVE(sd_event_run(e, UINT64_MAX));
ASSERT_EQ(count, 1U);
ASSERT_OK_POSITIVE(sd_event_source_get_enabled(nd->timeout_event_source, NULL));
ASSERT_OK(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_exit, e));
ASSERT_OK(sd_event_source_set_io_fd_own(s, true));
ASSERT_OK(sd_event_source_set_time(nd->timeout_event_source, 0));
ASSERT_OK(sd_event_loop(e));
test_fd[1] = -EBADF;
}
TEST(rs) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;