1    	/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2    	/* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
3    	 *
4    	 * This program is free software; you can redistribute it and/or modify
5    	 * it under the terms of the GNU General Public License as published by
6    	 * the Free Software Foundation; either version 2, or (at your option)
7    	 * any later version.
8    	 *
9    	 * This program is distributed in the hope that it will be useful,
10   	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   	 * GNU General Public License for more details.
13   	 *
14   	 * You should have received a copy of the GNU General Public License along
15   	 * with this program; if not, write to the Free Software Foundation, Inc.,
16   	 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17   	 *
18   	 * Copyright (C) 2008 - 2011 Red Hat, Inc.
19   	 *
20   	 */
21   	
22   	#include <glib.h>
23   	#include <dbus/dbus-glib.h>
24   	#include <netinet/in.h>
25   	#include <arpa/inet.h>
26   	#include <string.h>
27   	
28   	#include "nm-test-helpers.h"
29   	#include <nm-utils.h>
30   	
31   	#include "nm-dhcp-manager.h"
32   	
33   	typedef struct {
34   		const char *name;
35   		const char *value;
36   	} Option;
37   	
38   	static void
39   	destroy_gvalue (gpointer data)
40   	{
41   		GValue *value = (GValue *) data;
42   	
43   		g_value_unset (value);
44   		g_slice_free (GValue, value);
45   	}
46   	
47   	static GValue *
48   	string_to_byte_array_gvalue (const char *str)
49   	{
50   		GByteArray *array;
51   		GValue *val;
52   	
53   		array = g_byte_array_sized_new (strlen (str));
54   		g_byte_array_append (array, (const guint8 *) str, strlen (str));
55   	
56   		val = g_slice_new0 (GValue);
57   		g_value_init (val, DBUS_TYPE_G_UCHAR_ARRAY);
58   		g_value_take_boxed (val, array);
59   	
60   		return val;
61   	}
62   	
63   	static GHashTable *
64   	fill_table (Option *test_options, GHashTable *table)
65   	{
66   		Option *opt;
67   	
68   		if (!table)
69   			table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, destroy_gvalue);
70   		for (opt = test_options; opt->name; opt++) {
71   			g_hash_table_insert (table,
72   			                     (gpointer) opt->name,
73   			                     string_to_byte_array_gvalue (opt->value));
74   		}
75   		return table;
76   	}
77   	
78   	static Option generic_options[] = {
79   		{ "new_subnet_mask",            "255.255.255.0" },
80   		{ "new_ip_address",             "192.168.1.106" },
81   		{ "new_network_number",         "192.168.1.0" },
82   		{ "interface",                  "eth0" },
83   		{ "reason",                     "BOUND" },
84   		{ "new_expiry",                 "1232324877" },
85   		{ "new_dhcp_lease_time",        "3600" },
86   		{ "new_dhcp_server_identifier", "192.168.1.1" },
87   		{ "new_routers",                "192.168.1.1" },
88   		{ "new_domain_name_servers",    "216.254.95.2 216.231.41.2" },
89   		{ "new_dhcp_message_type",      "5" },
90   		{ "new_broadcast_address",      "192.168.1.255" },
91   		{ "new_domain_search",          "foobar.com blah.foobar.com" },
92   		{ "new_host_name",              "nmreallywhipsthe" },
93   		{ "new_domain_name",            "lamasass.com" },
94   		{ "new_interface_mtu",          "987" },
95   		{ "new_static_routes",          "10.1.1.5 10.1.1.1 100.99.88.56 10.1.1.1" },
96   		{ NULL, NULL }
97   	};
98   	
99   	static void
100  	test_generic_options (const char *client)
101  	{
102  		GHashTable *options;
103  		NMIP4Config *ip4_config;
104  		const NMPlatformIP4Address *address;
105  		const NMPlatformIP4Route *route;
106  		guint32 tmp;
107  		const char *expected_addr = "192.168.1.106";
108  		const char *expected_gw = "192.168.1.1";
109  		const char *expected_dns1 = "216.254.95.2";
110  		const char *expected_dns2 = "216.231.41.2";
111  		const char *expected_search1 = "foobar.com";
112  		const char *expected_search2 = "blah.foobar.com";
113  		const char *expected_route1_dest = "10.1.1.5";
114  		const char *expected_route1_gw = "10.1.1.1";
115  		const char *expected_route2_dest = "100.99.88.56";
116  		const char *expected_route2_gw = "10.1.1.1";
117  	
118  		options = fill_table (generic_options, NULL);
119  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
120  		ASSERT (ip4_config != NULL,
121  		        "dhcp-generic", "failed to parse DHCP4 options");
122  	
123  		/* IP4 address */
124  		ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1,
125  		        "dhcp-generic", "unexpected number of IP addresses");
126  		address = nm_ip4_config_get_address (ip4_config, 0);
127  	
128  		ASSERT (inet_pton (AF_INET, expected_addr, &tmp) > 0,
129  		        "dhcp-generic", "couldn't convert expected IP address");
130  		ASSERT (address->address == tmp,
131  		        "dhcp-generic", "unexpected IP address");
132  	
133  		ASSERT (address->plen == 24,
134  		        "dhcp-generic", "unexpected IP address prefix length");
135  	
136  		/* Gateway */
137  		ASSERT (inet_pton (AF_INET, expected_gw, &tmp) > 0,
138  		        "dhcp-generic", "couldn't convert expected IP gateway");
139  		ASSERT (nm_ip4_config_get_gateway (ip4_config) == tmp,
140  		        "dhcp-generic", "unexpected IP gateway");
141  	
142  		ASSERT (nm_ip4_config_get_ptp_address (ip4_config) == 0,
143  		        "dhcp-generic", "unexpected PTP address");
144  	
145  		ASSERT (nm_ip4_config_get_num_wins (ip4_config) == 0,
146  		        "dhcp-generic", "unexpected number of WINS servers");
147  	
148  		ASSERT (nm_ip4_config_get_mtu (ip4_config) == 987,
149  		        "dhcp-generic", "unexpected MTU");
150  	
151  		/* Domain searches */
152  		ASSERT (nm_ip4_config_get_num_searches (ip4_config) == 2,
153  		        "dhcp-generic", "unexpected number of domain searches");
154  		ASSERT (strcmp (nm_ip4_config_get_search (ip4_config, 0), expected_search1) == 0,
155  		        "dhcp-generic", "unexpected domain search #1");
156  		ASSERT (strcmp (nm_ip4_config_get_search (ip4_config, 1), expected_search2) == 0,
157  		        "dhcp-generic", "unexpected domain search #2");
158  	
159  		/* DNS servers */
160  		ASSERT (nm_ip4_config_get_num_nameservers (ip4_config) == 2,
161  		        "dhcp-generic", "unexpected number of domain name servers");
162  		ASSERT (inet_pton (AF_INET, expected_dns1, &tmp) > 0,
163  		        "dhcp-generic", "couldn't convert expected DNS server address #1");
164  		ASSERT (nm_ip4_config_get_nameserver (ip4_config, 0) == tmp,
165  		        "dhcp-generic", "unexpected domain name server #1");
166  		ASSERT (inet_pton (AF_INET, expected_dns2, &tmp) > 0,
167  		        "dhcp-generic", "couldn't convert expected DNS server address #2");
168  		ASSERT (nm_ip4_config_get_nameserver (ip4_config, 1) == tmp,
169  		        "dhcp-generic", "unexpected domain name server #2");
170  	
171  		/* Routes */
172  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2,
173  		        "dhcp-generic", "unexpected number of routes");
174  	
175  		/* Route #1 */
176  		route = nm_ip4_config_get_route (ip4_config, 0);
177  		ASSERT (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0,
178  		        "dhcp-generic", "couldn't convert expected route destination #1");
179  		ASSERT (route->network == tmp,
180  		        "dhcp-generic", "unexpected route #1 destination");
181  	
182  		ASSERT (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0,
183  		        "dhcp-generic", "couldn't convert expected route next hop #1");
184  		ASSERT (route->gateway == tmp,
185  		        "dhcp-generic", "unexpected route #1 next hop");
186  	
187  		ASSERT (route->plen == 32,
188  		        "dhcp-generic", "unexpected route #1 prefix");
189  		ASSERT (route->metric == 0,
190  		        "dhcp-generic", "unexpected route #1 metric");
191  	
192  		/* Route #2 */
193  		route = nm_ip4_config_get_route (ip4_config, 1);
194  		ASSERT (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0,
195  		        "dhcp-generic", "couldn't convert expected route destination #2");
196  		ASSERT (route->network == tmp,
197  		        "dhcp-generic", "unexpected route #2 destination");
198  	
199  		ASSERT (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0,
200  		        "dhcp-generic", "couldn't convert expected route next hop #2");
201  		ASSERT (route->gateway == tmp,
202  		        "dhcp-generic", "unexpected route #2 next hop");
203  	
204  		ASSERT (route->plen == 32,
205  		        "dhcp-generic", "unexpected route #2 prefix");
206  		ASSERT (route->metric == 0,
207  		        "dhcp-generic", "unexpected route #2 metric");
208  	
209  		g_hash_table_destroy (options);
210  	}
211  	
212  	static Option wins_options[] = {
213  		{ "new_netbios_name_servers", "63.12.199.5 150.4.88.120" },
214  		{ NULL, NULL }
215  	};
216  	
217  	static void
218  	test_wins_options (const char *client)
219  	{
220  		GHashTable *options;
221  		NMIP4Config *ip4_config;
222  		const NMPlatformIP4Address *address;
223  		guint32 tmp;
224  		const char *expected_wins1 = "63.12.199.5";
225  		const char *expected_wins2 = "150.4.88.120";
226  	
227  		options = fill_table (generic_options, NULL);
228  		options = fill_table (wins_options, options);
229  	
230  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
231  		ASSERT (ip4_config != NULL,
232  		        "dhcp-wins", "failed to parse DHCP4 options");
233  	
234  		/* IP4 address */
235  		ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1,
236  		        "dhcp-wins", "unexpected number of IP addresses");
(1) Event returned_pointer: Pointer "address" returned by "nm_ip4_config_get_address(ip4_config, 0U)" is never used.
237  		address = nm_ip4_config_get_address (ip4_config, 0);
238  	
239  		ASSERT (nm_ip4_config_get_num_wins (ip4_config) == 2,
240  		        "dhcp-wins", "unexpected number of WINS servers");
241  		ASSERT (inet_pton (AF_INET, expected_wins1, &tmp) > 0,
242  		        "dhcp-wins", "couldn't convert expected WINS server address #1");
243  		ASSERT (nm_ip4_config_get_wins (ip4_config, 0) == tmp,
244  		        "dhcp-wins", "unexpected WINS server #1");
245  		ASSERT (inet_pton (AF_INET, expected_wins2, &tmp) > 0,
246  		        "dhcp-wins", "couldn't convert expected WINS server address #1");
247  		ASSERT (nm_ip4_config_get_wins (ip4_config, 1) == tmp,
248  		        "dhcp-wins", "unexpected WINS server #1");
249  	
250  		g_hash_table_destroy (options);
251  	}
252  	
253  	static void
254  	ip4_test_route (const char *test,
255  	                NMIP4Config *ip4_config,
256  	                guint route_num,
257  	                const char *expected_dest,
258  	                const char *expected_gw,
259  	                guint expected_prefix)
260  	{
261  		const NMPlatformIP4Route *route;
262  		guint32 tmp;
263  	
264  		route = nm_ip4_config_get_route (ip4_config, route_num);
265  		ASSERT (inet_pton (AF_INET, expected_dest, &tmp) > 0,
266  		        test, "couldn't convert expected route destination #1");
267  		ASSERT (route->network == tmp,
268  		        test, "unexpected route %d destination", route_num + 1);
269  	
270  		ASSERT (inet_pton (AF_INET, expected_gw, &tmp) > 0,
271  		        test, "couldn't convert expected route next hop %d",
272  		        route_num + 1);
273  		ASSERT (route->gateway == tmp,
274  		        test, "unexpected route %d next hop", route_num + 1);
275  	
276  		ASSERT (route->plen == expected_prefix,
277  		        test, "unexpected route %d prefix", route_num + 1);
278  		ASSERT (route->metric == 0,
279  		        test, "unexpected route %d metric", route_num + 1);
280  	}
281  	
282  	static void
283  	ip4_test_gateway (const char *test,
284  	                  NMIP4Config *ip4_config,
285  	                  const char *expected_gw)
286  	{
287  		guint32 tmp;
288  	
289  		ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1,
290  		        test, "unexpected number of IP addresses");
291  		ASSERT (inet_pton (AF_INET, expected_gw, &tmp) > 0,
292  		        test, "couldn't convert expected IP gateway");
293  		ASSERT (nm_ip4_config_get_gateway (ip4_config) == tmp,
294  		        test, "unexpected IP gateway");
295  	}
296  	
297  	static void
298  	test_classless_static_routes_1 (const char *client)
299  	{
300  		GHashTable *options;
301  		NMIP4Config *ip4_config;
302  		const char *expected_route1_dest = "192.168.10.0";
303  		const char *expected_route1_gw = "192.168.1.1";
304  		const char *expected_route2_dest = "10.0.0.0";
305  		const char *expected_route2_gw = "10.17.66.41";
306  		static Option data[] = {
307  			/* dhclient custom format */
308  			{ "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 8 10 10 17 66 41" },
309  			{ NULL, NULL }
310  		};
311  	
312  		options = fill_table (generic_options, NULL);
313  		options = fill_table (data, options);
314  	
315  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
316  		ASSERT (ip4_config != NULL,
317  		        "dhcp-classless-1", "failed to parse DHCP4 options");
318  	
319  		/* IP4 routes */
320  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2,
321  		        "dhcp-classless-1", "unexpected number of IP routes");
322  		ip4_test_route ("dhcp-classless-1", ip4_config, 0,
323  		                expected_route1_dest, expected_route1_gw, 24);
324  		ip4_test_route ("dhcp-classless-1", ip4_config, 1,
325  		                expected_route2_dest, expected_route2_gw, 8);
326  	
327  		g_hash_table_destroy (options);
328  	}
329  	
330  	static void
331  	test_classless_static_routes_2 (const char *client)
332  	{
333  		GHashTable *options;
334  		NMIP4Config *ip4_config;
335  		const char *expected_route1_dest = "192.168.10.0";
336  		const char *expected_route1_gw = "192.168.1.1";
337  		const char *expected_route2_dest = "10.0.0.0";
338  		const char *expected_route2_gw = "10.17.66.41";
339  		static Option data[] = {
340  			/* dhcpcd format */
341  			{ "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41" },
342  			{ NULL, NULL }
343  		};
344  	
345  		options = fill_table (generic_options, NULL);
346  		options = fill_table (data, options);
347  	
348  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
349  		ASSERT (ip4_config != NULL,
350  		        "dhcp-classless-2", "failed to parse DHCP4 options");
351  	
352  		/* IP4 routes */
353  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2,
354  		        "dhcp-classless-2", "unexpected number of IP routes");
355  		ip4_test_route ("dhcp-classless-2", ip4_config, 0,
356  		                expected_route1_dest, expected_route1_gw, 24);
357  		ip4_test_route ("dhcp-classless-2", ip4_config, 1,
358  		                expected_route2_dest, expected_route2_gw, 8);
359  	
360  		g_hash_table_destroy (options);
361  	}
362  	
363  	static void
364  	test_fedora_dhclient_classless_static_routes (const char *client)
365  	{
366  		GHashTable *options;
367  		NMIP4Config *ip4_config;
368  		const char *expected_route1_dest = "129.210.177.128";
369  		const char *expected_route1_gw = "192.168.0.113";
370  		const char *expected_route2_dest = "2.0.0.0";
371  		const char *expected_route2_gw = "10.34.255.6";
372  		const char *expected_gateway = "192.168.0.113";
373  		static Option data[] = {
374  			/* Fedora dhclient format */
375  			{ "new_classless_static_routes", "0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6" },
376  			{ NULL, NULL }
377  		};
378  	
379  		options = fill_table (generic_options, NULL);
380  		options = fill_table (data, options);
381  	
382  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
383  		ASSERT (ip4_config != NULL,
384  		        "dhcp-fedora-dhclient-classless", "failed to parse DHCP4 options");
385  	
386  		/* IP4 routes */
387  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2,
388  		        "dhcp-fedora-dhclient-classless", "unexpected number of IP routes");
389  		ip4_test_route ("dhcp-fedora-dhclient-classless", ip4_config, 0,
390  		                expected_route1_dest, expected_route1_gw, 25);
391  		ip4_test_route ("dhcp-fedora-dhclient-classless", ip4_config, 1,
392  		                expected_route2_dest, expected_route2_gw, 7);
393  	
394  		/* Gateway */
395  		ip4_test_gateway ("dhcp-fedora-dhclient-classless", ip4_config, expected_gateway);
396  	
397  		g_hash_table_destroy (options);
398  	}
399  	
400  	static void
401  	test_dhclient_invalid_classless_routes_1 (const char *client)
402  	{
403  		GHashTable *options;
404  		NMIP4Config *ip4_config;
405  		const char *expected_route1_dest = "192.168.10.0";
406  		const char *expected_route1_gw = "192.168.1.1";
407  		static Option data[] = {
408  			/* dhclient format */
409  			{ "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 45 10 17 66 41" },
410  			{ NULL, NULL }
411  		};
412  	
413  		options = fill_table (generic_options, NULL);
414  		options = fill_table (data, options);
415  	
416  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
417  		ASSERT (ip4_config != NULL,
418  		        "dhcp-dhclient-classless-invalid-1", "failed to parse DHCP4 options");
419  	
420  		/* IP4 routes */
421  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1,
422  		        "dhcp-dhclient-classless-invalid-1", "unexpected number of IP routes");
423  	
424  		ip4_test_route ("dhcp-dhclient-classless-invalid-1", ip4_config, 0,
425  		                expected_route1_dest, expected_route1_gw, 24);
426  	
427  		g_hash_table_destroy (options);
428  	}
429  	
430  	static void
431  	test_dhcpcd_invalid_classless_routes_1 (const char *client)
432  	{
433  		GHashTable *options;
434  		NMIP4Config *ip4_config;
435  		const char *expected_route1_dest = "10.1.1.5";
436  		const char *expected_route1_gw = "10.1.1.1";
437  		const char *expected_route2_dest = "100.99.88.56";
438  		const char *expected_route2_gw = "10.1.1.1";
439  		static Option data[] = {
440  			/* dhcpcd format */
441  			{ "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.adfadf/44 10.17.66.41" },
442  			{ NULL, NULL }
443  		};
444  	
445  		options = fill_table (generic_options, NULL);
446  		options = fill_table (data, options);
447  	
448  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
449  		ASSERT (ip4_config != NULL,
450  		        "dhcp-dhcpcd-classless-invalid-1", "failed to parse DHCP4 options");
451  	
452  		/* Test falling back to old-style static routes if the classless static
453  		 * routes are invalid.
454  		 */
455  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2,
456  		        "dhcp-dhcpcdp-classless-invalid-1", "unexpected number of routes");
457  		ip4_test_route ("dhcp-dhcpcdp-classless-invalid-1", ip4_config, 0,
458  		                expected_route1_dest, expected_route1_gw, 32);
459  		ip4_test_route ("dhcp-dhcpcdp-classless-invalid-1", ip4_config, 1,
460  		                expected_route2_dest, expected_route2_gw, 32);
461  	
462  		g_hash_table_destroy (options);
463  	}
464  	
465  	static void
466  	test_dhclient_invalid_classless_routes_2 (const char *client)
467  	{
468  		GHashTable *options;
469  		NMIP4Config *ip4_config;
470  		const char *expected_route1_dest = "10.1.1.5";
471  		const char *expected_route1_gw = "10.1.1.1";
472  		const char *expected_route2_dest = "100.99.88.56";
473  		const char *expected_route2_gw = "10.1.1.1";
474  		static Option data[] = {
475  			{ "new_rfc3442_classless_static_routes", "45 10 17 66 41 24 192 168 10 192 168 1 1" },
476  			{ NULL, NULL }
477  		};
478  	
479  		options = fill_table (generic_options, NULL);
480  		options = fill_table (data, options);
481  	
482  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
483  		ASSERT (ip4_config != NULL,
484  		        "dhcp-dhclient-classless-invalid-2", "failed to parse DHCP4 options");
485  	
486  		/* Test falling back to old-style static routes if the classless static
487  		 * routes are invalid.
488  		 */
489  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2,
490  		        "dhcp-dhclient-classless-invalid-2", "unexpected number of routes");
491  		ip4_test_route ("dhcp-dhclient-classless-invalid-2", ip4_config, 0,
492  		                expected_route1_dest, expected_route1_gw, 32);
493  		ip4_test_route ("dhcp-dhclient-classless-invalid-2", ip4_config, 1,
494  		                expected_route2_dest, expected_route2_gw, 32);
495  	
496  		g_hash_table_destroy (options);
497  	}
498  	
499  	static void
500  	test_dhcpcd_invalid_classless_routes_2 (const char *client)
501  	{
502  		GHashTable *options;
503  		NMIP4Config *ip4_config;
504  		const char *expected_route1_dest = "10.1.1.5";
505  		const char *expected_route1_gw = "10.1.1.1";
506  		const char *expected_route2_dest = "100.99.88.56";
507  		const char *expected_route2_gw = "10.1.1.1";
508  		static Option data[] = {
509  			{ "new_classless_static_routes", "10.0.adfadf/44 10.17.66.41 192.168.10.0/24 192.168.1.1" },
510  			{ NULL, NULL }
511  		};
512  	
513  		options = fill_table (generic_options, NULL);
514  		options = fill_table (data, options);
515  	
516  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
517  		ASSERT (ip4_config != NULL,
518  		        "dhcp-dhcpcd-classless-invalid-2", "failed to parse DHCP4 options");
519  	
520  		/* Test falling back to old-style static routes if the classless static
521  		 * routes are invalid.
522  		 */
523  	
524  		/* Routes */
525  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2,
526  		        "dhcp-dhcpcd-classless-invalid-2", "unexpected number of routes");
527  		ip4_test_route ("dhcp-dhcpcd-classless-invalid-2", ip4_config, 0,
528  		                expected_route1_dest, expected_route1_gw, 32);
529  		ip4_test_route ("dhcp-dhcpcd-classless-invalid-2", ip4_config, 1,
530  		                expected_route2_dest, expected_route2_gw, 32);
531  	
532  		g_hash_table_destroy (options);
533  	}
534  	
535  	static void
536  	test_dhclient_invalid_classless_routes_3 (const char *client)
537  	{
538  		GHashTable *options;
539  		NMIP4Config *ip4_config;
540  		const char *expected_route1_dest = "192.168.10.0";
541  		const char *expected_route1_gw = "192.168.1.1";
542  		static Option data[] = {
543  			{ "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 32 128 10 17 66 41" },
544  			{ NULL, NULL }
545  		};
546  	
547  		options = fill_table (generic_options, NULL);
548  		options = fill_table (data, options);
549  	
550  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
551  		ASSERT (ip4_config != NULL,
552  		        "dhcp-dhclient-classless-invalid-3", "failed to parse DHCP4 options");
553  	
554  		/* IP4 routes */
555  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1,
556  		        "dhcp-dhclient-classless-invalid-3", "unexpected number of IP routes");
557  		ip4_test_route ("dhcp-dhclient-classless-invalid-3", ip4_config, 0,
558  		                expected_route1_dest, expected_route1_gw, 24);
559  	
560  		g_hash_table_destroy (options);
561  	}
562  	
563  	static void
564  	test_dhcpcd_invalid_classless_routes_3 (const char *client)
565  	{
566  		GHashTable *options;
567  		NMIP4Config *ip4_config;
568  		const char *expected_route1_dest = "192.168.10.0";
569  		const char *expected_route1_gw = "192.168.1.1";
570  		static Option data[] = {
571  			{ "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 128/32 10.17.66.41" },
572  			{ NULL, NULL }
573  		};
574  	
575  		options = fill_table (generic_options, NULL);
576  		options = fill_table (data, options);
577  	
578  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
579  		ASSERT (ip4_config != NULL,
580  		        "dhcp-dhcpcd-classless-invalid-3", "failed to parse DHCP4 options");
581  	
582  		/* IP4 routes */
583  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1,
584  		        "dhcp-dhcpcd-classless-invalid-3", "unexpected number of IP routes");
585  		ip4_test_route ("dhcp-dhcpcd-classless-invalid-3", ip4_config, 0,
586  		                expected_route1_dest, expected_route1_gw, 24);
587  	
588  		g_hash_table_destroy (options);
589  	}
590  	
591  	static void
592  	test_dhclient_gw_in_classless_routes (const char *client)
593  	{
594  		GHashTable *options;
595  		NMIP4Config *ip4_config;
596  		const char *expected_route1_dest = "192.168.10.0";
597  		const char *expected_route1_gw = "192.168.1.1";
598  		const char *expected_gateway = "192.2.3.4";
599  		static Option data[] = {
600  			{ "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 0 192 2 3 4" },
601  			{ NULL, NULL }
602  		};
603  	
604  		options = fill_table (generic_options, NULL);
605  		options = fill_table (data, options);
606  	
607  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
608  		ASSERT (ip4_config != NULL,
609  		        "dhcp-dhclient-classless-gateway", "failed to parse DHCP4 options");
610  	
611  		/* IP4 routes */
612  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1,
613  		        "dhcp-dhclient-classless-gateway", "unexpected number of IP routes");
614  		ip4_test_route ("dhcp-dhclient-classless-gateway", ip4_config, 0,
615  		                expected_route1_dest, expected_route1_gw, 24);
616  	
617  		/* Gateway */
618  		ip4_test_gateway ("dhcp-dhclient-classless-gateway", ip4_config, expected_gateway);
619  	
620  		g_hash_table_destroy (options);
621  	}
622  	
623  	static void
624  	test_dhcpcd_gw_in_classless_routes (const char *client)
625  	{
626  		GHashTable *options;
627  		NMIP4Config *ip4_config;
628  		const char *expected_route1_dest = "192.168.10.0";
629  		const char *expected_route1_gw = "192.168.1.1";
630  		const char *expected_gateway = "192.2.3.4";
631  		static Option data[] = {
632  			{ "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 0.0.0.0/0 192.2.3.4" },
633  			{ NULL, NULL }
634  		};
635  	
636  		options = fill_table (generic_options, NULL);
637  		options = fill_table (data, options);
638  	
639  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
640  		ASSERT (ip4_config != NULL,
641  		        "dhcp-dhcpcd-classless-gateway", "failed to parse DHCP4 options");
642  	
643  		/* IP4 routes */
644  		ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1,
645  		        "dhcp-dhcpcd-classless-gateway", "unexpected number of IP routes");
646  		ip4_test_route ("dhcp-dhcpcd-classless-gateway", ip4_config, 0,
647  		                expected_route1_dest, expected_route1_gw, 24);
648  	
649  		/* Gateway */
650  		ip4_test_gateway ("dhcp-dhcpcd-classless-gateway", ip4_config, expected_gateway);
651  	
652  		g_hash_table_destroy (options);
653  	}
654  	
655  	static Option escaped_searches_options[] = {
656  		{ "new_domain_search", "host1\\032host2\\032host3" },
657  		{ NULL, NULL }
658  	};
659  	
660  	static void
661  	test_escaped_domain_searches (const char *client)
662  	{
663  		GHashTable *options;
664  		NMIP4Config *ip4_config;
665  		const char *expected_search0 = "host1";
666  		const char *expected_search1 = "host2";
667  		const char *expected_search2 = "host3";
668  	
669  		options = fill_table (generic_options, NULL);
670  		options = fill_table (escaped_searches_options, options);
671  	
672  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
673  		ASSERT (ip4_config != NULL,
674  		        "dhcp-escaped-domain-searches", "failed to parse DHCP4 options");
675  	
676  		/* domain searches */
677  		ASSERT (nm_ip4_config_get_num_searches (ip4_config) == 3,
678  		        "dhcp-escaped-domain-searches", "unexpected number of searches");
679  		ASSERT (!strcmp (nm_ip4_config_get_search (ip4_config, 0), expected_search0),
680  		        "dhcp-escaped-domain-searches", "unexpected domain search #1");
681  		ASSERT (!strcmp (nm_ip4_config_get_search (ip4_config, 1), expected_search1),
682  		        "dhcp-escaped-domain-searches", "unexpected domain search #1");
683  		ASSERT (!strcmp (nm_ip4_config_get_search (ip4_config, 2), expected_search2),
684  		        "dhcp-escaped-domain-searches", "unexpected domain search #1");
685  	
686  		g_hash_table_destroy (options);
687  	}
688  	
689  	static Option invalid_escaped_searches_options[] = {
690  		{ "new_domain_search", "host1\\aahost2\\032host3" },
691  		{ NULL, NULL }
692  	};
693  	
694  	static void
695  	test_invalid_escaped_domain_searches (const char *client)
696  	{
697  		GHashTable *options;
698  		NMIP4Config *ip4_config;
699  	
700  		options = fill_table (generic_options, NULL);
701  		options = fill_table (invalid_escaped_searches_options, options);
702  	
703  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
704  		ASSERT (ip4_config != NULL,
705  		        "dhcp-invalid-escaped-domain-searches", "failed to parse DHCP4 options");
706  	
707  		/* domain searches */
708  		ASSERT (nm_ip4_config_get_num_searches (ip4_config) == 0,
709  		        "dhcp-invalid-escaped-domain-searches", "unexpected domain searches");
710  	
711  		g_hash_table_destroy (options);
712  	}
713  	
714  	static void
715  	test_ip4_missing_prefix (const char *client, const char *ip, guint32 expected_prefix)
716  	{
717  		GHashTable *options;
718  		NMIP4Config *ip4_config;
719  		const NMPlatformIP4Address *address;
720  	
721  		options = fill_table (generic_options, NULL);
722  		g_hash_table_insert (options, "new_ip_address", string_to_byte_array_gvalue (ip));
723  		g_hash_table_remove (options, "new_subnet_mask");
724  	
725  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
726  		ASSERT (ip4_config != NULL,
727  		        "dhcp-ip4-missing-prefix", "failed to parse DHCP4 options");
728  	
729  		ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1,
730  		        "dhcp-ip4-missing-prefix", "unexpected number of IP4 addresses (not 1)");
731  	
732  		address = nm_ip4_config_get_address (ip4_config, 0);
733  		ASSERT (address,
734  		        "dhcp-ip4-missing-prefix", "missing IP4 address #1");
735  	
736  		ASSERT (address->plen == expected_prefix,
737  		        "dhcp-ip4-missing-prefix", "unexpected IP4 address prefix %d (expected %d)",
738  		        address->plen, expected_prefix);
739  	
740  		g_hash_table_destroy (options);
741  	}
742  	
743  	static void
744  	test_ip4_prefix_classless (const char *client)
745  	{
746  		GHashTable *options;
747  		NMIP4Config *ip4_config;
748  		const NMPlatformIP4Address *address;
749  	
750  		/* Ensure that the missing-subnet-mask handler doesn't mangle classless
751  		 * subnet masks at all.  The handler should trigger only if the server
752  		 * doesn't send the subnet mask.
753  		 */
754  	
755  		options = fill_table (generic_options, NULL);
756  		g_hash_table_insert (options, "new_ip_address", string_to_byte_array_gvalue ("172.16.54.22"));
757  		g_hash_table_insert (options, "new_subnet_mask", string_to_byte_array_gvalue ("255.255.252.0"));
758  	
759  		ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind");
760  		ASSERT (ip4_config != NULL,
761  		        "dhcp-ip4-prefix-classless", "failed to parse DHCP4 options");
762  	
763  		ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1,
764  		        "dhcp-ip4-prefix-classless", "unexpected number of IP4 addresses (not 1)");
765  	
766  		address = nm_ip4_config_get_address (ip4_config, 0);
767  		ASSERT (address,
768  		        "dhcp-ip4-prefix-classless", "missing IP4 address #1");
769  	
770  		ASSERT (address->plen == 22,
771  		        "dhcp-ip4-prefix-classless", "unexpected IP4 address prefix %d (expected 22)",
772  		        address->plen);
773  	
774  		g_hash_table_destroy (options);
775  	}
776  	
777  	int main (int argc, char **argv)
778  	{
779  		GError *error = NULL;
780  		char *base;
781  		const char *clients[2][2] = { {DHCLIENT_PATH, "dhclient"}, {DHCPCD_PATH, "dhcpcd"} };
782  		guint32 i;
783  	
784  		g_type_init ();
785  	
786  		if (!nm_utils_init (&error))
787  			FAIL ("nm-utils-init", "failed to initialize libnm-util: %s", error->message);
788  	
789  		/* The tests */
790  		for (i = 0; i < 2; i++) {
791  			const char *client_path = clients[i][0];
792  			const char *client = clients[i][1];
793  	
794  			if (!client_path || !strlen (client_path))
795  				continue;
796  	
797  			test_generic_options (client);
798  			test_wins_options (client);
799  			test_classless_static_routes_1 (client);
800  			test_classless_static_routes_2 (client);
801  			test_fedora_dhclient_classless_static_routes (client);
802  			test_dhclient_invalid_classless_routes_1 (client);
803  			test_dhcpcd_invalid_classless_routes_1 (client);
804  			test_dhclient_invalid_classless_routes_2 (client);
805  			test_dhcpcd_invalid_classless_routes_2 (client);
806  			test_dhclient_invalid_classless_routes_3 (client);
807  			test_dhcpcd_invalid_classless_routes_3 (client);
808  			test_dhclient_gw_in_classless_routes (client);
809  			test_dhcpcd_gw_in_classless_routes (client);
810  			test_escaped_domain_searches (client);
811  			test_invalid_escaped_domain_searches (client);
812  			test_ip4_missing_prefix (client, "192.168.1.10", 24);
813  			test_ip4_missing_prefix (client, "172.16.54.50", 16);
814  			test_ip4_missing_prefix (client, "10.1.2.3", 8);
815  			test_ip4_prefix_classless (client);
816  		}
817  	
818  		base = g_path_get_basename (argv[0]);
819  		fprintf (stdout, "%s: SUCCESS\n", base);
820  		g_free (base);
821  		return 0;
822  	}
823  	
824