diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index c3a2b92f3c4..5f9ef80e3e5 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -282,8 +282,24 @@ int link_get_captive_portal(Link *link, const char **ret) { return r; } - if (link->network->ipv6_accept_ra_use_captive_portal && link->ndisc_captive_portal) - ndisc_cp = link->ndisc_captive_portal; + if (link->network->ipv6_accept_ra_use_captive_portal) { + NDiscCaptivePortal *cp; + usec_t usec = 0; + + /* Use the captive portal with the longest lifetime. */ + + SET_FOREACH(cp, link->ndisc_captive_portals) { + if (cp->lifetime_usec < usec) + continue; + + ndisc_cp = cp->captive_portal; + usec = cp->lifetime_usec; + } + + if (set_size(link->ndisc_captive_portals) > 1) + log_link_debug(link, "Multiple captive portals obtained by IPv6RA, using \"%s\" and ignoring others.", + ndisc_cp); + } if (dhcp4_cp) { if (dhcp6_cp && !streq(dhcp4_cp, dhcp6_cp)) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 7d9ea6f8e7b..62dd892afaa 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -196,7 +196,6 @@ static Link *link_free(Link *link) { free(link->ssid); free(link->previous_ssid); free(link->driver); - free(link->ndisc_captive_portal); unlink_and_free(link->lease_file); unlink_and_free(link->lldp_file); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index bc8fe083746..b91259c4870 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -154,7 +154,7 @@ typedef struct Link { sd_event_source *ndisc_expire; Set *ndisc_rdnss; Set *ndisc_dnssl; - char *ndisc_captive_portal; + Set *ndisc_captive_portals; unsigned ndisc_messages; bool ndisc_configured:1; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 8115595dc5c..66d8d278700 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -716,43 +716,6 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( ndisc_dnssl_compare_func, free); -static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) { - const char *uri; - _cleanup_free_ char *captive_portal = NULL; - size_t len; - int r; - - assert(link); - assert(link->network); - assert(rt); - - if (!link->network->ipv6_accept_ra_use_captive_portal) - return 0; - - r = sd_ndisc_router_captive_portal_get_uri(rt, &uri, &len); - if (r < 0) - return r; - - if (len == 0) { - link->ndisc_captive_portal = mfree(link->ndisc_captive_portal); - return 0; - } - - r = make_cstring(uri, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &captive_portal); - if (r < 0) - return r; - - if (!in_charset(captive_portal, URI_VALID)) - return -EBADMSG; - - if (!streq_ptr(link->ndisc_captive_portal, captive_portal)) { - free_and_replace(link->ndisc_captive_portal, captive_portal); - link_dirty(link); - } - - return 0; -} - static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { _cleanup_strv_free_ char **l = NULL; usec_t lifetime_usec, timestamp_usec; @@ -834,6 +797,108 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { return 0; } +static NDiscCaptivePortal* ndisc_captive_portal_free(NDiscCaptivePortal *x) { + if (!x) + return NULL; + + free(x->captive_portal); + return mfree(x); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(NDiscCaptivePortal*, ndisc_captive_portal_free); + +static void ndisc_captive_portal_hash_func(const NDiscCaptivePortal *x, struct siphash *state) { + assert(x); + siphash24_compress_string(x->captive_portal, state); +} + +static int ndisc_captive_portal_compare_func(const NDiscCaptivePortal *a, const NDiscCaptivePortal *b) { + assert(a); + assert(b); + return strcmp_ptr(a->captive_portal, b->captive_portal); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_captive_portal_hash_ops, + NDiscCaptivePortal, + ndisc_captive_portal_hash_func, + ndisc_captive_portal_compare_func, + free); + +static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) { + _cleanup_(ndisc_captive_portal_freep) NDiscCaptivePortal *new_entry = NULL; + _cleanup_free_ char *captive_portal = NULL; + usec_t lifetime_usec, timestamp_usec; + NDiscCaptivePortal *exist; + struct in6_addr router; + uint16_t lifetime_sec; + const char *uri; + size_t len; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ipv6_accept_ra_use_captive_portal) + return 0; + + r = sd_ndisc_router_get_address(rt, &router); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); + + r = sd_ndisc_router_get_lifetime(rt, &lifetime_sec); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m"); + + r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, ×tamp_usec); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); + + lifetime_usec = sec16_to_usec(lifetime_sec, timestamp_usec); + + r = sd_ndisc_router_captive_portal_get_uri(rt, &uri, &len); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get captive portal from RA: %m"); + + if (len == 0) + return 0; + + r = make_cstring(uri, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &captive_portal); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to convert captive portal URI: %m"); + + if (!in_charset(captive_portal, URI_VALID)) + return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received invalid captive portal, ignoring."); + + exist = set_get(link->ndisc_captive_portals, &(NDiscCaptivePortal) { .captive_portal = captive_portal }); + if (exist) { + /* update existing entry */ + exist->router = router; + exist->lifetime_usec = lifetime_usec; + return 0; + } + + new_entry = new(NDiscCaptivePortal, 1); + if (!new_entry) + return log_oom(); + + *new_entry = (NDiscCaptivePortal) { + .router = router, + .lifetime_usec = lifetime_usec, + .captive_portal = TAKE_PTR(captive_portal), + }; + + r = set_ensure_put(&link->ndisc_captive_portals, &ndisc_captive_portal_hash_ops, new_entry); + if (r < 0) + return log_oom(); + assert(r > 0); + TAKE_PTR(new_entry); + + link_dirty(link); + return 0; +} + static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { int r; @@ -882,6 +947,7 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { bool updated = false; NDiscDNSSL *dnssl; NDiscRDNSS *rdnss; + NDiscCaptivePortal *cp; Address *address; Route *route; int r = 0, k; @@ -934,6 +1000,14 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { updated = true; } + SET_FOREACH(cp, link->ndisc_captive_portals) { + if (cp->lifetime_usec >= timestamp_usec) + continue; /* the captive portal is still valid */ + + free(set_remove(link->ndisc_captive_portals, cp)); + updated = true; + } + if (updated) link_dirty(link); @@ -957,6 +1031,7 @@ static int ndisc_expire_handler(sd_event_source *s, uint64_t usec, void *userdat static int ndisc_setup_expire(Link *link) { usec_t lifetime_usec = USEC_INFINITY; + NDiscCaptivePortal *cp; NDiscDNSSL *dnssl; NDiscRDNSS *rdnss; Address *address; @@ -992,6 +1067,9 @@ static int ndisc_setup_expire(Link *link) { SET_FOREACH(dnssl, link->ndisc_dnssl) lifetime_usec = MIN(lifetime_usec, dnssl->lifetime_usec); + SET_FOREACH(cp, link->ndisc_captive_portals) + lifetime_usec = MIN(lifetime_usec, cp->lifetime_usec); + if (lifetime_usec == USEC_INFINITY) return 0; @@ -1254,10 +1332,11 @@ int ndisc_stop(Link *link) { void ndisc_flush(Link *link) { assert(link); - /* Removes all RDNSS and DNSSL entries, without exception */ + /* Remove all RDNSS, DNSSL, and Captive Portal entries, without exception. */ link->ndisc_rdnss = set_free(link->ndisc_rdnss); link->ndisc_dnssl = set_free(link->ndisc_dnssl); + link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); } static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index 7affa8217e4..267f7d4a020 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -31,6 +31,14 @@ typedef struct NDiscDNSSL { /* The domain name follows immediately. */ } NDiscDNSSL; +typedef struct NDiscCaptivePortal { + struct in6_addr router; + /* This is an absolute point in time, and NOT a timespan/duration. + * Must be specified with CLOCK_BOOTTIME. */ + usec_t lifetime_usec; + char *captive_portal; +} NDiscCaptivePortal; + static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); }