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; +} +