From f2e8870338fbe7056bf511e8d2cd895b0e9335a8 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 18 Jan 2009 23:19:09 -0500 Subject: [PATCH] dhcp: handle classless static routes (bgo #567246) Based on patches by Johan Bilien , nick loeve , and Roy Marples with significant changes for dhclient formatting and test cases. Note that dhclient needs help before it can actually parse classless static routes by adding the following to the dhclient.conf file: option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; --- configure.in | 1 + src/Makefile.am | 29 +- src/dhcp-manager/nm-dhcp-dhclient.c | 132 +++++++ src/dhcp-manager/nm-dhcp-dhcpcd.c | 79 ++++ src/dhcp-manager/nm-dhcp-manager.c | 400 +++++++++++--------- src/dhcp-manager/nm-dhcp-manager.h | 16 +- src/nm-device.c | 25 +- src/tests/Makefile.am | 33 ++ src/tests/test-dhcp-options.c | 549 ++++++++++++++++++++++++++++ 9 files changed, 1065 insertions(+), 199 deletions(-) create mode 100644 src/tests/Makefile.am create mode 100644 src/tests/test-dhcp-options.c diff --git a/configure.in b/configure.in index e75cbdb28f..2e1e3f40d1 100644 --- a/configure.in +++ b/configure.in @@ -496,6 +496,7 @@ AC_CONFIG_FILES([ Makefile include/Makefile src/Makefile +src/tests/Makefile marshallers/Makefile src/named-manager/Makefile src/vpn-manager/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 3c4ce75278..3211bce862 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,7 +5,8 @@ SUBDIRS= \ supplicant-manager \ ppp-manager \ backends \ - dnsmasq-manager + dnsmasq-manager \ + tests INCLUDES = -I${top_srcdir} \ -I${top_srcdir}/include \ @@ -139,19 +140,19 @@ NetworkManager_CPPFLAGS = \ -DARP_DEBUG NetworkManager_LDADD = \ - $(DBUS_LIBS) \ - $(GTHREAD_LIBS) \ - $(HAL_LIBS) \ - $(LIBNL_LIBS) \ - $(top_builddir)/marshallers/libmarshallers.la \ - ./named-manager/libnamed-manager.la \ - ./vpn-manager/libvpn-manager.la \ - ./dhcp-manager/libdhcp-manager.la \ - ./supplicant-manager/libsupplicant-manager.la \ - ./dnsmasq-manager/libdnsmasq-manager.la \ - ./ppp-manager/libppp-manager.la \ - ./backends/libnmbackend.la \ - $(top_builddir)/libnm-util/libnm-util.la + $(DBUS_LIBS) \ + $(GTHREAD_LIBS) \ + $(HAL_LIBS) \ + $(LIBNL_LIBS) \ + $(top_builddir)/marshallers/libmarshallers.la \ + ./named-manager/libnamed-manager.la \ + ./vpn-manager/libvpn-manager.la \ + ./dhcp-manager/libdhcp-manager.la \ + ./supplicant-manager/libsupplicant-manager.la \ + ./dnsmasq-manager/libdnsmasq-manager.la \ + ./ppp-manager/libppp-manager.la \ + ./backends/libnmbackend.la \ + $(top_builddir)/libnm-util/libnm-util.la NetworkManager_LDFLAGS = -rdynamic diff --git a/src/dhcp-manager/nm-dhcp-dhclient.c b/src/dhcp-manager/nm-dhcp-dhclient.c index 846058d80c..552014e0e6 100644 --- a/src/dhcp-manager/nm-dhcp-dhclient.c +++ b/src/dhcp-manager/nm-dhcp-dhclient.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include @@ -288,3 +290,133 @@ out: g_ptr_array_free (dhclient_argv, TRUE); return success; } + +static const char ** +process_rfc3442_route (const char **octets, NMIP4Route **out_route) +{ + const char **o = octets; + int addr_len = 0, i = 0; + long int tmp; + NMIP4Route *route; + char *next_hop; + struct in_addr tmp_addr; + + if (!*o) + return o; /* no prefix */ + + tmp = strtol (*o, NULL, 10); + if (tmp < 0 || tmp > 32) /* 32 == max IP4 prefix length */ + return o; + + route = nm_ip4_route_new (); + nm_ip4_route_set_prefix (route, (guint32) tmp); + o++; + + if (tmp > 0) + addr_len = ((tmp - 1) / 8) + 1; + + /* ensure there's at least the address + next hop left */ + if (g_strv_length ((char **) o) < addr_len + 4) + goto error; + + if (tmp) { + const char *addr[4] = { "0", "0", "0", "0" }; + char *str_addr; + + for (i = 0; i < addr_len; i++) + addr[i] = *o++; + + str_addr = g_strjoin (".", addr[0], addr[1], addr[2], addr[3], NULL); + if (inet_pton (AF_INET, str_addr, &tmp_addr) <= 0) { + g_free (str_addr); + goto error; + } + tmp_addr.s_addr &= nm_utils_ip4_prefix_to_netmask ((guint32) tmp); + nm_ip4_route_set_dest (route, tmp_addr.s_addr); + } + + /* Handle next hop */ + next_hop = g_strjoin (".", o[0], o[1], o[2], o[3], NULL); + if (inet_pton (AF_INET, next_hop, &tmp_addr) <= 0) { + g_free (next_hop); + goto error; + } + nm_ip4_route_set_next_hop (route, tmp_addr.s_addr); + g_free (next_hop); + + *out_route = route; + return o + 4; /* advance to past the next hop */ + +error: + nm_ip4_route_unref (route); + return o; +} + +gboolean +nm_dhcp_client_process_classless_routes (GHashTable *options, + NMIP4Config *ip4_config, + guint32 *gwaddr) +{ + const char *str; + char **octets, **o; + gboolean have_routes = FALSE; + NMIP4Route *route = NULL; + + /* dhclient doesn't have actual support for rfc3442 classless static routes + * upstream. Thus, people resort to defining the option in dhclient.conf + * and using arbitrary formats like so: + * + * option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; + * + * See https://lists.isc.org/pipermail/dhcp-users/2008-December/007629.html + */ + + str = g_hash_table_lookup (options, "new_rfc3442_classless_static_routes"); + /* Microsoft version; same as rfc3442 but with a different option # (249) */ + if (!str) + str = g_hash_table_lookup (options, "new_ms_classless_static_routes"); + + if (!str || !strlen (str)) + return FALSE; + + o = octets = g_strsplit (str, " ", 0); + if (g_strv_length (octets) < 5) { + nm_warning ("Ignoring invalid classless static routes '%s'", str); + goto out; + } + + while (*o) { + route = NULL; + o = (char **) process_rfc3442_route ((const char **) o, &route); + if (!route) { + nm_warning ("Ignoring invalid classless static routes"); + break; + } + + have_routes = TRUE; + if (nm_ip4_route_get_prefix (route) == 0) { + /* gateway passed as classless static route */ + *gwaddr = nm_ip4_route_get_next_hop (route); + nm_ip4_route_unref (route); + } else { + char addr[INET_ADDRSTRLEN + 1]; + char nh[INET_ADDRSTRLEN + 1]; + struct in_addr tmp; + + /* normal route */ + nm_ip4_config_take_route (ip4_config, route); + + tmp.s_addr = nm_ip4_route_get_dest (route); + inet_ntop (AF_INET, &tmp, addr, sizeof (addr)); + tmp.s_addr = nm_ip4_route_get_next_hop (route); + inet_ntop (AF_INET, &tmp, nh, sizeof (nh)); + nm_info (" classless static route %s/%d gw %s", + addr, nm_ip4_route_get_prefix (route), nh); + } + } + +out: + g_strfreev (octets); + return have_routes; +} + diff --git a/src/dhcp-manager/nm-dhcp-dhcpcd.c b/src/dhcp-manager/nm-dhcp-dhcpcd.c index 18d2b51614..62f09688a4 100644 --- a/src/dhcp-manager/nm-dhcp-dhcpcd.c +++ b/src/dhcp-manager/nm-dhcp-dhcpcd.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "nm-dhcp-manager.h" #include "nm-utils.h" @@ -118,3 +120,80 @@ out: g_ptr_array_free (argv, TRUE); return success; } + +gboolean +nm_dhcp_client_process_classless_routes (GHashTable *options, + NMIP4Config *ip4_config, + guint32 *gwaddr) +{ + const char *str; + char **routes, **r; + gboolean have_routes = FALSE; + + /* Classless static routes over-ride any static routes and routers + * provided. We should also check for MS classless static routes as + * they implemented the draft RFC using their own code. + */ + str = g_hash_table_lookup (options, "new_classless_static_routes"); + if (!str) + str = g_hash_table_lookup (options, "new_ms_classless_static_routes"); + + if (!str || !strlen (str)) + return FALSE; + + routes = g_strsplit (str, " ", 0); + if (g_strv_length (routes) == 0) + goto out; + + if ((g_strv_length (routes) % 2) != 0) { + nm_info (" classless static routes provided, but invalid"); + goto out; + } + + for (r = routes; *r; r += 2) { + char *slash; + NMIP4Route *route; + int rt_cidr = 32; + struct in_addr rt_addr; + struct in_addr rt_route; + + slash = strchr(*r, '/'); + if (slash) { + *slash = '\0'; + errno = 0; + rt_cidr = strtol (slash + 1, NULL, 10); + if ((errno == EINVAL) || (errno == ERANGE)) { + nm_warning ("DHCP provided invalid classless static route cidr: '%s'", slash + 1); + continue; + } + } + if (inet_pton (AF_INET, *r, &rt_addr) <= 0) { + nm_warning ("DHCP provided invalid classless static route address: '%s'", *r); + continue; + } + if (inet_pton (AF_INET, *(r + 1), &rt_route) <= 0) { + nm_warning ("DHCP provided invalid classless static route gateway: '%s'", *(r + 1)); + continue; + } + + have_routes = TRUE; + if (rt_cidr == 0 && rt_addr.s_addr == 0) { + /* FIXME: how to handle multiple routers? */ + *gwaddr = rt_addr.s_addr; + } else { + route = nm_ip4_route_new (); + nm_ip4_route_set_dest (route, (guint32) rt_addr.s_addr); + nm_ip4_route_set_prefix (route, rt_cidr); + nm_ip4_route_set_next_hop (route, (guint32) rt_route.s_addr); + + + nm_ip4_config_take_route (ip4_config, route); + nm_info (" classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1)); + } + } + +out: + g_strfreev (routes); + return have_routes; +} + diff --git a/src/dhcp-manager/nm-dhcp-manager.c b/src/dhcp-manager/nm-dhcp-manager.c index cf21ab38fe..44e49a9c67 100644 --- a/src/dhcp-manager/nm-dhcp-manager.c +++ b/src/dhcp-manager/nm-dhcp-manager.c @@ -720,6 +720,209 @@ nm_dhcp_manager_cancel_transaction (NMDHCPManager *manager, nm_dhcp_manager_cancel_transaction_real (device); } +static void +process_classful_routes (GHashTable *options, NMIP4Config *ip4_config) +{ + const char *str; + char **searches, **s; + + str = g_hash_table_lookup (options, "new_static_routes"); + if (!str) + return; + + searches = g_strsplit (str, " ", 0); + if ((g_strv_length (searches) % 2)) { + nm_info (" static routes provided, but invalid"); + goto out; + } + + for (s = searches; *s; s += 2) { + NMIP4Route *route; + struct in_addr rt_addr; + struct in_addr rt_route; + + if (inet_pton (AF_INET, *s, &rt_addr) <= 0) { + nm_warning ("DHCP provided invalid static route address: '%s'", *s); + continue; + } + if (inet_pton (AF_INET, *(s + 1), &rt_route) <= 0) { + nm_warning ("DHCP provided invalid static route gateway: '%s'", *(s + 1)); + continue; + } + + // FIXME: ensure the IP addresse and route are sane + + route = nm_ip4_route_new (); + nm_ip4_route_set_dest (route, (guint32) rt_addr.s_addr); + nm_ip4_route_set_prefix (route, 32); /* 255.255.255.255 */ + nm_ip4_route_set_next_hop (route, (guint32) rt_route.s_addr); + + nm_ip4_config_take_route (ip4_config, route); + nm_info (" static route %s gw %s", *s, *(s + 1)); + } + +out: + g_strfreev (searches); +} + +/* Given a table of DHCP options from the client, convert into an IP4Config */ +NMIP4Config * +nm_dhcp_manager_options_to_ip4_config (const char *iface, GHashTable *options) +{ + NMIP4Config *ip4_config = NULL; + struct in_addr tmp_addr; + NMIP4Address *addr = NULL; + char *str = NULL; + guint32 gwaddr = 0; + gboolean have_classless = FALSE; + + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (options != NULL, NULL); + + ip4_config = nm_ip4_config_new (); + if (!ip4_config) { + nm_warning ("%s: couldn't allocate memory for an IP4Config!", iface); + return NULL; + } + + addr = nm_ip4_address_new (); + if (!addr) { + nm_warning ("%s: couldn't allocate memory for an IP4 Address!", iface); + goto error; + } + + str = g_hash_table_lookup (options, "new_ip_address"); + if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { + nm_ip4_address_set_address (addr, tmp_addr.s_addr); + nm_info (" address %s", str); + } else + goto error; + + str = g_hash_table_lookup (options, "new_subnet_mask"); + if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { + nm_ip4_address_set_prefix (addr, nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr)); + nm_info (" prefix %d (%s)", nm_ip4_address_get_prefix (addr), str); + } + + /* Routes: if the server returns classless static routes, we MUST ignore + * the 'static_routes' option. + */ + have_classless = nm_dhcp_client_process_classless_routes (options, ip4_config, &gwaddr); + if (!have_classless) { + gwaddr = 0; /* Ensure client code doesn't lie */ + process_classful_routes (options, ip4_config); + } + + if (gwaddr) { + char buf[INET_ADDRSTRLEN + 1]; + + inet_ntop (AF_INET, &gwaddr, buf, sizeof (buf)); + nm_info (" gateway %s", buf); + nm_ip4_address_set_gateway (addr, gwaddr); + } else { + /* If the gateway wasn't provided as a classless static route with a + * subnet length of 0, try to find it using the old-style 'routers' option. + */ + str = g_hash_table_lookup (options, "new_routers"); + if (str) { + char **routers = g_strsplit (str, " ", 0); + char **s; + + for (s = routers; *s; s++) { + /* FIXME: how to handle multiple routers? */ + if (inet_pton (AF_INET, *s, &tmp_addr) > 0) { + nm_ip4_address_set_gateway (addr, tmp_addr.s_addr); + nm_info (" gateway %s", *s); + break; + } else + nm_warning ("Ignoring invalid gateway '%s'", *s); + } + g_strfreev (routers); + } + } + + nm_ip4_config_take_address (ip4_config, addr); + addr = NULL; + + str = g_hash_table_lookup (options, "new_host_name"); + if (str) + nm_info (" hostname '%s'", str); + + str = g_hash_table_lookup (options, "new_domain_name_servers"); + if (str) { + char **searches = g_strsplit (str, " ", 0); + char **s; + + for (s = searches; *s; s++) { + if (inet_pton (AF_INET, *s, &tmp_addr) > 0) { + nm_ip4_config_add_nameserver (ip4_config, tmp_addr.s_addr); + nm_info (" nameserver '%s'", *s); + } else + nm_warning ("Ignoring invalid nameserver '%s'", *s); + } + g_strfreev (searches); + } + + str = g_hash_table_lookup (options, "new_domain_name"); + if (str) { + char **domains = g_strsplit (str, " ", 0); + char **s; + + for (s = domains; *s; s++) { + nm_info (" domain name '%s'", *s); + nm_ip4_config_add_domain (ip4_config, *s); + } + g_strfreev (domains); + } + + str = g_hash_table_lookup (options, "new_domain_search"); + if (str) { + char **searches = g_strsplit (str, " ", 0); + char **s; + + for (s = searches; *s; s++) { + nm_info (" domain search '%s'", *s); + nm_ip4_config_add_search (ip4_config, *s); + } + g_strfreev (searches); + } + + str = g_hash_table_lookup (options, "new_netbios_name_servers"); + if (str) { + char **searches = g_strsplit (str, " ", 0); + char **s; + + for (s = searches; *s; s++) { + if (inet_pton (AF_INET, *s, &tmp_addr) > 0) { + nm_ip4_config_add_wins (ip4_config, tmp_addr.s_addr); + nm_info (" wins '%s'", *s); + } else + nm_warning ("Ignoring invalid WINS server '%s'", *s); + } + g_strfreev (searches); + } + + str = g_hash_table_lookup (options, "new_interface_mtu"); + if (str) { + int int_mtu; + + errno = 0; + int_mtu = strtol (str, NULL, 10); + if ((errno == EINVAL) || (errno == ERANGE)) + goto error; + + if (int_mtu) + nm_ip4_config_set_mtu (ip4_config, int_mtu); + } + + return ip4_config; + +error: + if (addr) + nm_ip4_address_unref (addr); + g_object_unref (ip4_config); + return NULL; +} /* * nm_dhcp_manager_get_ip4_config @@ -733,10 +936,6 @@ nm_dhcp_manager_get_ip4_config (NMDHCPManager *manager, { NMDHCPManagerPrivate *priv; NMDHCPDevice *device; - NMIP4Config *ip4_config = NULL; - struct in_addr tmp_addr; - NMIP4Address *addr = NULL; - char *str = NULL; g_return_val_if_fail (NM_IS_DHCP_MANAGER (manager), NULL); g_return_val_if_fail (iface != NULL, NULL); @@ -754,179 +953,23 @@ nm_dhcp_manager_get_ip4_config (NMDHCPManager *manager, return NULL; } - ip4_config = nm_ip4_config_new (); - if (!ip4_config) { - nm_warning ("%s: couldn't allocate memory for an IP4Config!", device->iface); - return NULL; - } - - addr = nm_ip4_address_new (); - if (!addr) { - nm_warning ("%s: couldn't allocate memory for an IP4 Address!", device->iface); - goto error; - } - - str = g_hash_table_lookup (device->options, "new_ip_address"); - if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { - nm_ip4_address_set_address (addr, tmp_addr.s_addr); - nm_info (" address %s", str); - } else - goto error; - - str = g_hash_table_lookup (device->options, "new_subnet_mask"); - if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { - nm_ip4_address_set_prefix (addr, nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr)); - nm_info (" prefix %d (%s)", nm_ip4_address_get_prefix (addr), str); - } - - str = g_hash_table_lookup (device->options, "new_routers"); - if (str) { - char **routers = g_strsplit (str, " ", 0); - char **s; - - for (s = routers; *s; s++) { - /* FIXME: how to handle multiple routers? */ - if (inet_pton (AF_INET, *s, &tmp_addr) > 0) { - nm_ip4_address_set_gateway (addr, tmp_addr.s_addr); - nm_info (" gateway %s", *s); - break; - } else - nm_warning ("Ignoring invalid gateway '%s'", *s); - } - g_strfreev (routers); - } - - nm_ip4_config_take_address (ip4_config, addr); - addr = NULL; - - str = g_hash_table_lookup (device->options, "new_host_name"); - if (str) - nm_info (" hostname '%s'", str); - - str = g_hash_table_lookup (device->options, "new_domain_name_servers"); - if (str) { - char **searches = g_strsplit (str, " ", 0); - char **s; - - for (s = searches; *s; s++) { - if (inet_pton (AF_INET, *s, &tmp_addr) > 0) { - nm_ip4_config_add_nameserver (ip4_config, tmp_addr.s_addr); - nm_info (" nameserver '%s'", *s); - } else - nm_warning ("Ignoring invalid nameserver '%s'", *s); - } - g_strfreev (searches); - } - - str = g_hash_table_lookup (device->options, "new_domain_name"); - if (str) { - char **domains = g_strsplit (str, " ", 0); - char **s; - - for (s = domains; *s; s++) { - nm_info (" domain name '%s'", *s); - nm_ip4_config_add_domain (ip4_config, *s); - } - g_strfreev (domains); - } - - str = g_hash_table_lookup (device->options, "new_domain_search"); - if (str) { - char **searches = g_strsplit (str, " ", 0); - char **s; - - for (s = searches; *s; s++) { - nm_info (" domain search '%s'", *s); - nm_ip4_config_add_search (ip4_config, *s); - } - g_strfreev (searches); - } - - str = g_hash_table_lookup (device->options, "new_netbios_name_servers"); - if (str) { - char **searches = g_strsplit (str, " ", 0); - char **s; - - for (s = searches; *s; s++) { - if (inet_pton (AF_INET, *s, &tmp_addr) > 0) { - nm_ip4_config_add_wins (ip4_config, tmp_addr.s_addr); - nm_info (" wins '%s'", *s); - } else - nm_warning ("Ignoring invalid WINS server '%s'", *s); - } - g_strfreev (searches); - } - - str = g_hash_table_lookup (device->options, "new_static_routes"); - if (str) { - char **searches = g_strsplit (str, " ", 0); - - if ((g_strv_length (searches) % 2) == 0) { - char **s; - - for (s = searches; *s; s += 2) { - NMIP4Route *route; - struct in_addr rt_addr; - struct in_addr rt_route; - - if (inet_pton (AF_INET, *s, &rt_addr) <= 0) { - nm_warning ("DHCP provided invalid static route address: '%s'", *s); - continue; - } - if (inet_pton (AF_INET, *(s + 1), &rt_route) <= 0) { - nm_warning ("DHCP provided invalid static route gateway: '%s'", *(s + 1)); - continue; - } - - // FIXME: ensure the IP addresse and route are sane - - route = nm_ip4_route_new (); - nm_ip4_route_set_dest (route, (guint32) rt_addr.s_addr); - nm_ip4_route_set_prefix (route, 32); /* 255.255.255.255 */ - nm_ip4_route_set_next_hop (route, (guint32) rt_route.s_addr); - - nm_ip4_config_take_route (ip4_config, route); - nm_info (" static route %s gw %s", *s, *(s + 1)); - } - } else { - nm_info (" static routes provided, but invalid"); - } - g_strfreev (searches); - } - - str = g_hash_table_lookup (device->options, "new_interface_mtu"); - if (str) { - int int_mtu; - - errno = 0; - int_mtu = strtol (str, NULL, 10); - if ((errno == EINVAL) || (errno == ERANGE)) - goto error; - - if (int_mtu) - nm_ip4_config_set_mtu (ip4_config, int_mtu); - } - - return ip4_config; - -error: - if (addr) - g_free (addr); - - g_object_unref (ip4_config); - - return NULL; + return nm_dhcp_manager_options_to_ip4_config (iface, device->options); } #define NEW_TAG "new_" #define OLD_TAG "old_" +typedef struct { + GHFunc func; + gpointer user_data; +} Dhcp4ForeachInfo; + static void -copy_dhcp4_config_option (gpointer key, - gpointer value, - gpointer user_data) +iterate_dhcp4_config_option (gpointer key, + gpointer value, + gpointer user_data) { - NMDHCP4Config *config = NM_DHCP4_CONFIG (user_data); + Dhcp4ForeachInfo *info = (Dhcp4ForeachInfo *) user_data; char *tmp_key = NULL; const char **p; static const char *filter_options[] = { @@ -947,21 +990,23 @@ copy_dhcp4_config_option (gpointer key, else tmp_key = g_strdup ((const char *) key); - nm_dhcp4_config_add_option (config, tmp_key, (const char *) value); + (*info->func) ((gpointer) tmp_key, value, info->user_data); g_free (tmp_key); } gboolean -nm_dhcp_manager_set_dhcp4_config (NMDHCPManager *self, - const char *iface, - NMDHCP4Config *config) +nm_dhcp_manager_foreach_dhcp4_option (NMDHCPManager *self, + const char *iface, + GHFunc func, + gpointer user_data) { NMDHCPManagerPrivate *priv; NMDHCPDevice *device; + Dhcp4ForeachInfo info = { NULL, NULL }; g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), FALSE); g_return_val_if_fail (iface != NULL, FALSE); - g_return_val_if_fail (config != NULL, FALSE); + g_return_val_if_fail (func != NULL, FALSE); priv = NM_DHCP_MANAGER_GET_PRIVATE (self); @@ -976,8 +1021,9 @@ nm_dhcp_manager_set_dhcp4_config (NMDHCPManager *self, return FALSE; } - nm_dhcp4_config_reset (config); - g_hash_table_foreach (device->options, copy_dhcp4_config_option, config); + info.func = func; + info.user_data = user_data; + g_hash_table_foreach (device->options, iterate_dhcp4_config_option, &info); return TRUE; } diff --git a/src/dhcp-manager/nm-dhcp-manager.h b/src/dhcp-manager/nm-dhcp-manager.h index 291e1b0193..8a8713c906 100644 --- a/src/dhcp-manager/nm-dhcp-manager.h +++ b/src/dhcp-manager/nm-dhcp-manager.h @@ -96,13 +96,21 @@ void nm_dhcp_manager_cancel_transaction (NMDHCPManager *manager, NMIP4Config * nm_dhcp_manager_get_ip4_config (NMDHCPManager *manager, const char *iface); NMDHCPState nm_dhcp_manager_get_state_for_device (NMDHCPManager *manager, const char *iface); -gboolean nm_dhcp_manager_set_dhcp4_config (NMDHCPManager *manager, +gboolean nm_dhcp_manager_foreach_dhcp4_option (NMDHCPManager *self, const char *iface, - NMDHCP4Config *config); - -gboolean nm_dhcp_manager_process_signal (NMDHCPManager *manager, DBusMessage *message); + GHFunc func, + gpointer user_data); +/* The following are implemented by the DHCP client backends */ gboolean nm_dhcp_client_start (NMDHCPDevice *device, NMSettingIP4Config *s_ip4); void nm_dhcp_client_stop (const char *iface, pid_t pid); +gboolean nm_dhcp_client_process_classless_routes (GHashTable *options, + NMIP4Config *ip4_config, + guint32 *gwaddr); + +/* Test functions */ +NMIP4Config *nm_dhcp_manager_options_to_ip4_config (const char *iface, + GHashTable *options); + #endif /* NM_DHCP_MANAGER_H */ diff --git a/src/nm-device.c b/src/nm-device.c index 7dc4047598..bb24b8b8af 100644 --- a/src/nm-device.c +++ b/src/nm-device.c @@ -1020,6 +1020,14 @@ nm_device_new_ip4_shared_config (NMDevice *self, NMDeviceStateReason *reason) return config; } +static void +dhcp4_add_option_cb (gpointer key, gpointer value, gpointer user_data) +{ + nm_dhcp4_config_add_option (NM_DHCP4_CONFIG (user_data), + (const char *) key, + (const char *) value); +} + static NMActStageReturn real_act_stage4_get_ip4_config (NMDevice *self, NMIP4Config **config, @@ -1048,7 +1056,12 @@ real_act_stage4_get_ip4_config (NMDevice *self, if (*config) { nm_utils_merge_ip4_config (*config, s_ip4); - nm_dhcp_manager_set_dhcp4_config (priv->dhcp_manager, ip_iface, priv->dhcp4_config); + nm_dhcp4_config_reset (priv->dhcp4_config); + nm_dhcp_manager_foreach_dhcp4_option (priv->dhcp_manager, + ip_iface, + dhcp4_add_option_cb, + priv->dhcp4_config); + /* Notify of new DHCP4 config */ g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_DHCP4_CONFIG); } else @@ -1757,9 +1770,13 @@ handle_dhcp_lease_change (NMDevice *device) g_object_set_data (G_OBJECT (req), NM_ACT_REQUEST_IP4_CONFIG, config); - if (nm_device_set_ip4_config (device, config, &reason)) - nm_dhcp_manager_set_dhcp4_config (priv->dhcp_manager, ip_iface, priv->dhcp4_config); - else { + if (nm_device_set_ip4_config (device, config, &reason)) { + nm_dhcp4_config_reset (priv->dhcp4_config); + nm_dhcp_manager_foreach_dhcp4_option (priv->dhcp_manager, + ip_iface, + dhcp4_add_option_cb, + priv->dhcp4_config); + } else { nm_warning ("Failed to update IP4 config in response to DHCP event."); nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason); } diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am new file mode 100644 index 0000000000..ae069095a5 --- /dev/null +++ b/src/tests/Makefile.am @@ -0,0 +1,33 @@ +INCLUDES = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/libnm-util \ + -I$(top_srcdir)/src/dhcp-manager \ + -I$(top_srcdir)/marshallers \ + -I$(top_srcdir)/src + +noinst_PROGRAMS = test-dhcp-options + +test_dhcp_options_SOURCES = \ + test-dhcp-options.c \ + ../nm-ip4-config.c \ + ../nm-dbus-manager.c + +test_dhcp_options_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(LIBNL_CFLAGS) + +test_dhcp_options_LDADD = \ + $(GTHREAD_LIBS) \ + $(DBUS_LIBS) \ + $(LIBNL_LIBS) \ + $(top_builddir)/libnm-util/libnm-util.la \ + $(top_builddir)/src/dhcp-manager/libdhcp-manager.la + +if WITH_TESTS + +check-local: test-dhcp-options + $(abs_builddir)/test-dhcp-options + +endif + diff --git a/src/tests/test-dhcp-options.c b/src/tests/test-dhcp-options.c new file mode 100644 index 0000000000..e1782237f9 --- /dev/null +++ b/src/tests/test-dhcp-options.c @@ -0,0 +1,549 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2008 Red Hat, Inc. + * + */ + +#include +#include +#include +#include +#include + +#include "nm-test-helpers.h" +#include + +#include "nm-dhcp-manager.h" + +typedef struct { + const char *name; + const char *value; +} Option; + +static GHashTable * +fill_table (Option *test_options, GHashTable *table) +{ + Option *opt; + + if (!table) + table = g_hash_table_new (g_str_hash, g_str_equal); + for (opt = test_options; opt->name; opt++) + g_hash_table_insert (table, (gpointer) opt->name, (gpointer) opt->value); + return table; +} + +static Option generic_options[] = { + { "new_subnet_mask", "255.255.255.0" }, + { "new_ip_address", "192.168.1.106" }, + { "new_network_number", "192.168.1.0" }, + { "interface", "eth0" }, + { "reason", "BOUND" }, + { "new_expiry", "1232324877" }, + { "new_dhcp_lease_time", "3600" }, + { "new_dhcp_server_identifier", "192.168.1.1" }, + { "new_routers", "192.168.1.1" }, + { "new_domain_name_servers", "216.254.95.2 216.231.41.2" }, + { "new_dhcp_message_type", "5" }, + { "new_broadcast_address", "192.168.1.255" }, + { "new_domain_search", "foobar.com blah.foobar.com" }, + { "new_host_name", "nmreallywhipsthe" }, + { "new_domain_name", "lamasass.com" }, + { "new_interface_mtu", "987" }, + { "new_static_routes", "10.1.1.5 10.1.1.1 100.99.88.56 10.1.1.1" }, + { NULL, NULL } +}; + +static void +test_generic_options (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + NMIP4Address *addr; + NMIP4Route *route; + struct in_addr tmp; + const char *expected_addr = "192.168.1.106"; + const char *expected_gw = "192.168.1.1"; + const char *expected_dns1 = "216.254.95.2"; + const char *expected_dns2 = "216.231.41.2"; + const char *expected_search1 = "foobar.com"; + const char *expected_search2 = "blah.foobar.com"; + const char *expected_route1_dest = "10.1.1.5"; + const char *expected_route1_gw = "10.1.1.1"; + const char *expected_route2_dest = "100.99.88.56"; + const char *expected_route2_gw = "10.1.1.1"; + + options = fill_table (generic_options, NULL); + ip4_config = nm_dhcp_manager_options_to_ip4_config ("eth0", options); + ASSERT (ip4_config != NULL, + "dhcp-generic", "failed to parse DHCP4 options"); + + /* IP4 address */ + ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1, + "dhcp-generic", "unexpected number of IP addresses"); + addr = nm_ip4_config_get_address (ip4_config, 0); + + ASSERT (inet_pton (AF_INET, expected_addr, &tmp) > 0, + "dhcp-generic", "couldn't convert expected IP address"); + ASSERT (nm_ip4_address_get_address (addr) == tmp.s_addr, + "dhcp-generic", "unexpected IP address"); + + ASSERT (nm_ip4_address_get_prefix (addr) == 24, + "dhcp-generic", "unexpected IP address prefix length"); + + /* Gateway */ + ASSERT (inet_pton (AF_INET, expected_gw, &tmp) > 0, + "dhcp-generic", "couldn't convert expected IP gateway"); + ASSERT (nm_ip4_address_get_gateway (addr) == tmp.s_addr, + "dhcp-generic", "unexpected IP gateway"); + + ASSERT (nm_ip4_config_get_ptp_address (ip4_config) == 0, + "dhcp-generic", "unexpected PTP address"); + + ASSERT (nm_ip4_config_get_num_wins (ip4_config) == 0, + "dhcp-generic", "unexpected number of WINS servers"); + + ASSERT (nm_ip4_config_get_mtu (ip4_config) == 987, + "dhcp-generic", "unexpected MTU"); + + /* Domain searches */ + ASSERT (nm_ip4_config_get_num_searches (ip4_config) == 2, + "dhcp-generic", "unexpected number of domain searches"); + ASSERT (strcmp (nm_ip4_config_get_search (ip4_config, 0), expected_search1) == 0, + "dhcp-generic", "unexpected domain search #1"); + ASSERT (strcmp (nm_ip4_config_get_search (ip4_config, 1), expected_search2) == 0, + "dhcp-generic", "unexpected domain search #2"); + + /* DNS servers */ + ASSERT (nm_ip4_config_get_num_nameservers (ip4_config) == 2, + "dhcp-generic", "unexpected number of domain name servers"); + ASSERT (inet_pton (AF_INET, expected_dns1, &tmp) > 0, + "dhcp-generic", "couldn't convert expected DNS server address #1"); + ASSERT (nm_ip4_config_get_nameserver (ip4_config, 0) == tmp.s_addr, + "dhcp-generic", "unexpected domain name server #1"); + ASSERT (inet_pton (AF_INET, expected_dns2, &tmp) > 0, + "dhcp-generic", "couldn't convert expected DNS server address #2"); + ASSERT (nm_ip4_config_get_nameserver (ip4_config, 1) == tmp.s_addr, + "dhcp-generic", "unexpected domain name server #2"); + + /* Routes */ + ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, + "dhcp-generic", "unexpected number of routes"); + + /* Route #1 */ + route = nm_ip4_config_get_route (ip4_config, 0); + ASSERT (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0, + "dhcp-generic", "couldn't convert expected route destination #1"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-generic", "unexpected route #1 destination"); + + ASSERT (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0, + "dhcp-generic", "couldn't convert expected route next hop #1"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-generic", "unexpected route #1 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 32, + "dhcp-generic", "unexpected route #1 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-generic", "unexpected route #1 metric"); + + /* Route #2 */ + route = nm_ip4_config_get_route (ip4_config, 1); + ASSERT (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0, + "dhcp-generic", "couldn't convert expected route destination #2"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-generic", "unexpected route #2 destination"); + + ASSERT (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0, + "dhcp-generic", "couldn't convert expected route next hop #2"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-generic", "unexpected route #2 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 32, + "dhcp-generic", "unexpected route #2 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-generic", "unexpected route #2 metric"); + + g_hash_table_destroy (options); +} + +static Option wins_options[] = { + { "new_netbios_name_servers", "63.12.199.5 150.4.88.120" }, + { NULL, NULL } +}; + +static void +test_wins_options (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + NMIP4Address *addr; + struct in_addr tmp; + const char *expected_wins1 = "63.12.199.5"; + const char *expected_wins2 = "150.4.88.120"; + + options = fill_table (generic_options, NULL); + options = fill_table (wins_options, options); + + ip4_config = nm_dhcp_manager_options_to_ip4_config ("eth0", options); + ASSERT (ip4_config != NULL, + "dhcp-wins", "failed to parse DHCP4 options"); + + /* IP4 address */ + ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1, + "dhcp-wins", "unexpected number of IP addresses"); + addr = nm_ip4_config_get_address (ip4_config, 0); + + ASSERT (nm_ip4_config_get_num_wins (ip4_config) == 2, + "dhcp-wins", "unexpected number of WINS servers"); + ASSERT (inet_pton (AF_INET, expected_wins1, &tmp) > 0, + "dhcp-wins", "couldn't convert expected WINS server address #1"); + ASSERT (nm_ip4_config_get_wins (ip4_config, 0) == tmp.s_addr, + "dhcp-wins", "unexpected WINS server #1"); + ASSERT (inet_pton (AF_INET, expected_wins2, &tmp) > 0, + "dhcp-wins", "couldn't convert expected WINS server address #1"); + ASSERT (nm_ip4_config_get_wins (ip4_config, 1) == tmp.s_addr, + "dhcp-wins", "unexpected WINS server #1"); + + g_hash_table_destroy (options); +} + +static Option classless_routes_options[] = { + /* For dhclient */ + { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 8 10 10 17 66 41" }, + /* For dhcpcd */ + { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41" }, + { NULL, NULL } +}; + +static void +test_classless_static_routes (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + NMIP4Route *route; + struct in_addr tmp; + const char *expected_route1_dest = "192.168.10.0"; + const char *expected_route1_gw = "192.168.1.1"; + const char *expected_route2_dest = "10.0.0.0"; + const char *expected_route2_gw = "10.17.66.41"; + + options = fill_table (generic_options, NULL); + options = fill_table (classless_routes_options, options); + + ip4_config = nm_dhcp_manager_options_to_ip4_config ("eth0", options); + ASSERT (ip4_config != NULL, + "dhcp-rfc3442", "failed to parse DHCP4 options"); + + /* IP4 routes */ + ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, + "dhcp-rfc3442", "unexpected number of IP routes"); + + /* Route #1 */ + route = nm_ip4_config_get_route (ip4_config, 0); + ASSERT (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0, + "dhcp-rfc3442", "couldn't convert expected route destination #1"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-rfc3442", "unexpected route #1 destination"); + + ASSERT (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0, + "dhcp-rfc3442", "couldn't convert expected route next hop #1"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-rfc3442", "unexpected route #1 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 24, + "dhcp-rfc3442", "unexpected route #1 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-rfc3442", "unexpected route #1 metric"); + + /* Route #2 */ + route = nm_ip4_config_get_route (ip4_config, 1); + ASSERT (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0, + "dhcp-rfc3442", "couldn't convert expected route destination #2"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-rfc3442", "unexpected route #2 destination"); + + ASSERT (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0, + "dhcp-rfc3442", "couldn't convert expected route next hop #2"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-rfc3442", "unexpected route #2 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 8, + "dhcp-rfc3442", "unexpected route #2 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-rfc3442", "unexpected route #2 metric"); + + g_hash_table_destroy (options); +} + +static Option invalid_classless_routes1[] = { + /* For dhclient */ + { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 45 10 17 66 41" }, + /* For dhcpcd */ + { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.adfadf/44 10.17.66.41" }, + { NULL, NULL } +}; + +static void +test_invalid_classless_routes1 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + NMIP4Route *route; + struct in_addr tmp; + const char *expected_route1_dest = "192.168.10.0"; + const char *expected_route1_gw = "192.168.1.1"; + + options = fill_table (generic_options, NULL); + options = fill_table (invalid_classless_routes1, options); + + ip4_config = nm_dhcp_manager_options_to_ip4_config ("eth0", options); + ASSERT (ip4_config != NULL, + "dhcp-rfc3442-invalid-1", "failed to parse DHCP4 options"); + + /* IP4 routes */ + ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, + "dhcp-rfc3442-invalid-1", "unexpected number of IP routes"); + + /* Route #1 */ + route = nm_ip4_config_get_route (ip4_config, 0); + ASSERT (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0, + "dhcp-rfc3442-invalid-1", "couldn't convert expected route destination #1"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-1", "unexpected route #1 destination"); + + ASSERT (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0, + "dhcp-rfc3442-invalid-1", "couldn't convert expected route next hop #1"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-1", "unexpected route #1 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 24, + "dhcp-rfc3442-invalid-1", "unexpected route #1 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-rfc3442-invalid-1", "unexpected route #1 metric"); + + g_hash_table_destroy (options); +} + +static Option invalid_classless_routes2[] = { + /* For dhclient */ + { "new_rfc3442_classless_static_routes", "45 10 17 66 41 24 192 168 10 192 168 1 1" }, + /* For dhcpcd */ + { "new_classless_static_routes", "10.0.adfadf/44 10.17.66.41 192.168.10.0/24 192.168.1.1" }, + { NULL, NULL } +}; + +static void +test_invalid_classless_routes2 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + NMIP4Route *route; + struct in_addr tmp; + const char *expected_route1_dest = "10.1.1.5"; + const char *expected_route1_gw = "10.1.1.1"; + const char *expected_route2_dest = "100.99.88.56"; + const char *expected_route2_gw = "10.1.1.1"; + + options = fill_table (generic_options, NULL); + options = fill_table (invalid_classless_routes2, options); + + ip4_config = nm_dhcp_manager_options_to_ip4_config ("eth0", options); + ASSERT (ip4_config != NULL, + "dhcp-rfc3442-invalid-2", "failed to parse DHCP4 options"); + + /* Test falling back to old-style static routes if the classless static + * routes are invalid. + */ + + /* Routes */ + ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, + "dhcp-rfc3442-invalid-2", "unexpected number of routes"); + + /* Route #1 */ + route = nm_ip4_config_get_route (ip4_config, 0); + ASSERT (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0, + "dhcp-rfc3442-invalid-2", "couldn't convert expected route destination #1"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-2", "unexpected route #1 destination"); + + ASSERT (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0, + "dhcp-rfc3442-invalid-2", "couldn't convert expected route next hop #1"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-2", "unexpected route #1 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 32, + "dhcp-rfc3442-invalid-2", "unexpected route #1 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-rfc3442-invalid-2", "unexpected route #1 metric"); + + /* Route #2 */ + route = nm_ip4_config_get_route (ip4_config, 1); + ASSERT (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0, + "dhcp-rfc3442-invalid-2", "couldn't convert expected route destination #2"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-2", "unexpected route #2 destination"); + + ASSERT (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0, + "dhcp-rfc3442-invalid-2", "couldn't convert expected route next hop #2"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-2", "unexpected route #2 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 32, + "dhcp-rfc3442-invalid-2", "unexpected route #2 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-rfc3442-invalid-2", "unexpected route #2 metric"); + + g_hash_table_destroy (options); +} + +static Option invalid_classless_routes3[] = { + /* For dhclient */ + { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 32 128 10 17 66 41" }, + /* For dhcpcd */ + { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 128/32 10.17.66.41" }, + { NULL, NULL } +}; + +static void +test_invalid_classless_routes3 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + NMIP4Route *route; + struct in_addr tmp; + const char *expected_route1_dest = "192.168.10.0"; + const char *expected_route1_gw = "192.168.1.1"; + + options = fill_table (generic_options, NULL); + options = fill_table (invalid_classless_routes3, options); + + ip4_config = nm_dhcp_manager_options_to_ip4_config ("eth0", options); + ASSERT (ip4_config != NULL, + "dhcp-rfc3442-invalid-3", "failed to parse DHCP4 options"); + + /* IP4 routes */ + ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, + "dhcp-rfc3442-invalid-3", "unexpected number of IP routes"); + + /* Route #1 */ + route = nm_ip4_config_get_route (ip4_config, 0); + ASSERT (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0, + "dhcp-rfc3442-invalid-3", "couldn't convert expected route destination #1"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-3", "unexpected route #1 destination"); + + ASSERT (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0, + "dhcp-rfc3442-invalid-3", "couldn't convert expected route next hop #1"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-rfc3442-invalid-3", "unexpected route #1 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 24, + "dhcp-rfc3442-invalid-3", "unexpected route #1 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-rfc3442-invalid-3", "unexpected route #1 metric"); + + g_hash_table_destroy (options); +} + +static Option gw_in_classless_routes[] = { + /* For dhclient */ + { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 0 192 2 3 4" }, + /* For dhcpcd */ + { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 0.0.0.0/0 192.2.3.4" }, + { NULL, NULL } +}; + +static void +test_gateway_in_classless_routes (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + NMIP4Address *addr; + NMIP4Route *route; + struct in_addr tmp; + const char *expected_route1_dest = "192.168.10.0"; + const char *expected_route1_gw = "192.168.1.1"; + const char *expected_gateway = "192.2.3.4"; + + options = fill_table (generic_options, NULL); + options = fill_table (gw_in_classless_routes, options); + + ip4_config = nm_dhcp_manager_options_to_ip4_config ("eth0", options); + ASSERT (ip4_config != NULL, + "dhcp-rfc3442-gateway", "failed to parse DHCP4 options"); + + /* IP4 routes */ + ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, + "dhcp-rfc3442-gateway", "unexpected number of IP routes"); + + /* Route #1 */ + route = nm_ip4_config_get_route (ip4_config, 0); + ASSERT (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0, + "dhcp-rfc3442-gateway", "couldn't convert expected route destination #1"); + ASSERT (nm_ip4_route_get_dest (route) == tmp.s_addr, + "dhcp-rfc3442-gateway", "unexpected route #1 destination"); + + ASSERT (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0, + "dhcp-rfc3442-gateway", "couldn't convert expected route next hop #1"); + ASSERT (nm_ip4_route_get_next_hop (route) == tmp.s_addr, + "dhcp-rfc3442-gateway", "unexpected route #1 next hop"); + + ASSERT (nm_ip4_route_get_prefix (route) == 24, + "dhcp-rfc3442-gateway", "unexpected route #1 prefix"); + ASSERT (nm_ip4_route_get_metric (route) == 0, + "dhcp-rfc3442-gateway", "unexpected route #1 metric"); + + /* Address */ + ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1, + "dhcp-rfc3442-gateway", "unexpected number of IP addresses"); + addr = nm_ip4_config_get_address (ip4_config, 0); + ASSERT (inet_pton (AF_INET, expected_gateway, &tmp) > 0, + "dhcp-rfc3442-gateway", "couldn't convert expected IP gateway"); + ASSERT (nm_ip4_address_get_gateway (addr) == tmp.s_addr, + "dhcp-rfc3442-gateway", "unexpected IP gateway"); + + g_hash_table_destroy (options); +} + +int main (int argc, char **argv) +{ + GError *error = NULL; + DBusGConnection *bus; + char *basename; + + g_type_init (); + bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + + if (!nm_utils_init (&error)) + FAIL ("nm-utils-init", "failed to initialize libnm-util: %s", error->message); + + /* The tests */ + test_generic_options (); + test_wins_options (); + test_classless_static_routes (); + test_invalid_classless_routes1 (); + test_invalid_classless_routes2 (); + test_invalid_classless_routes3 (); + test_gateway_in_classless_routes (); + + basename = g_path_get_basename (argv[0]); + fprintf (stdout, "%s: SUCCESS\n", basename); + g_free (basename); + return 0; +} +