diff options
Diffstat (limited to 'src/dhcp-manager/nm-dhcp-dhclient-utils.c')
| -rw-r--r-- | src/dhcp-manager/nm-dhcp-dhclient-utils.c | 313 |
1 files changed, 276 insertions, 37 deletions
diff --git a/src/dhcp-manager/nm-dhcp-dhclient-utils.c b/src/dhcp-manager/nm-dhcp-dhclient-utils.c index db4bf7e8..8527e6c0 100644 --- a/src/dhcp-manager/nm-dhcp-dhclient-utils.c +++ b/src/dhcp-manager/nm-dhcp-dhclient-utils.c @@ -25,6 +25,9 @@ #include <ctype.h> #include "nm-dhcp-dhclient-utils.h" +#include "nm-ip4-config.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" #define CLIENTID_TAG "send dhcp-client-identifier" #define CLIENTID_FORMAT CLIENTID_TAG " \"%s\"; # added by NetworkManager" @@ -68,33 +71,32 @@ add_hostname (GString *str, const char *format, const char *hostname) } static void -add_ip4_config (GString *str, NMSettingIP4Config *s_ip4, const char *hostname) +add_ip4_config (GString *str, const char *dhcp_client_id, const char *hostname) { - if (s_ip4) { - const char *tmp; - - tmp = nm_setting_ip4_config_get_dhcp_client_id (s_ip4); - if (tmp) { - gboolean is_octets = TRUE; - const char *p = tmp; - - while (*p) { - if (!g_ascii_isxdigit (*p) && (*p != ':')) { - is_octets = FALSE; - break; - } - p++; + if (dhcp_client_id) { + gboolean is_octets = TRUE; + int i = 0; + + while (dhcp_client_id[i]) { + if ((i % 3) != 2 && !g_ascii_isxdigit (dhcp_client_id[i])) { + is_octets = FALSE; + break; } - - /* If the client ID is just hex digits and : then don't use quotes, - * because dhclient expects either a quoted ASCII string, or a byte - * array formated as hex octets separated by : - */ - if (is_octets) - g_string_append_printf (str, CLIENTID_FORMAT_OCTETS "\n", tmp); - else - g_string_append_printf (str, CLIENTID_FORMAT "\n", tmp); + if ((i % 3) == 2 && dhcp_client_id[i] != ':') { + is_octets = FALSE; + break; + } + i++; } + + /* If the client ID is just hex digits and : then don't use quotes, + * because dhclient expects either a quoted ASCII string, or a byte + * array formated as hex octets separated by : + */ + if (is_octets) + g_string_append_printf (str, CLIENTID_FORMAT_OCTETS "\n", dhcp_client_id); + else + g_string_append_printf (str, CLIENTID_FORMAT "\n", dhcp_client_id); } add_hostname (str, HOSTNAME4_FORMAT "\n", hostname); @@ -113,7 +115,7 @@ add_ip4_config (GString *str, NMSettingIP4Config *s_ip4, const char *hostname) } static void -add_ip6_config (GString *str, NMSettingIP6Config *s_ip6, const char *hostname) +add_ip6_config (GString *str, const char *hostname) { add_hostname (str, HOSTNAME6_FORMAT "\n", hostname); g_string_append (str, @@ -125,9 +127,8 @@ add_ip6_config (GString *str, NMSettingIP6Config *s_ip6, const char *hostname) char * nm_dhcp_dhclient_create_config (const char *interface, gboolean is_ip6, - NMSettingIP4Config *s_ip4, - NMSettingIP6Config *s_ip6, - guint8 *anycast_addr, + const char *dhcp_client_id, + GByteArray *anycast_addr, const char *hostname, const char *orig_path, const char *orig_contents) @@ -155,9 +156,7 @@ nm_dhcp_dhclient_create_config (const char *interface, /* Override config file "dhcp-client-id" and use one from the * connection. */ - if ( s_ip4 - && nm_setting_ip4_config_get_dhcp_client_id (s_ip4) - && !strncmp (p, CLIENTID_TAG, strlen (CLIENTID_TAG))) + if (dhcp_client_id && !strncmp (p, CLIENTID_TAG, strlen (CLIENTID_TAG))) continue; /* Override config file hostname and use one from the connection */ @@ -222,13 +221,13 @@ nm_dhcp_dhclient_create_config (const char *interface, g_string_append_c (new_contents, '\n'); if (is_ip6) { - add_ip6_config (new_contents, s_ip6, hostname); + add_ip6_config (new_contents, hostname); add_also_request (alsoreq, "dhcp6.name-servers"); add_also_request (alsoreq, "dhcp6.domain-search"); add_also_request (alsoreq, "dhcp6.client-id"); add_also_request (alsoreq, "dhcp6.server-id"); } else { - add_ip4_config (new_contents, s_ip4, hostname); + add_ip4_config (new_contents, dhcp_client_id, hostname); add_also_request (alsoreq, "rfc3442-classless-static-routes"); add_also_request (alsoreq, "ms-classless-static-routes"); add_also_request (alsoreq, "static-routes"); @@ -247,17 +246,22 @@ nm_dhcp_dhclient_create_config (const char *interface, g_string_append_c (new_contents, '\n'); - if (anycast_addr) { + if (anycast_addr && anycast_addr->len == 6) { + const guint8 *p_anycast_addr = anycast_addr->data; + g_string_append_printf (new_contents, "interface \"%s\" {\n" " initial-interval 1; \n" " anycast-mac ethernet %02x:%02x:%02x:%02x:%02x:%02x;\n" "}\n", interface, - anycast_addr[0], anycast_addr[1], - anycast_addr[2], anycast_addr[3], - anycast_addr[4], anycast_addr[5]); + p_anycast_addr[0], p_anycast_addr[1], + p_anycast_addr[2], p_anycast_addr[3], + p_anycast_addr[4], p_anycast_addr[5]); } + /* Finally, assert that anycast_addr was unset or a 48 bit mac address. */ + g_return_val_if_fail (!anycast_addr || anycast_addr->len == 6, g_string_free (new_contents, FALSE)); + return g_string_free (new_contents, FALSE); } @@ -423,3 +427,238 @@ nm_dhcp_dhclient_save_duid (const char *leasefile, return success; } +static void +add_lease_option (GHashTable *hash, char *line) +{ + char *spc; + size_t len; + + /* Find the space after "option" */ + spc = strchr (line, ' '); + if (!spc) + return; + + /* Find the option tag's data, which is after the second space */ + if (g_str_has_prefix (line, "option ")) { + while (g_ascii_isspace (*spc)) + spc++; + spc = strchr (spc + 1, ' '); + if (!spc) + return; + } + + /* Split the line at the space */ + *spc = '\0'; + spc++; + + /* Kill the ';' at the end of the line, if any */ + len = strlen (spc); + if (*(spc + len - 1) == ';') + *(spc + len - 1) = '\0'; + + /* Strip leading quote */ + while (g_ascii_isspace (*spc)) + spc++; + if (*spc == '"') + spc++; + + /* Strip trailing quote */ + len = strlen (spc); + if (len > 0 && spc[len - 1] == '"') + spc[len - 1] = '\0'; + + if (spc[0]) + g_hash_table_insert (hash, g_strdup (line), g_strdup (spc)); +} + +#define LEASE_INVALID G_MININT64 +static GTimeSpan +lease_validity_span (const char *str_expire, GDateTime *now) +{ + GDateTime *expire = NULL; + struct tm expire_tm; + GTimeSpan span; + + g_return_val_if_fail (now != NULL, LEASE_INVALID); + g_return_val_if_fail (str_expire != NULL, LEASE_INVALID); + + /* Skip initial number (day of week?) */ + if (!isdigit (*str_expire++)) + return LEASE_INVALID; + if (!isspace (*str_expire++)) + return LEASE_INVALID; + /* Read lease expiration (in UTC) */ + if (!strptime (str_expire, "%t%Y/%m/%d %H:%M:%S", &expire_tm)) + return LEASE_INVALID; + + expire = g_date_time_new_utc (expire_tm.tm_year + 1900, + expire_tm.tm_mon + 1, + expire_tm.tm_mday, + expire_tm.tm_hour, + expire_tm.tm_min, + expire_tm.tm_sec); + if (!expire) + return LEASE_INVALID; + + span = g_date_time_difference (expire, now); + g_date_time_unref (expire); + + /* GDateTime only supports a range of less then 10000 years, so span can + * not overflow or be equal to LEASE_INVALID */ + return span; +} + +/** + * nm_dhcp_dhclient_read_lease_ip_configs: + * @iface: the interface name to match leases with + * @contents: the contents of a dhclient leasefile + * @ipv6: whether to read IPv4 or IPv6 leases + * @now: the current UTC date/time; pass %NULL to automatically use current + * UTC time. Testcases may need a different value for 'now' + * + * Reads dhclient leases from @contents and parses them into either + * #NMIP4Config or #NMIP6Config objects depending on the value of @ipv6. + * + * Returns: a #GSList of #NMIP4Config objects (if @ipv6 is %FALSE) or a list of + * #NMIP6Config objects (if @ipv6 is %TRUE) containing the lease data. + */ +GSList * +nm_dhcp_dhclient_read_lease_ip_configs (const char *iface, + const char *contents, + gboolean ipv6, + GDateTime *now) +{ + GSList *parsed = NULL, *iter, *leases = NULL; + char **line, **split = NULL; + GHashTable *hash = NULL; + gint32 now_monotonic_ts; + + g_return_val_if_fail (contents != NULL, NULL); + + split = g_strsplit_set (contents, "\n\r", -1); + if (!split) + return NULL; + + for (line = split; line && *line; line++) { + *line = g_strstrip (*line); + + if (*line[0] == '#') { + /* Comment */ + } else if (!strcmp (*line, "}")) { + /* Lease ends */ + parsed = g_slist_append (parsed, hash); + hash = NULL; + } else if (!strcmp (*line, "lease {")) { + /* Beginning of a new lease */ + if (hash) { + /* Ignore malformed lease that doesn't end before new one starts */ + g_hash_table_destroy (hash); + } + + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + } else if (hash && strlen (*line)) + add_lease_option (hash, *line); + } + g_strfreev (split); + + /* Check if the last lease in the file was properly ended */ + if (hash) { + /* Ignore malformed lease that doesn't end before new one starts */ + g_hash_table_destroy (hash); + hash = NULL; + } + + if (now) + g_date_time_ref (now); + else + now = g_date_time_new_now_utc (); + now_monotonic_ts = nm_utils_get_monotonic_timestamp_s (); + + for (iter = parsed; iter; iter = g_slist_next (iter)) { + NMIP4Config *ip4; + NMPlatformIP4Address address; + const char *value; + GTimeSpan expiry; + guint32 tmp, gw = 0; + + hash = iter->data; + + /* Make sure this lease is for the interface we want */ + value = g_hash_table_lookup (hash, "interface"); + if (!value || strcmp (value, iface)) + continue; + + value = g_hash_table_lookup (hash, "expire"); + if (!value) + continue; + expiry = lease_validity_span (value, now); + if (expiry == LEASE_INVALID) + continue; + + /* scale expiry to seconds (and CLAMP into the range of guint32) */ + expiry = CLAMP (expiry / G_TIME_SPAN_SECOND, 0, NM_PLATFORM_LIFETIME_PERMANENT-1); + if (expiry <= 0) { + /* the address is already expired. Don't even add it. */ + continue; + } + + memset (&address, 0, sizeof (address)); + + /* IP4 address */ + value = g_hash_table_lookup (hash, "fixed-address"); + if (!value) + continue; + if (!inet_pton (AF_INET, value, &address.address)) + continue; + + /* Gateway */ + value = g_hash_table_lookup (hash, "option routers"); + if (!value) + continue; + if (!inet_pton (AF_INET, value, &gw)) + continue; + + /* Netmask */ + value = g_hash_table_lookup (hash, "option subnet-mask"); + if (value && inet_pton (AF_INET, value, &tmp)) + address.plen = nm_utils_ip4_netmask_to_prefix (tmp); + + /* Get default netmask for the IP according to appropriate class. */ + if (!address.plen) + address.plen = nm_utils_ip4_get_default_prefix (address.address); + + address.timestamp = now_monotonic_ts; + address.lifetime = address.preferred = expiry; + address.source = NM_PLATFORM_SOURCE_DHCP; + + ip4 = nm_ip4_config_new (); + nm_ip4_config_add_address (ip4, &address); + nm_ip4_config_set_gateway (ip4, gw); + + value = g_hash_table_lookup (hash, "option domain-name-servers"); + if (value) { + char **dns, **dns_iter; + + dns = g_strsplit_set (value, ",", -1); + for (dns_iter = dns; dns_iter && *dns_iter; dns_iter++) { + if (inet_pton (AF_INET, *dns_iter, &tmp)) + nm_ip4_config_add_nameserver (ip4, tmp); + } + if (dns) + g_strfreev (dns); + } + + value = g_hash_table_lookup (hash, "option domain-name"); + if (value && value[0]) + nm_ip4_config_add_domain (ip4, value); + + /* FIXME: static routes */ + + leases = g_slist_append (leases, ip4); + } + + g_date_time_unref (now); + g_slist_free_full (parsed, (GDestroyNotify) g_hash_table_destroy); + return leases; +} + |