network: handle captive portal with multiple routers

Before this patch, if a network has multiple routers and one of them
provides a captive portal, then the portal was overwritten or cleared
when another RA from another router is received.

This makes captive portals managed in the similar way as DNS servers or
DNS domains. So now captive portal can safely handled even if a network
has multiple routers.
This commit is contained in:
Yu Watanabe 2023-07-06 11:12:19 +09:00
parent 04eaf63c66
commit 64de00c49f
5 changed files with 144 additions and 42 deletions

View file

@ -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))

View file

@ -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);

View file

@ -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;

View file

@ -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, &timestamp_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] = {

View file

@ -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));
}