From 5199e9618dcebe50bde17811d8b4d1eb4c0e6854 Mon Sep 17 00:00:00 2001 From: quanyeyang Date: Wed, 9 Sep 2026 12:10:01 +0800 Subject: [PATCH] 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. --- src/libsystemd-network/sd-ndisc.c | 7 ++++- src/libsystemd-network/test-ndisc-rs.c | 42 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 51e12d4528..4cabd189bd 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -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); diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index 86b7c07bdb..6eda6a2116 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -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;