diff options
Diffstat (limited to 'src/ip6-manager/nm-ip6-manager.c')
| -rw-r--r-- | src/ip6-manager/nm-ip6-manager.c | 415 |
1 files changed, 343 insertions, 72 deletions
diff --git a/src/ip6-manager/nm-ip6-manager.c b/src/ip6-manager/nm-ip6-manager.c index f6f6127c..7ffa2d1f 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 */ @@ -285,7 +299,7 @@ set_rdnss_timeout (NMIP6Device *device) nm_log_dbg (LOGD_IP6, "(%s): removing expired RA-provided nameserver %s", device->iface, buf); } - g_array_remove_index_fast (device->rdnss_servers, i--); + g_array_remove_index (device->rdnss_servers, i--); continue; } @@ -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,13 +638,258 @@ 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; uint16_t nd_opt_rdnss_reserved1; uint32_t nd_opt_rdnss_lifetime; /* 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) +{ + size_t opt_len; + struct nd_opt_rdnss *rdnss_opt; + time_t now = time (NULL); + struct in6_addr *addr; + GArray *new_servers; + NMIP6RDNSS server, *cur_server; + gboolean changed = FALSE; + guint i; + + opt_len = opt->nd_opt_len; + + if (opt_len < 3 || (opt_len & 1) == 0) + return FALSE; + + rdnss_opt = (struct nd_opt_rdnss *) opt; + + new_servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS)); + + /* 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. + */ + server.expires = ntohl (rdnss_opt->nd_opt_rdnss_lifetime); + if (server.expires > 0) + server.expires += now + 10; + + for (addr = (struct in6_addr *) (rdnss_opt + 1); opt_len >= 2; addr++, opt_len -= 2) { + char buf[INET6_ADDRSTRLEN + 1]; + + if (!inet_ntop (AF_INET6, addr, buf, sizeof (buf))) + strcpy(buf, "[invalid]"); + + for (i = 0; i < device->rdnss_servers->len; i++) { + cur_server = &(g_array_index (device->rdnss_servers, NMIP6RDNSS, i)); + + if (!IN6_ARE_ADDR_EQUAL (addr, &cur_server->addr)) + continue; + + cur_server->expires = server.expires; + + if (server.expires > 0) { + nm_log_dbg (LOGD_IP6, "(%s): refreshing RA-provided nameserver %s (expires in %d seconds)", + device->iface, buf, + server.expires - now); + break; + } + + nm_log_dbg (LOGD_IP6, "(%s): removing RA-provided nameserver %s on router request", + device->iface, buf); + + g_array_remove_index (device->rdnss_servers, i); + changed = TRUE; + break; + } + + if (server.expires == 0) + continue; + if (i < device->rdnss_servers->len) + continue; + + nm_log_dbg (LOGD_IP6, "(%s): found RA-provided nameserver %s (expires in %d seconds)", + device->iface, buf, server.expires - now); + + server.addr = *addr; + g_array_append_val (new_servers, server); + } + + /* New servers must be added in the order they are listed in the + * RA option and before any existing servers. + * + * Note: This is the place to remove servers if we want to cap the + * number of resolvers. The RFC states that the one to expire + * first of the existing servers should be removed. + */ + if (new_servers->len) { + g_array_prepend_vals (device->rdnss_servers, + new_servers->data, new_servers->len); + changed = TRUE; + } + + g_array_free (new_servers, TRUE); + + /* Timeouts may have changed even if IPs didn't */ + set_rdnss_timeout (device); + + 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) @@ -583,13 +897,8 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg) NMIP6Device *device; struct nduseroptmsg *ndmsg; struct nd_opt_hdr *opt; - guint opts_len, i; - time_t now = time (NULL); - struct nd_opt_rdnss *rdnss_opt; - struct in6_addr *addr; - GArray *servers; - NMIP6RDNSS server, *sa, *sb; - gboolean changed; + guint opts_len; + gboolean changed = FALSE; nm_log_dbg (LOGD_IP6, "processing netlink nduseropt message"); @@ -608,8 +917,6 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg) return NULL; } - servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS)); - opt = (struct nd_opt_hdr *) (ndmsg + 1); opts_len = ndmsg->nduseropt_opts_len; @@ -619,66 +926,19 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg) if (nd_opt_len == 0 || opts_len < (nd_opt_len << 3)) break; - if (opt->nd_opt_type != ND_OPT_RDNSS) - goto next; - - if (nd_opt_len < 3 || (nd_opt_len & 1) == 0) - goto next; - - rdnss_opt = (struct nd_opt_rdnss *) opt; - - /* 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). - */ - server.expires = now + ntohl (rdnss_opt->nd_opt_rdnss_lifetime) + 10; - - for (addr = (struct in6_addr *) (rdnss_opt + 1); nd_opt_len >= 2; addr++, nd_opt_len -= 2) { - char buf[INET6_ADDRSTRLEN + 1]; - - if (inet_ntop (AF_INET6, addr, buf, sizeof (buf))) { - nm_log_dbg (LOGD_IP6, "(%s): found RA-provided nameserver %s (expires in %d seconds)", - device->iface, buf, - ntohl (rdnss_opt->nd_opt_rdnss_lifetime)); - } - - server.addr = *addr; - g_array_append_val (servers, server); + switch (opt->nd_opt_type) { + case ND_OPT_RDNSS: + changed = process_nduseropt_rdnss (device, opt); + break; + case ND_OPT_DNSSL: + changed = process_nduseropt_dnssl (device, opt); + break; } - next: opts_len -= opt->nd_opt_len << 3; opt = (struct nd_opt_hdr *) ((uint8_t *) opt + (opt->nd_opt_len << 3)); } - /* See if anything (other than expiration time) changed */ - if (servers->len != device->rdnss_servers->len) - changed = TRUE; - else { - for (i = 0; i < servers->len; i++) { - sa = &(g_array_index (servers, NMIP6RDNSS, i)); - sb = &(g_array_index (device->rdnss_servers, NMIP6RDNSS, i)); - if (IN6_ARE_ADDR_EQUAL (&sa->addr, &sb->addr) == FALSE) { - changed = TRUE; - break; - } - } - changed = FALSE; - } - - if (changed) { - nm_log_dbg (LOGD_IP6, "(%s): RA-provided nameservers changed", device->iface); - } - - /* Always copy in new servers (even if unchanged) to get their updated - * expiration times. - */ - g_array_free (device->rdnss_servers, TRUE); - device->rdnss_servers = servers; - - /* Timeouts may have changed even if IPs didn't */ - set_rdnss_timeout (device); - if (changed) return device; else @@ -791,7 +1051,7 @@ netlink_notification (NMNetlinkMonitor *monitor, struct nl_msg *msg, gpointer us } } -void +gboolean nm_ip6_manager_prepare_interface (NMIP6Manager *manager, int ifindex, NMSettingIP6Config *s_ip6, @@ -801,16 +1061,17 @@ nm_ip6_manager_prepare_interface (NMIP6Manager *manager, NMIP6Device *device; const char *method = NULL; - g_return_if_fail (NM_IS_IP6_MANAGER (manager)); - g_return_if_fail (ifindex > 0); + g_return_val_if_fail (NM_IS_IP6_MANAGER (manager), FALSE); + g_return_val_if_fail (ifindex > 0, FALSE); priv = NM_IP6_MANAGER_GET_PRIVATE (manager); device = nm_ip6_device_new (manager, ifindex); - g_return_if_fail (device != NULL); - g_return_if_fail ( strchr (device->iface, '/') == NULL - && strcmp (device->iface, "all") != 0 - && strcmp (device->iface, "default") != 0); + g_return_val_if_fail (device != NULL, FALSE); + g_return_val_if_fail ( strchr (device->iface, '/') == NULL + && strcmp (device->iface, "all") != 0 + && strcmp (device->iface, "default") != 0, + FALSE); if (s_ip6) method = nm_setting_ip6_config_get_method (s_ip6); @@ -825,6 +1086,8 @@ nm_ip6_manager_prepare_interface (NMIP6Manager *manager, device->target_state = NM_IP6_DEVICE_GOT_ADDRESS; nm_utils_do_sysctl (accept_ra_path, "1\n"); } + + return TRUE; } static gboolean @@ -1014,6 +1277,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; } |