diff --git a/src/ip6-manager/nm-ip6-manager.c b/src/ip6-manager/nm-ip6-manager.c index ae748ca1b3..ea93f02c02 100644 --- a/src/ip6-manager/nm-ip6-manager.c +++ b/src/ip6-manager/nm-ip6-manager.c @@ -76,6 +76,11 @@ typedef struct { time_t expires; } NMIP6RDNSS; +typedef struct { + char domain[256]; + time_t expires; +} NMIP6DNSSL; + /******************************************************************/ typedef struct { @@ -97,6 +102,9 @@ typedef struct { GArray *rdnss_servers; guint rdnss_timeout_id; + GArray *dnssl_domains; + guint dnssl_timeout_id; + guint ip6flags_poll_id; guint32 ra_flags; @@ -122,6 +130,10 @@ nm_ip6_device_destroy (NMIP6Device *device) g_array_free (device->rdnss_servers, TRUE); if (device->rdnss_timeout_id) g_source_remove (device->rdnss_timeout_id); + if (device->dnssl_domains) + g_array_free (device->dnssl_domains, TRUE); + if (device->dnssl_timeout_id) + g_source_remove (device->dnssl_timeout_id); if (device->ip6flags_poll_id) g_source_remove (device->ip6flags_poll_id); @@ -155,6 +167,8 @@ nm_ip6_device_new (NMIP6Manager *manager, int ifindex) device->rdnss_servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS)); + device->dnssl_domains = g_array_new (FALSE, FALSE, sizeof (NMIP6DNSSL)); + g_hash_table_replace (priv->devices, GINT_TO_POINTER (device->ifindex), device); /* and the original value of IPv6 enable/disable */ @@ -300,6 +314,61 @@ set_rdnss_timeout (NMIP6Device *device) } } +static void set_dnssl_timeout (NMIP6Device *device); + +static gboolean +dnssl_expired (gpointer user_data) +{ + NMIP6Device *device = user_data; + CallbackInfo info = { device, IP6_DHCP_OPT_NONE }; + + nm_log_dbg (LOGD_IP6, "(%s): IPv6 DNSSL information expired", device->iface); + + set_dnssl_timeout (device); + emit_config_changed (&info); + return FALSE; +} + +static void +set_dnssl_timeout (NMIP6Device *device) +{ + time_t expires = 0, now = time (NULL); + NMIP6DNSSL *dnssl; + int i; + + if (device->dnssl_timeout_id) { + g_source_remove (device->dnssl_timeout_id); + device->dnssl_timeout_id = 0; + } + + /* Find the soonest expiration time. */ + for (i = 0; i < device->dnssl_domains->len; i++) { + dnssl = &g_array_index (device->dnssl_domains, NMIP6DNSSL, i); + if (dnssl->expires == 0) + continue; + + /* If the entry has already expired, remove it; the "+ 1" is + * because g_timeout_add_seconds() might fudge the timing a + * bit. + */ + if (dnssl->expires <= now + 1) { + nm_log_dbg (LOGD_IP6, "(%s): removing expired RA-provided domain %s", + device->iface, dnssl->domain); + g_array_remove_index (device->dnssl_domains, i--); + continue; + } + + if (!expires || dnssl->expires < expires) + expires = dnssl->expires; + } + + if (expires) { + device->dnssl_timeout_id = g_timeout_add_seconds (expires - now, + dnssl_expired, + device); + } +} + static CallbackInfo * callback_info_new (NMIP6Device *device, guint dhcp_opts, gboolean success) { @@ -569,6 +638,8 @@ process_prefix (NMIP6Manager *manager, struct nl_msg *msg) */ #define ND_OPT_RDNSS 25 +#define ND_OPT_DNSSL 31 + struct nd_opt_rdnss { uint8_t nd_opt_rdnss_type; uint8_t nd_opt_rdnss_len; @@ -577,6 +648,14 @@ struct nd_opt_rdnss { /* followed by one or more IPv6 addresses */ } __attribute__ ((packed)); +struct nd_opt_dnssl { + uint8_t nd_opt_dnssl_type; + uint8_t nd_opt_dnssl_len; + uint16_t nd_opt_dnssl_reserved1; + uint32_t nd_opt_dnssl_lifetime; + /* followed by one or more suffixes */ +} __attribute__ ((packed)); + static gboolean process_nduseropt_rdnss (NMIP6Device *device, struct nd_opt_hdr *opt) { @@ -669,6 +748,149 @@ process_nduseropt_rdnss (NMIP6Device *device, struct nd_opt_hdr *opt) return changed; } +static const char * +parse_dnssl_domain (const unsigned char *buffer, size_t maxlen) +{ + static char domain[256]; + size_t label_len; + + domain[0] = '\0'; + + while (maxlen > 0) { + label_len = *buffer; + buffer++; + maxlen--; + + if (label_len == 0) + return domain; + + if (label_len > maxlen) + return NULL; + if ((sizeof (domain) - strlen (domain)) < (label_len + 2)) + return NULL; + + if (domain[0] != '\0') + strcat (domain, "."); + strncat (domain, (const char *)buffer, label_len); + buffer += label_len; + maxlen -= label_len; + } + + return NULL; +} + +static gboolean +process_nduseropt_dnssl (NMIP6Device *device, struct nd_opt_hdr *opt) +{ + size_t opt_len; + struct nd_opt_dnssl *dnssl_opt; + unsigned char *opt_ptr; + time_t now = time (NULL); + GArray *new_domains; + NMIP6DNSSL domain, *cur_domain; + gboolean changed; + guint i; + + opt_len = opt->nd_opt_len; + + if (opt_len < 2) + return FALSE; + + dnssl_opt = (struct nd_opt_dnssl *) opt; + + opt_ptr = (unsigned char *)(dnssl_opt + 1); + opt_len = (opt_len - 1) * 8; /* prefer bytes for later handling */ + + new_domains = g_array_new (FALSE, FALSE, sizeof (NMIP6DNSSL)); + + changed = FALSE; + + /* Pad the DNS server expiry somewhat to give a bit of slack in cases + * where one RA gets lost or something (which can happen on unreliable + * links like wifi where certain types of frames are not retransmitted). + * Note that 0 has special meaning and is therefore not adjusted. + */ + domain.expires = ntohl (dnssl_opt->nd_opt_dnssl_lifetime); + if (domain.expires > 0) + domain.expires += now + 10; + + while (opt_len) { + const char *domain_str; + + domain_str = parse_dnssl_domain (opt_ptr, opt_len); + if (domain_str == NULL) { + nm_log_dbg (LOGD_IP6, "(%s): invalid DNSSL option, parsing aborted", + device->iface); + break; + } + + /* The DNSSL encoding of domains happen to occupy the same size + * as the length of the resulting string, including terminating + * null. */ + opt_ptr += strlen (domain_str) + 1; + opt_len -= strlen (domain_str) + 1; + + /* Ignore empty domains. They're probably just padding... */ + if (domain_str[0] == '\0') + continue; + + for (i = 0; i < device->dnssl_domains->len; i++) { + cur_domain = &(g_array_index (device->dnssl_domains, NMIP6DNSSL, i)); + + if (strcmp (domain_str, cur_domain->domain) != 0) + continue; + + cur_domain->expires = domain.expires; + + if (domain.expires > 0) { + nm_log_dbg (LOGD_IP6, "(%s): refreshing RA-provided domain %s (expires in %d seconds)", + device->iface, domain_str, + domain.expires - now); + break; + } + + nm_log_dbg (LOGD_IP6, "(%s): removing RA-provided domain %s on router request", + device->iface, domain_str); + + g_array_remove_index (device->dnssl_domains, i); + changed = TRUE; + break; + } + + if (domain.expires == 0) + continue; + if (i < device->dnssl_domains->len) + continue; + + nm_log_dbg (LOGD_IP6, "(%s): found RA-provided domain %s (expires in %d seconds)", + device->iface, domain_str, domain.expires - now); + + g_assert (strlen (domain_str) < sizeof (domain.domain)); + strcpy (domain.domain, domain_str); + g_array_append_val (new_domains, domain); + } + + /* New domains must be added in the order they are listed in the + * RA option and before any existing domains. + * + * Note: This is the place to remove domains if we want to cap the + * number of domains. The RFC states that the one to expire + * first of the existing domains should be removed. + */ + if (new_domains->len) { + g_array_prepend_vals (device->dnssl_domains, + new_domains->data, new_domains->len); + changed = TRUE; + } + + g_array_free (new_domains, TRUE); + + /* Timeouts may have changed even if domains didn't */ + set_dnssl_timeout (device); + + return changed; +} + static NMIP6Device * process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg) { @@ -708,6 +930,9 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg) case ND_OPT_RDNSS: changed = process_nduseropt_rdnss (device, opt); break; + case ND_OPT_DNSSL: + changed = process_nduseropt_dnssl (device, opt); + break; } opts_len -= opt->nd_opt_len << 3; @@ -1049,6 +1274,14 @@ nm_ip6_manager_get_ip6_config (NMIP6Manager *manager, int ifindex) nm_ip6_config_add_nameserver (config, &rdnss[i].addr); } + /* Add DNS domains */ + if (device->dnssl_domains) { + NMIP6DNSSL *dnssl = (NMIP6DNSSL *)(device->dnssl_domains->data); + + for (i = 0; i < device->dnssl_domains->len; i++) + nm_ip6_config_add_domain (config, dnssl[i].domain); + } + return config; }