diff options
author | Dan Williams <dcbw@redhat.com> | 2014-07-22 14:39:32 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2014-07-22 14:39:32 -0500 |
commit | afebbb6ef8db81270beb8d55fa03000890ca8875 (patch) | |
tree | f621459bd1a42a360cab70ecb0a4819126522de3 | |
parent | 5de81735842a6e774b46c7f03e3a5fdba1556dd4 (diff) | |
parent | 7ef4e3fd01ea5c61c408e0bb8418395badae8846 (diff) |
merge: DHCP client code cleanups and simplification (bgo #732965)
In preparation for library-based DHCP clients, remove some assupmtions
that all DHCP clients are subprocesses, and that all DHCP options
get passed back NM via D-Bus.
https://bugzilla.gnome.org/show_bug.cgi?id=732965
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/devices/nm-device.c | 204 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-client.c | 1254 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-client.h | 99 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-dhclient.c | 36 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-dhcpcd.c | 27 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-manager.c | 132 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-manager.h | 19 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-utils.c | 685 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-utils.h | 39 | ||||
-rw-r--r-- | src/dhcp-manager/tests/Makefile.am | 19 | ||||
-rw-r--r-- | src/dhcp-manager/tests/test-dhcp-options.c | 683 | ||||
-rw-r--r-- | src/tests/Makefile.am | 14 | ||||
-rw-r--r-- | src/tests/test-dhcp-options.c | 937 |
14 files changed, 1892 insertions, 2258 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index ebbee17935..367b4c85fa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -97,6 +97,8 @@ nm_sources = \ \ dhcp-manager/nm-dhcp-client.c \ dhcp-manager/nm-dhcp-client.h \ + dhcp-manager/nm-dhcp-utils.c \ + dhcp-manager/nm-dhcp-utils.h \ dhcp-manager/nm-dhcp-dhclient.c \ dhcp-manager/nm-dhcp-dhclient.h \ dhcp-manager/nm-dhcp-dhclient-utils.c \ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 4750d0b3a8..ba9a675eae 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -241,7 +241,6 @@ typedef struct { /* DHCPv4 tracking */ NMDHCPClient * dhcp4_client; gulong dhcp4_state_sigid; - gulong dhcp4_timeout_sigid; NMDHCP4Config * dhcp4_config; NMIP4Config * vpn4_config; /* routes added by a VPN which uses this device */ @@ -279,7 +278,6 @@ typedef struct { NMDHCPClient * dhcp6_client; NMRDiscDHCPLevel dhcp6_mode; gulong dhcp6_state_sigid; - gulong dhcp6_timeout_sigid; NMDHCP6Config * dhcp6_config; /* IP6 config from DHCP */ NMIP6Config * dhcp6_ip6_config; @@ -2591,11 +2589,6 @@ dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release) priv->dhcp4_state_sigid = 0; } - if (priv->dhcp4_timeout_sigid) { - g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_timeout_sigid); - priv->dhcp4_timeout_sigid = 0; - } - nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE); if (stop) @@ -2610,14 +2603,6 @@ dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release) } } -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 gboolean ip4_config_merge_and_apply (NMDevice *self, NMIP4Config *config, @@ -2689,8 +2674,7 @@ dhcp4_fail (NMDevice *device, gboolean timeout) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - nm_dhcp4_config_reset (priv->dhcp4_config); - + dhcp4_cleanup (device, TRUE, FALSE); if (timeout || (priv->ip4_state == IP_CONF)) nm_device_activate_schedule_ip4_config_timeout (device); else if (priv->ip4_state == IP_FAIL) @@ -2698,26 +2682,40 @@ dhcp4_fail (NMDevice *device, gboolean timeout) } static void +dhcp4_update_config (NMDevice *self, NMDHCP4Config *config, GHashTable *options) +{ + GHashTableIter iter; + const char *key, *value; + + /* Update the DHCP4 config object with new DHCP options */ + nm_dhcp4_config_reset (config); + + g_hash_table_iter_init (&iter, options); + while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) + nm_dhcp4_config_add_option (config, key, value); + + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP4_CONFIG); +} + +static void dhcp4_state_changed (NMDHCPClient *client, - NMDHCPState state, + NMDhcpState state, + NMIP4Config *ip4_config, + GHashTable *options, gpointer user_data) { NMDevice *device = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - NMIP4Config *config; g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE); + g_return_if_fail (!ip4_config || NM_IS_IP4_CONFIG (ip4_config)); nm_log_dbg (LOGD_DHCP4, "(%s): new DHCPv4 client state %d", nm_device_get_iface (device), state); switch (state) { - case DHC_BOUND4: /* lease obtained */ - case DHC_RENEW4: /* lease renewed */ - case DHC_REBOOT: /* have valid lease, but now obtained a different one */ - case DHC_REBIND4: /* new, different lease */ - config = nm_dhcp_client_get_ip4_config (priv->dhcp4_client, FALSE); - if (!config) { + case NM_DHCP_STATE_BOUND: + if (!ip4_config) { nm_log_warn (LOGD_DHCP4, "(%s): failed to get IPv4 config in response to DHCP event.", nm_device_get_ip_iface (device)); nm_device_state_changed (device, @@ -2726,26 +2724,18 @@ dhcp4_state_changed (NMDHCPClient *client, break; } - /* Update the DHCP4 config object with new DHCP options */ - nm_dhcp4_config_reset (priv->dhcp4_config); - nm_dhcp_client_foreach_option (priv->dhcp4_client, - dhcp4_add_option_cb, - priv->dhcp4_config); - g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP4_CONFIG); + dhcp4_update_config (device, priv->dhcp4_config, options); if (priv->ip4_state == IP_CONF) - nm_device_activate_schedule_ip4_config_result (device, config); + nm_device_activate_schedule_ip4_config_result (device, ip4_config); else if (priv->ip4_state == IP_DONE) - dhcp4_lease_change (device, config); - g_object_unref (config); - + dhcp4_lease_change (device, ip4_config); break; - case DHC_TIMEOUT: /* timed out contacting DHCP server */ + case NM_DHCP_STATE_TIMEOUT: dhcp4_fail (device, TRUE); break; - case DHC_END: /* dhclient exited normally */ - case DHC_FAIL: /* all attempts to contact server timed out, sleeping */ - case DHC_ABEND: /* dhclient exited abnormally */ + case NM_DHCP_STATE_DONE: + case NM_DHCP_STATE_FAIL: /* dhclient quit and can't get/renew a lease; so kill the connection */ dhcp4_fail (device, FALSE); break; @@ -2754,18 +2744,6 @@ dhcp4_state_changed (NMDHCPClient *client, } } -static void -dhcp4_timeout (NMDHCPClient *client, gpointer user_data) -{ - NMDevice *device = NM_DEVICE (user_data); - - g_return_if_fail (nm_device_get_act_request (device) != NULL); - g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE); - - nm_dhcp_client_stop (client, FALSE); - dhcp4_fail (device, TRUE); -} - static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection, @@ -2791,10 +2769,13 @@ dhcp4_start (NMDevice *self, g_warn_if_fail (priv->dhcp4_client == NULL); priv->dhcp4_client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (), nm_device_get_ip_iface (self), + nm_device_get_ip_ifindex (self), tmp, nm_connection_get_uuid (connection), nm_device_get_priority (self), - s_ip4, + nm_setting_ip4_config_get_dhcp_send_hostname (s_ip4), + nm_setting_ip4_config_get_dhcp_hostname (s_ip4), + nm_setting_ip4_config_get_dhcp_client_id (s_ip4), priv->dhcp_timeout, priv->dhcp_anycast_address); @@ -2810,10 +2791,6 @@ dhcp4_start (NMDevice *self, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (dhcp4_state_changed), self); - priv->dhcp4_timeout_sigid = g_signal_connect (priv->dhcp4_client, - NM_DHCP_CLIENT_SIGNAL_TIMEOUT, - G_CALLBACK (dhcp4_timeout), - self); nm_device_add_pending_action (self, PENDING_ACTION_DHCP4, TRUE); @@ -3037,11 +3014,6 @@ dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release) priv->dhcp6_state_sigid = 0; } - if (priv->dhcp6_timeout_sigid) { - g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_timeout_sigid); - priv->dhcp6_timeout_sigid = 0; - } - nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE); if (stop) @@ -3056,14 +3028,6 @@ dhcp6_cleanup (NMDevice *self, gboolean stop, gboolean release) } } -static void -dhcp6_add_option_cb (gpointer key, gpointer value, gpointer user_data) -{ - nm_dhcp6_config_add_option (NM_DHCP6_CONFIG (user_data), - (const char *) key, - (const char *) value); -} - static gboolean ip6_config_merge_and_apply (NMDevice *self, gboolean commit, @@ -3139,8 +3103,7 @@ dhcp6_fail (NMDevice *device, gboolean timeout) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - nm_dhcp6_config_reset (priv->dhcp6_config); - + dhcp6_cleanup (device, TRUE, FALSE); if (timeout || (priv->ip6_state == IP_CONF)) nm_device_activate_schedule_ip6_config_timeout (device); else if (priv->ip6_state == IP_FAIL) @@ -3148,34 +3111,59 @@ dhcp6_fail (NMDevice *device, gboolean timeout) } static void +dhcp6_timeout (NMDevice *self, NMDHCPClient *client) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) + dhcp6_fail (self, TRUE); + else { + /* not a hard failure; just live with the RA info */ + dhcp6_cleanup (self, TRUE, FALSE); + if (priv->ip6_state == IP_CONF) + nm_device_activate_schedule_ip6_config_result (self); + } +} + +static void +dhcp6_update_config (NMDevice *self, NMDHCP6Config *config, GHashTable *options) +{ + GHashTableIter iter; + const char *key, *value; + + /* Update the DHCP6 config object with new DHCP options */ + nm_dhcp6_config_reset (config); + + g_hash_table_iter_init (&iter, options); + while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) + nm_dhcp6_config_add_option (config, key, value); + + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); +} + +static void dhcp6_state_changed (NMDHCPClient *client, - NMDHCPState state, + NMDhcpState state, + NMIP6Config *ip6_config, + GHashTable *options, gpointer user_data) { NMDevice *device = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE); + g_return_if_fail (!ip6_config || NM_IS_IP6_CONFIG (ip6_config)); nm_log_dbg (LOGD_DHCP6, "(%s): new DHCPv6 client state %d", nm_device_get_iface (device), state); switch (state) { - case DHC_BOUND6: - case DHC_RENEW6: /* lease renewed */ - case DHC_REBOOT: /* have valid lease, but now obtained a different one */ - case DHC_REBIND6: /* new, different lease */ + case NM_DHCP_STATE_BOUND: g_clear_object (&priv->dhcp6_ip6_config); - priv->dhcp6_ip6_config = nm_dhcp_client_get_ip6_config (priv->dhcp6_client, FALSE); - - /* Update the DHCP6 config object with new DHCP options */ - nm_dhcp6_config_reset (priv->dhcp6_config); - if (priv->dhcp6_ip6_config) { - nm_dhcp_client_foreach_option (priv->dhcp6_client, - dhcp6_add_option_cb, - priv->dhcp6_config); + if (ip6_config) { + priv->dhcp6_ip6_config = g_object_ref (ip6_config); + dhcp6_update_config (device, priv->dhcp6_config, options); } - g_object_notify (G_OBJECT (device), NM_DEVICE_DHCP6_CONFIG); if (priv->ip6_state == IP_CONF) { if (priv->dhcp6_ip6_config == NULL) { @@ -3187,10 +3175,10 @@ dhcp6_state_changed (NMDHCPClient *client, } else if (priv->ip6_state == IP_DONE) dhcp6_lease_change (device); break; - case DHC_TIMEOUT: /* timed out contacting DHCP server */ - dhcp6_fail (device, TRUE); + case NM_DHCP_STATE_TIMEOUT: + dhcp6_timeout (device, client); break; - case DHC_END: /* dhclient exited normally */ + case NM_DHCP_STATE_DONE: /* In IPv6 info-only mode, the client doesn't handle leases so it * may exit right after getting a response from the server. That's * normal. In that case we just ignore the exit. @@ -3198,8 +3186,7 @@ dhcp6_state_changed (NMDHCPClient *client, if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_OTHERCONF) break; /* Otherwise, fall through */ - case DHC_FAIL: /* all attempts to contact server timed out, sleeping */ - case DHC_ABEND: /* dhclient exited abnormally */ + case NM_DHCP_STATE_FAIL: /* dhclient quit and can't get/renew a lease; so kill the connection */ dhcp6_fail (device, FALSE); break; @@ -3208,30 +3195,6 @@ dhcp6_state_changed (NMDHCPClient *client, } } -static void -dhcp6_timeout (NMDHCPClient *client, gpointer user_data) -{ - NMDevice *device = NM_DEVICE (user_data); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); - - g_return_if_fail (nm_device_get_act_request (device) != NULL); - g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE); - - nm_dhcp_client_stop (client, FALSE); - if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) - dhcp6_fail (device, TRUE); - else { - /* not a hard failure; just live with the RA info */ - nm_dhcp6_config_reset (priv->dhcp6_config); - if (priv->dhcp6_ip6_config) - g_object_unref (priv->dhcp6_ip6_config); - priv->dhcp6_ip6_config = NULL; - - if (priv->ip6_state == IP_CONF) - nm_device_activate_schedule_ip6_config_result (device); - } -} - static NMActStageReturn dhcp6_start (NMDevice *self, NMConnection *connection, @@ -3248,7 +3211,8 @@ dhcp6_start (NMDevice *self, g_assert (connection); } - /* Begin a DHCP transaction on the interface */ + s_ip6 = nm_connection_get_setting_ip6_config (connection); + g_assert (s_ip6); /* Clear old exported DHCP options */ if (priv->dhcp6_config) @@ -3268,13 +3232,15 @@ dhcp6_start (NMDevice *self, priv->dhcp6_client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (), nm_device_get_ip_iface (self), + nm_device_get_ip_ifindex (self), tmp, nm_connection_get_uuid (connection), nm_device_get_priority (self), - nm_connection_get_setting_ip6_config (connection), + nm_setting_ip6_config_get_dhcp_hostname (s_ip6), priv->dhcp_timeout, priv->dhcp_anycast_address, - (dhcp_opt == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE); + (dhcp_opt == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE, + nm_setting_ip6_config_get_ip6_privacy (s_ip6)); if (tmp) g_byte_array_free (tmp, TRUE); @@ -3283,10 +3249,6 @@ dhcp6_start (NMDevice *self, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (dhcp6_state_changed), self); - priv->dhcp6_timeout_sigid = g_signal_connect (priv->dhcp6_client, - NM_DHCP_CLIENT_SIGNAL_TIMEOUT, - G_CALLBACK (dhcp6_timeout), - self); s_ip6 = nm_connection_get_setting_ip6_config (connection); if (!nm_setting_ip6_config_get_may_fail (s_ip6) || diff --git a/src/dhcp-manager/nm-dhcp-client.c b/src/dhcp-manager/nm-dhcp-client.c index 5c39fafa68..7fda8646f5 100644 --- a/src/dhcp-manager/nm-dhcp-client.c +++ b/src/dhcp-manager/nm-dhcp-client.c @@ -33,9 +33,11 @@ #include "nm-logging.h" #include "nm-dbus-glib-types.h" #include "nm-dhcp-client.h" +#include "nm-dhcp-utils.h" typedef struct { char * iface; + int ifindex; GByteArray * hwaddr; gboolean ipv6; char * uuid; @@ -43,13 +45,10 @@ typedef struct { guint32 timeout; GByteArray * duid; - guchar state; - GPid pid; - gboolean dead; + NMDhcpState state; + pid_t pid; guint timeout_id; guint watch_id; - guint32 remove_id; - GHashTable * options; gboolean info_only; } NMDHCPClientPrivate; @@ -60,8 +59,6 @@ G_DEFINE_TYPE_EXTENDED (NMDHCPClient, nm_dhcp_client, G_TYPE_OBJECT, G_TYPE_FLAG enum { SIGNAL_STATE_CHANGED, - SIGNAL_TIMEOUT, - SIGNAL_REMOVE, LAST_SIGNAL }; @@ -70,6 +67,7 @@ static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, PROP_IFACE, + PROP_IFINDEX, PROP_HWADDR, PROP_IPV6, PROP_UUID, @@ -80,7 +78,7 @@ enum { /********************************************/ -GPid +pid_t nm_dhcp_client_get_pid (NMDHCPClient *self) { g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), -1); @@ -96,6 +94,14 @@ nm_dhcp_client_get_iface (NMDHCPClient *self) return NM_DHCP_CLIENT_GET_PRIVATE (self)->iface; } +int +nm_dhcp_client_get_ifindex (NMDHCPClient *self) +{ + g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), -1); + + return NM_DHCP_CLIENT_GET_PRIVATE (self)->ifindex; +} + gboolean nm_dhcp_client_get_ipv6 (NMDHCPClient *self) { @@ -112,6 +118,72 @@ nm_dhcp_client_get_uuid (NMDHCPClient *self) return NM_DHCP_CLIENT_GET_PRIVATE (self)->uuid; } +const GByteArray * +nm_dhcp_client_get_duid (NMDHCPClient *self) +{ + g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid; +} + +const GByteArray * +nm_dhcp_client_get_hw_addr (NMDHCPClient *self) +{ + g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL); + + return NM_DHCP_CLIENT_GET_PRIVATE (self)->hwaddr; +} + +guint32 +nm_dhcp_client_get_priority (NMDHCPClient *self) +{ + g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), G_MAXUINT32); + + return NM_DHCP_CLIENT_GET_PRIVATE (self)->priority; +} + +/********************************************/ + +static const char *state_table[NM_DHCP_STATE_MAX + 1] = { + [NM_DHCP_STATE_UNKNOWN] = "unknown", + [NM_DHCP_STATE_BOUND] = "bound", + [NM_DHCP_STATE_TIMEOUT] = "timeout", + [NM_DHCP_STATE_DONE] = "done", + [NM_DHCP_STATE_FAIL] = "fail", +}; + +static const char * +state_to_string (NMDhcpState state) +{ + if (state >= 0 && state < G_N_ELEMENTS (state_table)) + return state_table[state]; + return NULL; +} + +static NMDhcpState +reason_to_state (const char *iface, const char *reason) +{ + if (g_ascii_strcasecmp (reason, "bound") == 0 || + g_ascii_strcasecmp (reason, "bound6") == 0 || + g_ascii_strcasecmp (reason, "renew") == 0 || + g_ascii_strcasecmp (reason, "renew6") == 0 || + g_ascii_strcasecmp (reason, "reboot") == 0 || + g_ascii_strcasecmp (reason, "rebind") == 0 || + g_ascii_strcasecmp (reason, "rebind6") == 0) + return NM_DHCP_STATE_BOUND; + else if (g_ascii_strcasecmp (reason, "timeout") == 0) + return NM_DHCP_STATE_TIMEOUT; + else if (g_ascii_strcasecmp (reason, "end") == 0) + return NM_DHCP_STATE_DONE; + else if (g_ascii_strcasecmp (reason, "fail") == 0 || + g_ascii_strcasecmp (reason, "abend") == 0 || + g_ascii_strcasecmp (reason, "nak") == 0) + return NM_DHCP_STATE_FAIL; + + nm_log_dbg (LOGD_DHCP, "(%s): unmapped DHCP state '%s'", iface, reason); + return NM_DHCP_STATE_UNKNOWN; +} + /********************************************/ static void @@ -137,7 +209,7 @@ watch_cleanup (NMDHCPClient *self) } void -nm_dhcp_client_stop_pid (GPid pid, const char *iface) +nm_dhcp_client_stop_pid (pid_t pid, const char *iface) { char *name = iface ? g_strdup_printf ("dhcp-client-%s", iface) : NULL; @@ -154,63 +226,76 @@ stop (NMDHCPClient *self, gboolean release, const GByteArray *duid) g_return_if_fail (NM_IS_DHCP_CLIENT (self)); priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - g_return_if_fail (priv->pid > 0); - - /* Clean up the watch handler since we're explicitly killing the daemon */ - watch_cleanup (self); - nm_dhcp_client_stop_pid (priv->pid, priv->iface); + if (priv->pid > 0) { + /* Clean up the watch handler since we're explicitly killing the daemon */ + watch_cleanup (self); + nm_dhcp_client_stop_pid (priv->pid, priv->iface); + priv->pid = -1; + } priv->info_only = FALSE; } -static gboolean -daemon_timeout (gpointer user_data) +void +nm_dhcp_client_set_state (NMDHCPClient *self, + NMDhcpState new_state, + GObject *ip_config, + GHashTable *options) { - NMDHCPClient *self = NM_DHCP_CLIENT (user_data); NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - if (priv->ipv6) { - nm_log_warn (LOGD_DHCP6, "(%s): DHCPv6 request timed out.", priv->iface); + if (new_state >= NM_DHCP_STATE_BOUND) + timeout_cleanup (self); + if (new_state >= NM_DHCP_STATE_TIMEOUT) + watch_cleanup (self); + + if (new_state == NM_DHCP_STATE_BOUND) { + g_assert ( (priv->ipv6 && NM_IS_IP6_CONFIG (ip_config)) + || (!priv->ipv6 && NM_IS_IP4_CONFIG (ip_config))); + g_assert (options); + g_assert_cmpint (g_hash_table_size (options), >, 0); } else { - nm_log_warn (LOGD_DHCP4, "(%s): DHCPv4 request timed out.", priv->iface); + g_assert (ip_config == NULL); + g_assert (options == NULL); } - g_signal_emit (G_OBJECT (self), signals[SIGNAL_TIMEOUT], 0); - return FALSE; -} -static gboolean -signal_remove (gpointer user_data) -{ - NMDHCPClient *self = NM_DHCP_CLIENT (user_data); + /* The client may send same-state transitions for RENEW/REBIND events and + * the lease may have changed, so handle same-state transitions for the + * BOUND state. Ignore same-state transitions for other events since + * the lease won't have changed and the state was already handled. + */ + if ((priv->state == new_state) && (new_state != NM_DHCP_STATE_BOUND)) + return; + + nm_log_info (priv->ipv6 ? LOGD_DHCP6 : LOGD_DHCP4, + "(%s): DHCPv%c state changed %s -> %s", + priv->iface, + priv->ipv6 ? '6' : '4', + state_to_string (priv->state), + state_to_string (new_state)); - NM_DHCP_CLIENT_GET_PRIVATE (self)->remove_id = 0; - g_signal_emit (G_OBJECT (self), signals[SIGNAL_REMOVE], 0); - return FALSE; + priv->state = new_state; + g_signal_emit (G_OBJECT (self), + signals[SIGNAL_STATE_CHANGED], 0, + new_state, + ip_config, + options); } -static void -dhcp_client_set_state (NMDHCPClient *self, - NMDHCPState state, - gboolean emit_state, - gboolean remove_now) +static gboolean +daemon_timeout (gpointer user_data) { + NMDHCPClient *self = NM_DHCP_CLIENT (user_data); NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - priv->state = state; - - if (emit_state) - g_signal_emit (G_OBJECT (self), signals[SIGNAL_STATE_CHANGED], 0, priv->state); - - if (state == DHC_END || state == DHC_ABEND) { - /* Start the remove signal timer */ - if (remove_now) { - g_signal_emit (G_OBJECT (self), signals[SIGNAL_REMOVE], 0); - } else { - if (!priv->remove_id) - priv->remove_id = g_timeout_add_seconds (5, signal_remove, self); - } - } + priv->timeout_id = 0; + nm_log_warn (priv->ipv6 ? LOGD_DHCP6 : LOGD_DHCP4, + "(%s): DHCPv%c request timed out.", + priv->iface, + priv->ipv6 ? '6' : '4'); + nm_dhcp_client_set_state (self, NM_DHCP_STATE_TIMEOUT, NULL, NULL); + return G_SOURCE_REMOVE; } static void @@ -218,7 +303,7 @@ daemon_watch_cb (GPid pid, gint status, gpointer user_data) { NMDHCPClient *self = NM_DHCP_CLIENT (user_data); NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - NMDHCPState new_state; + NMDhcpState new_state; if (priv->ipv6) { nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 client pid %d exited with status %d", @@ -231,32 +316,31 @@ daemon_watch_cb (GPid pid, gint status, gpointer user_data) } if (!WIFEXITED (status)) { - new_state = DHC_ABEND; + new_state = NM_DHCP_STATE_FAIL; nm_log_warn (LOGD_DHCP, "DHCP client died abnormally"); } else - new_state = DHC_END; + new_state = NM_DHCP_STATE_DONE; - watch_cleanup (self); - timeout_cleanup (self); - priv->dead = TRUE; + priv->pid = -1; - dhcp_client_set_state (self, new_state, TRUE, FALSE); + nm_dhcp_client_set_state (self, new_state, NULL, NULL); } -static void -start_monitor (NMDHCPClient *self) +void +nm_dhcp_client_watch_child (NMDHCPClient *self, pid_t pid) { NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - g_return_if_fail (priv->pid > 0); + g_return_if_fail (priv->pid == -1); + priv->pid = pid; /* Set up a timeout on the transaction to kill it after the timeout */ + g_assert (priv->timeout_id == 0); priv->timeout_id = g_timeout_add_seconds (priv->timeout, daemon_timeout, self); - priv->watch_id = g_child_watch_add (priv->pid, - (GChildWatchFunc) daemon_watch_cb, - self); + g_assert (priv->watch_id == 0); + priv->watch_id = g_child_watch_add (pid, daemon_watch_cb, self); } gboolean @@ -277,11 +361,7 @@ nm_dhcp_client_start_ip4 (NMDHCPClient *self, nm_log_info (LOGD_DHCP, "Activation (%s) Beginning DHCPv4 transaction (timeout in %d seconds)", priv->iface, priv->timeout); - priv->pid = NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, dhcp_client_id, dhcp_anycast_addr, hostname); - if (priv->pid) - start_monitor (self); - - return priv->pid ? TRUE : FALSE; + return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, dhcp_client_id, dhcp_anycast_addr, hostname); } /* uuid_parse does not work for machine-id, so we use our own converter */ @@ -370,38 +450,21 @@ generate_duid_from_machine_id (void) return duid; } -static char * -escape_duid (const GByteArray *duid) -{ - guint32 i = 0; - GString *s; - - g_return_val_if_fail (duid != NULL, NULL); - - s = g_string_sized_new (40); - while (i < duid->len) { - if (s->len) - g_string_append_c (s, ':'); - g_string_append_printf (s, "%02x", duid->data[i++]); - } - return g_string_free (s, FALSE); -} - static GByteArray * get_duid (NMDHCPClient *self) { static GByteArray *duid = NULL; GByteArray *copy = NULL; - char *escaped; + char *str; if (G_UNLIKELY (duid == NULL)) { duid = generate_duid_from_machine_id (); g_assert (duid); if (nm_logging_enabled (LOGL_DEBUG, LOGD_DHCP6)) { - escaped = escape_duid (duid); - nm_log_dbg (LOGD_DHCP6, "Generated DUID %s", escaped); - g_free (escaped); + str = nm_dhcp_utils_duid_to_string (duid); + nm_log_dbg (LOGD_DHCP6, "Generated DUID %s", str); + g_free (str); } } @@ -417,10 +480,11 @@ gboolean nm_dhcp_client_start_ip6 (NMDHCPClient *self, GByteArray *dhcp_anycast_addr, const char *hostname, - gboolean info_only) + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy) { NMDHCPClientPrivate *priv; - char *escaped; + char *str; g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE); @@ -436,9 +500,9 @@ nm_dhcp_client_start_ip6 (NMDHCPClient *self, priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self); if (nm_logging_enabled (LOGL_DEBUG, LOGD_DHCP)) { - escaped = escape_duid (priv->duid); - nm_log_dbg (LOGD_DHCP, "(%s): DHCPv6 DUID is '%s'", priv->iface, escaped); - g_free (escaped); + str = nm_dhcp_utils_duid_to_string (priv->duid); + nm_log_dbg (LOGD_DHCP, "(%s): DHCPv6 DUID is '%s'", priv->iface, str); + g_free (str); } priv->info_only = info_only; @@ -446,15 +510,12 @@ nm_dhcp_client_start_ip6 (NMDHCPClient *self, nm_log_info (LOGD_DHCP, "Activation (%s) Beginning DHCPv6 transaction (timeout in %d seconds)", priv->iface, priv->timeout); - priv->pid = NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self, - dhcp_anycast_addr, - hostname, - info_only, - priv->duid); - if (priv->pid > 0) - start_monitor (self); - - return priv->pid ? TRUE : FALSE; + return NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self, + dhcp_anycast_addr, + hostname, + info_only, + privacy, + priv->duid); } void @@ -482,7 +543,7 @@ nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name) exe = proc_contents; if (!strcmp (exe, binary_name)) - nm_dhcp_client_stop_pid ((GPid) tmp, NULL); + nm_dhcp_client_stop_pid ((pid_t) tmp, NULL); } } @@ -498,100 +559,27 @@ void nm_dhcp_client_stop (NMDHCPClient *self, gboolean release) { NMDHCPClientPrivate *priv; + pid_t old_pid = 0; g_return_if_fail (NM_IS_DHCP_CLIENT (self)); priv = NM_DHCP_CLIENT_GET_PRIVATE (self); /* Kill the DHCP client */ - if (!priv->dead) { - NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release, priv->duid); - priv->dead = TRUE; - + old_pid = priv->pid; + NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release, priv->duid); + if (old_pid > 0) { nm_log_info (LOGD_DHCP, "(%s): canceled DHCP transaction, DHCP client pid %d", - priv->iface, priv->pid); - } - - /* And clean stuff up */ - - priv->pid = -1; - dhcp_client_set_state (self, DHC_END, FALSE, TRUE); - - g_hash_table_remove_all (priv->options); + priv->iface, old_pid); + } else + nm_log_info (LOGD_DHCP, "(%s): canceled DHCP transaction", priv->iface); + g_assert (priv->pid == -1); - timeout_cleanup (self); - watch_cleanup (self); + nm_dhcp_client_set_state (self, NM_DHCP_STATE_DONE, NULL, NULL); } /********************************************/ -static gboolean -state_is_bound (guint32 state) -{ - if ( (state == DHC_BOUND4) - || (state == DHC_BOUND6) - || (state == DHC_RENEW4) - || (state == DHC_RENEW6) - || (state == DHC_REBOOT) - || (state == DHC_REBIND4) - || (state == DHC_REBIND6) - || (state == DHC_IPV4LL)) - return TRUE; - - return FALSE; -} - -static const char *state_table[] = { - [DHC_NBI] = "nbi", - [DHC_PREINIT] = "preinit", - [DHC_PREINIT6] = "preinit6", - [DHC_BOUND4] = "bound", - [DHC_BOUND6] = "bound6", - [DHC_IPV4LL] = "ipv4ll", - [DHC_RENEW4] = "renew", - [DHC_RENEW6] = "renew6", - [DHC_REBOOT] = "reboot", - [DHC_REBIND4] = "rebind", - [DHC_REBIND6] = "rebind6", - [DHC_DEPREF6] = "depref6", - [DHC_STOP] = "stop", - [DHC_STOP6] = "stop6", - [DHC_MEDIUM] = "medium", - [DHC_TIMEOUT] = "timeout", - [DHC_FAIL] = "fail", - [DHC_EXPIRE] = "expire", - [DHC_EXPIRE6] = "expire6", - [DHC_RELEASE] = "release", - [DHC_RELEASE6] = "release6", - [DHC_START] = "start", - [DHC_ABEND] = "abend", - [DHC_END] = "end", -}; - -static const char * -state_to_string (NMDHCPState state) -{ - if (state >= 0 && state < G_N_ELEMENTS (state_table)) - return state_table[state]; - return NULL; -} - -static NMDHCPState -string_to_state (const char *name) -{ - int i; - - if (name) { - for (i = 0; i < G_N_ELEMENTS (state_table); i++) { - const char *n = state_table[i]; - - if (n && !strcasecmp (name, n)) - return i; - } - } - return 255; -} - static char * garray_to_string (GArray *array, const char *key) { @@ -625,25 +613,47 @@ garray_to_string (GArray *array, const char *key) return converted; } +#define OLD_TAG "old_" +#define NEW_TAG "new_" + static void -copy_option (gpointer key, - gpointer value, +copy_option (const char * key, + GValue *value, gpointer user_data) { GHashTable *hash = user_data; - const char *str_key = (const char *) key; char *str_value = NULL; + const char **p; + static const char *ignored_keys[] = { + "interface", + "pid", + "reason", + "dhcp_message_type", + NULL + }; + + if (!G_VALUE_HOLDS (value, DBUS_TYPE_G_UCHAR_ARRAY)) { + nm_log_warn (LOGD_DHCP, "key %s value type was not DBUS_TYPE_G_UCHAR_ARRAY", key); + return; + } - if (G_VALUE_TYPE (value) != DBUS_TYPE_G_UCHAR_ARRAY) { - nm_log_warn (LOGD_DHCP, "unexpected key %s value type was not " - "DBUS_TYPE_G_UCHAR_ARRAY", - str_key); + if (g_str_has_prefix (key, OLD_TAG)) return; + + /* Filter out stuff that's not actually new DHCP options */ + for (p = ignored_keys; *p; p++) { + if (!strcmp (*p, key)) + return; } - str_value = garray_to_string ((GArray *) g_value_get_boxed (value), str_key); + if (g_str_has_prefix (key, NEW_TAG)) + key += STRLEN (NEW_TAG); + if (!key[0]) + return; + + str_value = garray_to_string ((GArray *) g_value_get_boxed (value), key); if (str_value) - g_hash_table_insert (hash, g_strdup (str_key), str_value); + g_hash_table_insert (hash, g_strdup (key), str_value); } void @@ -654,6 +664,8 @@ nm_dhcp_client_new_options (NMDHCPClient *self, NMDHCPClientPrivate *priv; guint32 old_state; guint32 new_state; + GHashTable *str_options = NULL; + GObject *ip_config = NULL; g_return_if_fail (NM_IS_DHCP_CLIENT (self)); g_return_if_fail (options != NULL); @@ -661,789 +673,35 @@ nm_dhcp_client_new_options (NMDHCPClient *self, priv = NM_DHCP_CLIENT_GET_PRIVATE (self); old_state = priv->state; - new_state = string_to_state (reason); - - /* Clear old and save new DHCP options */ - g_hash_table_remove_all (priv->options); - g_hash_table_foreach (options, copy_option, priv->options); - - if (old_state == new_state) { - /* dhclient will stay in the same state (or, really, provide the same - * reason) for operations like RENEW and REBIND. We need to ensure - * that triggers various DHCP lease change code, so we need to pass - * along same-state transitions for these states. - */ - if ( new_state != DHC_BOUND4 - && new_state != DHC_RENEW4 - && new_state != DHC_REBIND4 - && new_state != DHC_BOUND6 - && new_state != DHC_RENEW6 - && new_state != DHC_REBIND6) - return; - } - - /* Handle changed device state */ - if (state_is_bound (new_state)) { - /* Cancel the timeout if the DHCP client is now bound */ - timeout_cleanup (self); - } - - if (priv->ipv6) { - nm_log_info (LOGD_DHCP6, "(%s): DHCPv6 state changed %s -> %s", - priv->iface, - state_to_string (old_state), - state_to_string (new_state)); - } else { - nm_log_info (LOGD_DHCP4, "(%s): DHCPv4 state changed %s -> %s", - priv->iface, - state_to_string (old_state), - state_to_string (new_state)); - } - - dhcp_client_set_state (self, new_state, TRUE, FALSE); -} - -#define NEW_TAG "new_" -#define OLD_TAG "old_" - -gboolean -nm_dhcp_client_foreach_option (NMDHCPClient *self, - GHFunc func, - gpointer user_data) -{ - NMDHCPClientPrivate *priv; - GHashTableIter iter; - gpointer iterkey, itervalue; - - g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE); - g_return_val_if_fail (func != NULL, FALSE); - - priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - - if (!state_is_bound (priv->state)) { - if (priv->ipv6) { - nm_log_warn (LOGD_DHCP6, "(%s): DHCPv6 client didn't bind to a lease.", priv->iface); - } else { - nm_log_warn (LOGD_DHCP4, "(%s): DHCPv4 client didn't bind to a lease.", priv->iface); - } - } - - g_hash_table_iter_init (&iter, priv->options); - while (g_hash_table_iter_next (&iter, &iterkey, &itervalue)) { - const char *key = iterkey, *value = itervalue; - const char **p; - static const char *filter_options[] = { - "interface", "pid", "reason", "dhcp_message_type", NULL - }; - gboolean ignore = FALSE; - - /* Filter out stuff that's not actually new DHCP options */ - for (p = filter_options; *p; p++) { - if (!strcmp (*p, key) || !strncmp (key, OLD_TAG, strlen (OLD_TAG))) { - ignore = TRUE; - break; - } - } - - if (!ignore) { - const char *tmp_key = key; - - /* Remove the "new_" prefix that dhclient passes back */ - if (!strncmp (key, NEW_TAG, strlen (NEW_TAG))) - tmp_key = key + strlen (NEW_TAG); - - func ((gpointer) tmp_key, (gpointer) value, user_data); - } - } - return TRUE; -} - -/********************************************/ - -static gboolean -ip4_process_dhcpcd_rfc3442_routes (NMDHCPClient *self, - const char *str, - NMIP4Config *ip4_config, - guint32 *gwaddr) -{ - NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - char **routes, **r; - gboolean have_routes = FALSE; - - routes = g_strsplit (str, " ", 0); - if (g_strv_length (routes) == 0) - goto out; - - if ((g_strv_length (routes) % 2) != 0) { - nm_log_warn (LOGD_DHCP4, " classless static routes provided, but invalid"); - goto out; - } - - for (r = routes; *r; r += 2) { - char *slash; - NMPlatformIP4Route route; - int rt_cidr = 32; - guint32 rt_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_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route cidr: '%s'", slash + 1); - continue; - } - } - if (inet_pton (AF_INET, *r, &rt_addr) <= 0) { - nm_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route address: '%s'", *r); - continue; - } - if (inet_pton (AF_INET, *(r + 1), &rt_route) <= 0) { - nm_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route gateway: '%s'", *(r + 1)); - continue; - } - - have_routes = TRUE; - if (rt_cidr == 0 && rt_addr == 0) { - /* FIXME: how to handle multiple routers? */ - *gwaddr = rt_route; - } else { - nm_log_info (LOGD_DHCP4, " classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1)); - memset (&route, 0, sizeof (route)); - route.network = rt_addr; - route.plen = rt_cidr; - route.gateway = rt_route; - route.source = NM_PLATFORM_SOURCE_DHCP; - route.metric = priv->priority; - nm_ip4_config_add_route (ip4_config, &route); - } - } - -out: - g_strfreev (routes); - return have_routes; -} - -static const char ** -process_dhclient_rfc3442_route (const char **octets, NMPlatformIP4Route *route, gboolean *success) -{ - const char **o = octets; - int addr_len = 0, i = 0; - long int tmp; - char *next_hop; - guint32 tmp_addr; - - *success = FALSE; - - if (!*o) - return o; /* no prefix */ - - tmp = strtol (*o, NULL, 10); - if (tmp < 0 || tmp > 32) /* 32 == max IP4 prefix length */ - return o; - - memset (route, 0, sizeof (*route)); - route->plen = 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 &= nm_utils_ip4_prefix_to_netmask ((guint32) tmp); - route->network = tmp_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; - } - route->gateway = tmp_addr; - g_free (next_hop); - - *success = TRUE; - return o + 4; /* advance to past the next hop */ - -error: - return o; -} - -static gboolean -ip4_process_dhclient_rfc3442_routes (NMDHCPClient *self, - const char *str, - NMIP4Config *ip4_config, - guint32 *gwaddr) -{ - NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - char **octets, **o; - gboolean have_routes = FALSE; - NMPlatformIP4Route route; - gboolean success; - - o = octets = g_strsplit_set (str, " .", 0); - if (g_strv_length (octets) < 5) { - nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes '%s'", str); - goto out; - } - - while (*o) { - memset (&route, 0, sizeof (route)); - o = (char **) process_dhclient_rfc3442_route ((const char **) o, &route, &success); - if (!success) { - nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes"); - break; - } - - have_routes = TRUE; - if (!route.plen) { - /* gateway passed as classless static route */ - *gwaddr = route.gateway; - } else { - char addr[INET_ADDRSTRLEN]; - - /* normal route */ - route.source = NM_PLATFORM_SOURCE_DHCP; - route.metric = priv->priority; - nm_ip4_config_add_route (ip4_config, &route); - - nm_log_info (LOGD_DHCP4, " classless static route %s/%d gw %s", - nm_utils_inet4_ntop (route.network, addr), route.plen, - nm_utils_inet4_ntop (route.gateway, NULL)); - } - } - -out: - g_strfreev (octets); - return have_routes; -} - -static gboolean -ip4_process_classless_routes (NMDHCPClient *self, - GHashTable *options, - NMIP4Config *ip4_config, - guint32 *gwaddr) -{ - const char *str, *p; - - g_return_val_if_fail (options != NULL, FALSE); - g_return_val_if_fail (ip4_config != NULL, FALSE); - - *gwaddr = 0; - - /* dhcpd/dhclient in Fedora has support for rfc3442 implemented using a - * slightly different format: - * - * option classless-static-routes = array of (destination-descriptor ip-address); - * - * which results in: - * - * 0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6 - * - * dhcpcd supports classless static routes natively and uses this same - * option identifier with the following format: - * - * 192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41 - */ - str = g_hash_table_lookup (options, "new_classless_static_routes"); - - /* 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 - */ - if (!str) - 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; - - p = str; - while (*p) { - if (!g_ascii_isdigit (*p) && (*p != ' ') && (*p != '.') && (*p != '/')) { - nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes '%s'", str); - return FALSE; - } - p++; - }; - - if (strchr (str, '/')) { - /* dhcpcd format */ - return ip4_process_dhcpcd_rfc3442_routes (self, str, ip4_config, gwaddr); - } - - return ip4_process_dhclient_rfc3442_routes (self, str, ip4_config, gwaddr); -} - -static void -process_classful_routes (NMDHCPClient *self, GHashTable *options, NMIP4Config *ip4_config) -{ - NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - 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_log_info (LOGD_DHCP, " static routes provided, but invalid"); - goto out; - } - - for (s = searches; *s; s += 2) { - NMPlatformIP4Route route; - guint32 rt_addr, rt_route; - - if (inet_pton (AF_INET, *s, &rt_addr) <= 0) { - nm_log_warn (LOGD_DHCP, "DHCP provided invalid static route address: '%s'", *s); - continue; - } - if (inet_pton (AF_INET, *(s + 1), &rt_route) <= 0) { - nm_log_warn (LOGD_DHCP, "DHCP provided invalid static route gateway: '%s'", *(s + 1)); - continue; - } - - // FIXME: ensure the IP address and route are sane - - memset (&route, 0, sizeof (route)); - route.network = rt_addr; - /* RFC 2132, updated by RFC 3442: - The Static Routes option (option 33) does not provide a subnet mask - for each route - it is assumed that the subnet mask is implicit in - whatever network number is specified in each route entry */ - route.plen = nm_utils_ip4_get_default_prefix (rt_addr); - if (rt_addr & ~nm_utils_ip4_prefix_to_netmask (route.plen)) { - /* RFC 943: target not "this network"; using host routing */ - route.plen = 32; - } - route.gateway = rt_route; - route.source = NM_PLATFORM_SOURCE_DHCP; - route.metric = priv->priority; - - nm_ip4_config_add_route (ip4_config, &route); - nm_log_info (LOGD_DHCP, " static route %s", - nm_platform_ip4_route_to_string (&route)); - } - -out: - g_strfreev (searches); -} - -static void -process_domain_search (const char *str, GFunc add_func, gpointer user_data) -{ - char **searches, **s; - char *unescaped, *p; - int i; - - g_return_if_fail (str != NULL); - g_return_if_fail (add_func != NULL); - - p = unescaped = g_strdup (str); - do { - p = strstr (p, "\\032"); - if (!p) - break; - - /* Clear the escaped space with real spaces */ - for (i = 0; i < 4; i++) - *p++ = ' '; - } while (*p++); - - if (strchr (unescaped, '\\')) { - nm_log_warn (LOGD_DHCP, " invalid domain search: '%s'", unescaped); - goto out; - } - - searches = g_strsplit (unescaped, " ", 0); - for (s = searches; *s; s++) { - if (strlen (*s)) { - nm_log_info (LOGD_DHCP, " domain search '%s'", *s); - add_func (*s, user_data); - } - } - g_strfreev (searches); - -out: - g_free (unescaped); -} - -static void -ip4_add_domain_search (gpointer data, gpointer user_data) -{ - nm_ip4_config_add_search (NM_IP4_CONFIG (user_data), (const char *) data); -} - -/* Given a table of DHCP options from the client, convert into an IP4Config */ -static NMIP4Config * -ip4_options_to_config (NMDHCPClient *self) -{ - NMDHCPClientPrivate *priv; - NMIP4Config *ip4_config = NULL; - guint32 tmp_addr; - NMPlatformIP4Address address; - char *str = NULL; - guint32 gwaddr = 0, plen = 0; - - g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL); - - priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - g_return_val_if_fail (priv->options != NULL, NULL); - - ip4_config = nm_ip4_config_new (); - memset (&address, 0, sizeof (address)); - address.timestamp = nm_utils_get_monotonic_timestamp_s (); - - str = g_hash_table_lookup (priv->options, "new_ip_address"); - if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { - address.address = tmp_addr; - nm_log_info (LOGD_DHCP4, " address %s", str); - } else - goto error; - - str = g_hash_table_lookup (priv->options, "new_subnet_mask"); - if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { - plen = nm_utils_ip4_netmask_to_prefix (tmp_addr); - nm_log_info (LOGD_DHCP4, " plen %d (%s)", plen, str); - } else { - /* Get default netmask for the IP according to appropriate class. */ - plen = nm_utils_ip4_get_default_prefix (address.address); - nm_log_info (LOGD_DHCP4, " plen %d (default)", plen); - } - address.plen = plen; - - /* Routes: if the server returns classless static routes, we MUST ignore - * the 'static_routes' option. - */ - if (!ip4_process_classless_routes (self, priv->options, ip4_config, &gwaddr)) - process_classful_routes (self, priv->options, ip4_config); - - if (gwaddr) { - nm_log_info (LOGD_DHCP4, " gateway %s", nm_utils_inet4_ntop (gwaddr, NULL)); - nm_ip4_config_set_gateway (ip4_config, 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 (priv->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, &gwaddr) > 0) { - nm_ip4_config_set_gateway (ip4_config, gwaddr); - nm_log_info (LOGD_DHCP4, " gateway %s", *s); - break; - } else - nm_log_warn (LOGD_DHCP4, "ignoring invalid gateway '%s'", *s); - } - g_strfreev (routers); - } - } - - /* - * RFC 2132, section 9.7 - * DHCP clients use the contents of the 'server identifier' field - * as the destination address for any DHCP messages unicast to - * the DHCP server. - * - * Some ISP's provide leases from central servers that are on - * different subnets that the address offered. If the host - * does not configure the interface as the default route, the - * dhcp server may not be reachable via unicast, and a host - * specific route is needed. - **/ - str = g_hash_table_lookup (priv->options, "new_dhcp_server_identifier"); - if (str) { - if (inet_pton (AF_INET, str, &tmp_addr) > 0) { - NMPlatformIP4Route route; - guint32 mask = nm_utils_ip4_prefix_to_netmask (address.plen); - - nm_log_info (LOGD_DHCP4, " server identifier %s", str); - if ((tmp_addr & mask) != (address.address & mask)) { - /* DHCP server not on assigned subnet, route needed */ - memset (&route, 0, sizeof (route)); - route.network = tmp_addr; - route.plen = 32; - /* this will be a device route if gwaddr is 0 */ - route.gateway = gwaddr; - route.source = NM_PLATFORM_SOURCE_DHCP; - route.metric = priv->priority; - nm_ip4_config_add_route (ip4_config, &route); - nm_log_dbg (LOGD_IP, "adding route for server identifier: %s", - nm_platform_ip4_route_to_string (&route)); + new_state = reason_to_state (priv->iface, reason); + + if (new_state == NM_DHCP_STATE_BOUND) { + /* Copy options */ + str_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_foreach (options, (GHFunc) copy_option, str_options); + + /* Create the IP config */ + g_warn_if_fail (g_hash_table_size (str_options)); + if (g_hash_table_size (str_options)) { + if (priv->ipv6) { + ip_config = (GObject *) nm_dhcp_utils_ip6_config_from_options (priv->iface, + str_options, + priv->priority, + priv->info_only); + } else { + ip_config = (GObject *) nm_dhcp_utils_ip4_config_from_options (priv->iface, + str_options, + priv->priority); } + g_warn_if_fail (ip_config != NULL); } - else - nm_log_warn (LOGD_DHCP4, "ignoring invalid server identifier '%s'", str); - } - - str = g_hash_table_lookup (priv->options, "new_dhcp_lease_time"); - if (str) { - address.lifetime = address.preferred = strtoul (str, NULL, 10); - nm_log_info (LOGD_DHCP4, " lease time %d", address.lifetime); - } - - address.source = NM_PLATFORM_SOURCE_DHCP; - nm_ip4_config_add_address (ip4_config, &address); - - str = g_hash_table_lookup (priv->options, "new_host_name"); - if (str) - nm_log_info (LOGD_DHCP4, " hostname '%s'", str); - - str = g_hash_table_lookup (priv->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); - nm_log_info (LOGD_DHCP4, " nameserver '%s'", *s); - } else - nm_log_warn (LOGD_DHCP4, "ignoring invalid nameserver '%s'", *s); - } - g_strfreev (searches); - } - - str = g_hash_table_lookup (priv->options, "new_domain_name"); - if (str) { - char **domains = g_strsplit (str, " ", 0); - char **s; - - for (s = domains; *s; s++) { - nm_log_info (LOGD_DHCP4, " domain name '%s'", *s); - nm_ip4_config_add_domain (ip4_config, *s); - } - g_strfreev (domains); - } - - str = g_hash_table_lookup (priv->options, "new_domain_search"); - if (str) - process_domain_search (str, ip4_add_domain_search, ip4_config); - - str = g_hash_table_lookup (priv->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); - nm_log_info (LOGD_DHCP4, " wins '%s'", *s); - } else - nm_log_warn (LOGD_DHCP4, "ignoring invalid WINS server '%s'", *s); - } - g_strfreev (searches); - } - - str = g_hash_table_lookup (priv->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 > 576) - nm_ip4_config_set_mtu (ip4_config, int_mtu); - } - - str = g_hash_table_lookup (priv->options, "new_nis_domain"); - if (str) { - nm_log_info (LOGD_DHCP4, " NIS domain '%s'", str); - nm_ip4_config_set_nis_domain (ip4_config, str); - } - - str = g_hash_table_lookup (priv->options, "new_nis_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_nis_server (ip4_config, tmp_addr); - nm_log_info (LOGD_DHCP4, " nis '%s'", *s); - } else - nm_log_warn (LOGD_DHCP4, "ignoring invalid NIS server '%s'", *s); - } - g_strfreev (searches); - } - - return ip4_config; - -error: - g_object_unref (ip4_config); - return NULL; -} - -NMIP4Config * -nm_dhcp_client_get_ip4_config (NMDHCPClient *self, gboolean test) -{ - NMDHCPClientPrivate *priv; - - g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL); - - priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - - if (test && !state_is_bound (priv->state)) { - nm_log_warn (LOGD_DHCP4, "(%s): DHCPv4 client didn't bind to a lease.", priv->iface); - return NULL; - } - - if (!g_hash_table_size (priv->options)) { - /* We never got a response from the DHCP client */ - return NULL; - } - - return ip4_options_to_config (self); -} - -/********************************************/ - -static void -ip6_add_domain_search (gpointer data, gpointer user_data) -{ - nm_ip6_config_add_search (NM_IP6_CONFIG (user_data), (const char *) data); -} - -/* Given a table of DHCP options from the client, convert into an IP6Config */ -static NMIP6Config * -ip6_options_to_config (NMDHCPClient *self) -{ - NMDHCPClientPrivate *priv; - NMIP6Config *ip6_config = NULL; - struct in6_addr tmp_addr; - NMPlatformIP6Address address; - char *str = NULL; - GHashTableIter iter; - gpointer key, value; - - g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL); - - memset (&address, 0, sizeof (address)); - address.plen = 128; - address.timestamp = nm_utils_get_monotonic_timestamp_s (); - - priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - g_return_val_if_fail (priv->options != NULL, NULL); - - g_hash_table_iter_init (&iter, priv->options); - while (g_hash_table_iter_next (&iter, &key, &value)) { - nm_log_dbg (LOGD_DHCP6, "(%s): option '%s'=>'%s'", - priv->iface, (const char *) key, (const char *) value); - } - - ip6_config = nm_ip6_config_new (); - - str = g_hash_table_lookup (priv->options, "new_max_life"); - if (str) { - address.lifetime = strtoul (str, NULL, 10); - nm_log_info (LOGD_DHCP6, " valid_lft %d", address.lifetime); - } - - str = g_hash_table_lookup (priv->options, "new_preferred_life"); - if (str) { - address.preferred = strtoul (str, NULL, 10); - nm_log_info (LOGD_DHCP6, " preferred_lft %d", address.preferred); - } - - str = g_hash_table_lookup (priv->options, "new_ip6_address"); - if (str) { - if (!inet_pton (AF_INET6, str, &tmp_addr)) { - nm_log_warn (LOGD_DHCP6, "(%s): DHCP returned invalid address '%s'", - priv->iface, str); - goto error; - } - - address.address = tmp_addr; - address.source = NM_PLATFORM_SOURCE_DHCP; - nm_ip6_config_add_address (ip6_config, &address); - nm_log_info (LOGD_DHCP6, " address %s", str); - } else if (priv->info_only == FALSE) { - /* No address in Managed mode is a hard error */ - goto error; - } - - str = g_hash_table_lookup (priv->options, "new_host_name"); - if (str) - nm_log_info (LOGD_DHCP6, " hostname '%s'", str); - - str = g_hash_table_lookup (priv->options, "new_dhcp6_name_servers"); - if (str) { - char **searches = g_strsplit (str, " ", 0); - char **s; - - for (s = searches; *s; s++) { - if (inet_pton (AF_INET6, *s, &tmp_addr) > 0) { - nm_ip6_config_add_nameserver (ip6_config, &tmp_addr); - nm_log_info (LOGD_DHCP6, " nameserver '%s'", *s); - } else - nm_log_warn (LOGD_DHCP6, "ignoring invalid nameserver '%s'", *s); - } - g_strfreev (searches); - } - - str = g_hash_table_lookup (priv->options, "new_dhcp6_domain_search"); - if (str) - process_domain_search (str, ip6_add_domain_search, ip6_config); - - return ip6_config; - -error: - g_object_unref (ip6_config); - return NULL; -} - -NMIP6Config * -nm_dhcp_client_get_ip6_config (NMDHCPClient *self, gboolean test) -{ - NMDHCPClientPrivate *priv; - - g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL); - - priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - - if (test && !state_is_bound (priv->state)) { - nm_log_warn (LOGD_DHCP6, "(%s): DHCPv6 client didn't bind to a lease.", priv->iface); - return NULL; } - if (!g_hash_table_size (priv->options)) { - /* We never got a response from the DHCP client */ - return NULL; - } + nm_dhcp_client_set_state (self, new_state, ip_config, str_options); - return ip6_options_to_config (self); + if (str_options) + g_hash_table_destroy (str_options); + g_clear_object (&ip_config); } /********************************************/ @@ -1451,10 +709,7 @@ nm_dhcp_client_get_ip6_config (NMDHCPClient *self, gboolean test) static void nm_dhcp_client_init (NMDHCPClient *self) { - NMDHCPClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); - - priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - priv->pid = -1; + NM_DHCP_CLIENT_GET_PRIVATE (self)->pid = -1; } static void @@ -1467,6 +722,9 @@ get_property (GObject *object, guint prop_id, case PROP_IFACE: g_value_set_string (value, priv->iface); break; + case PROP_IFINDEX: + g_value_set_int (value, priv->ifindex); + break; case PROP_HWADDR: g_value_set_boxed (value, priv->hwaddr); break; @@ -1499,6 +757,11 @@ set_property (GObject *object, guint prop_id, /* construct-only */ priv->iface = g_strdup (g_value_get_string (value)); break; + case PROP_IFINDEX: + /* construct-only */ + priv->ifindex = g_value_get_int (value); + g_warn_if_fail (priv->ifindex > 0); + break; case PROP_HWADDR: /* construct only */ priv->hwaddr = g_value_dup_boxed (value); @@ -1535,15 +798,9 @@ dispose (GObject *object) * the DHCP client. */ - if (priv->remove_id) { - g_source_remove (priv->remove_id); - priv->remove_id = 0; - } + watch_cleanup (self); + timeout_cleanup (self); - if (priv->options) { - g_hash_table_destroy (priv->options); - priv->options = NULL; - } g_clear_pointer (&priv->iface, g_free); if (priv->hwaddr) { @@ -1582,6 +839,12 @@ nm_dhcp_client_class_init (NMDHCPClientClass *client_class) G_PARAM_STATIC_STRINGS)); g_object_class_install_property + (object_class, PROP_IFINDEX, + g_param_spec_int (NM_DHCP_CLIENT_IFINDEX, "", "", + -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_HWADDR, g_param_spec_boxed (NM_DHCP_CLIENT_HWADDR, "", "", G_TYPE_BYTE_ARRAY, @@ -1622,26 +885,7 @@ nm_dhcp_client_class_init (NMDHCPClientClass *client_class) G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (NMDHCPClientClass, state_changed), - NULL, NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, 1, G_TYPE_UINT); - - signals[SIGNAL_TIMEOUT] = - g_signal_new (NM_DHCP_CLIENT_SIGNAL_TIMEOUT, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (NMDHCPClientClass, timeout), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - signals[SIGNAL_REMOVE] = - g_signal_new (NM_DHCP_CLIENT_SIGNAL_REMOVE, - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (NMDHCPClientClass, remove), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + NULL, NULL, NULL, + G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_OBJECT, G_TYPE_HASH_TABLE); } diff --git a/src/dhcp-manager/nm-dhcp-client.h b/src/dhcp-manager/nm-dhcp-client.h index ad18926af9..d01d938a2d 100644 --- a/src/dhcp-manager/nm-dhcp-client.h +++ b/src/dhcp-manager/nm-dhcp-client.h @@ -35,43 +35,24 @@ #define NM_DHCP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CLIENT, NMDHCPClientClass)) #define NM_DHCP_CLIENT_INTERFACE "iface" +#define NM_DHCP_CLIENT_IFINDEX "ifindex" #define NM_DHCP_CLIENT_HWADDR "hwaddr" #define NM_DHCP_CLIENT_IPV6 "ipv6" #define NM_DHCP_CLIENT_UUID "uuid" #define NM_DHCP_CLIENT_PRIORITY "priority" #define NM_DHCP_CLIENT_TIMEOUT "timeout" -#define NM_DHCP_CLIENT_SIGNAL_TIMEOUT "timeout" #define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed" -#define NM_DHCP_CLIENT_SIGNAL_REMOVE "remove" typedef enum { - DHC_NBI = 0, /* no broadcast interfaces found */ - DHC_PREINIT, /* configuration started */ - DHC_PREINIT6, /* configuration started */ - DHC_BOUND4, /* IPv4 lease obtained */ - DHC_BOUND6, /* IPv6 lease obtained */ - DHC_IPV4LL, /* IPv4LL address obtained */ - DHC_RENEW4, /* IPv4 lease renewed */ - DHC_RENEW6, /* IPv6 lease renewed */ - DHC_REBOOT, /* have valid lease, but now obtained a different one */ - DHC_REBIND4, /* IPv4 new/different lease */ - DHC_REBIND6, /* IPv6 new/different lease */ - DHC_DEPREF6, /* IPv6 lease depreferred */ - DHC_STOP, /* remove old lease */ - DHC_STOP6, /* remove old lease */ - DHC_MEDIUM, /* media selection begun */ - DHC_TIMEOUT, /* timed out contacting DHCP server */ - DHC_FAIL, /* all attempts to contact server timed out, sleeping */ - DHC_EXPIRE, /* lease has expired, renewing */ - DHC_EXPIRE6, /* lease has expired, renewing */ - DHC_RELEASE, /* releasing lease */ - DHC_RELEASE6, /* releasing lease */ - DHC_START, /* sent when dhclient started OK */ - DHC_ABEND, /* dhclient exited abnormally */ - DHC_END, /* dhclient exited normally */ - DHC_END_OPTIONS, /* last option in subscription sent */ -} NMDHCPState; + NM_DHCP_STATE_UNKNOWN = 0, + NM_DHCP_STATE_BOUND, /* lease changed (state_is_bound) */ + NM_DHCP_STATE_TIMEOUT, /* TIMEOUT */ + NM_DHCP_STATE_DONE, /* END */ + NM_DHCP_STATE_FAIL, /* failed or quit unexpectedly */ + __NM_DHCP_STATE_MAX, + NM_DHCP_STATE_MAX = __NM_DHCP_STATE_MAX - 1, +} NMDhcpState; typedef struct { GObject parent; @@ -82,20 +63,21 @@ typedef struct { /* Methods */ - GPid (*ip4_start) (NMDHCPClient *self, - const char *dhcp_client_id, - GByteArray *anycast_addr, - const char *hostname); + gboolean (*ip4_start) (NMDHCPClient *self, + const char *dhcp_client_id, + GByteArray *anycast_addr, + const char *hostname); - GPid (*ip6_start) (NMDHCPClient *self, - GByteArray *anycast_addr, - const char *hostname, - gboolean info_only, - const GByteArray *duid); + gboolean (*ip6_start) (NMDHCPClient *self, + GByteArray *anycast_addr, + const char *hostname, + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, + const GByteArray *duid); - void (*stop) (NMDHCPClient *self, - gboolean release, - const GByteArray *duid); + void (*stop) (NMDHCPClient *self, + gboolean release, + const GByteArray *duid); /** * get_duid: @@ -109,21 +91,30 @@ typedef struct { GByteArray * (*get_duid) (NMDHCPClient *self); /* Signals */ - void (*state_changed) (NMDHCPClient *self, NMDHCPState state); - void (*timeout) (NMDHCPClient *self); - void (*remove) (NMDHCPClient *self); + void (*state_changed) (NMDHCPClient *self, + NMDhcpState state, + GObject *ip_config, + GHashTable *options); } NMDHCPClientClass; GType nm_dhcp_client_get_type (void); -GPid nm_dhcp_client_get_pid (NMDHCPClient *self); +pid_t nm_dhcp_client_get_pid (NMDHCPClient *self); const char *nm_dhcp_client_get_iface (NMDHCPClient *self); +int nm_dhcp_client_get_ifindex (NMDHCPClient *self); + gboolean nm_dhcp_client_get_ipv6 (NMDHCPClient *self); const char *nm_dhcp_client_get_uuid (NMDHCPClient *self); +const GByteArray *nm_dhcp_client_get_duid (NMDHCPClient *self); + +const GByteArray *nm_dhcp_client_get_hw_addr (NMDHCPClient *self); + +guint32 nm_dhcp_client_get_priority (NMDHCPClient *self); + gboolean nm_dhcp_client_start_ip4 (NMDHCPClient *self, const char *dhcp_client_id, GByteArray *dhcp_anycast_addr, @@ -132,7 +123,8 @@ gboolean nm_dhcp_client_start_ip4 (NMDHCPClient *self, gboolean nm_dhcp_client_start_ip6 (NMDHCPClient *self, GByteArray *dhcp_anycast_addr, const char *hostname, - gboolean info_only); + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy); void nm_dhcp_client_stop (NMDHCPClient *self, gboolean release); @@ -140,18 +132,17 @@ void nm_dhcp_client_new_options (NMDHCPClient *self, GHashTable *options, const char *reason); -gboolean nm_dhcp_client_foreach_option (NMDHCPClient *self, - GHFunc func, - gpointer user_data); - -NMIP4Config *nm_dhcp_client_get_ip4_config (NMDHCPClient *self, gboolean test); +/* Backend helpers for subclasses */ +void nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name); -NMIP6Config *nm_dhcp_client_get_ip6_config (NMDHCPClient *self, gboolean test); +void nm_dhcp_client_stop_pid (pid_t pid, const char *iface); -/* Backend helpers */ -void nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name); +void nm_dhcp_client_watch_child (NMDHCPClient *self, pid_t pid); -void nm_dhcp_client_stop_pid (GPid pid, const char *iface); +void nm_dhcp_client_set_state (NMDHCPClient *self, + NMDhcpState new_state, + GObject *ip_config, /* NMIP4Config or NMIP6Config */ + GHashTable *options); /* str:str hash */ #endif /* NM_DHCP_CLIENT_H */ diff --git a/src/dhcp-manager/nm-dhcp-dhclient.c b/src/dhcp-manager/nm-dhcp-dhclient.c index 4232ee0cce..2f1e11deba 100644 --- a/src/dhcp-manager/nm-dhcp-dhclient.c +++ b/src/dhcp-manager/nm-dhcp-dhclient.c @@ -324,7 +324,7 @@ dhclient_child_setup (gpointer user_data G_GNUC_UNUSED) nm_unblock_posix_signals (NULL); } -static GPid +static gboolean dhclient_start (NMDHCPClient *client, const char *mode_opt, const GByteArray *duid, @@ -332,7 +332,7 @@ dhclient_start (NMDHCPClient *client, { NMDHCPDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (client); GPtrArray *argv = NULL; - GPid pid = -1; + pid_t pid; GError *error = NULL; const char *iface, *uuid, *system_bus_address; char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL; @@ -340,7 +340,7 @@ dhclient_start (NMDHCPClient *client, guint log_domain; char *escaped, *preferred_leasefile_path = NULL; - g_return_val_if_fail (priv->pid_file == NULL, -1); + g_return_val_if_fail (priv->pid_file == NULL, FALSE); iface = nm_dhcp_client_get_iface (client); uuid = nm_dhcp_client_get_uuid (client); @@ -350,7 +350,7 @@ dhclient_start (NMDHCPClient *client, if (!g_file_test (priv->path, G_FILE_TEST_EXISTS)) { nm_log_warn (log_domain, "%s does not exist.", priv->path); - return -1; + return FALSE; } pid_file = g_strdup_printf (LOCALSTATEDIR "/run/dhclient%s-%s.pid", @@ -404,7 +404,8 @@ dhclient_start (NMDHCPClient *client, iface, priv->lease_file, error ? error->code : -1, error && error->message ? error->message : "(unknown)"); - return -1; + g_free (pid_file); + return FALSE; } } @@ -456,22 +457,24 @@ dhclient_start (NMDHCPClient *client, nm_log_dbg (log_domain, "running: %s", cmd_str); g_free (cmd_str); - if (!g_spawn_async (NULL, (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, + if (g_spawn_async (NULL, (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, &dhclient_child_setup, NULL, &pid, &error)) { - nm_log_warn (log_domain, "dhclient failed to start: '%s'", error->message); - g_error_free (error); - pid = -1; - } else { + g_assert (pid > 0); nm_log_info (log_domain, "dhclient started with pid %d", pid); + nm_dhcp_client_watch_child (client, pid); priv->pid_file = pid_file; + } else { + nm_log_warn (log_domain, "dhclient failed to start: '%s'", error->message); + g_error_free (error); + g_free (pid_file); } g_ptr_array_free (argv, TRUE); g_free (system_bus_address_env); - return pid; + return pid > 0 ? TRUE : FALSE; } -static GPid +static gboolean ip4_start (NMDHCPClient *client, const char *dhcp_client_id, GByteArray *dhcp_anycast_addr, @@ -486,17 +489,18 @@ ip4_start (NMDHCPClient *client, priv->conf_file = create_dhclient_config (iface, FALSE, uuid, dhcp_client_id, dhcp_anycast_addr, hostname); if (!priv->conf_file) { nm_log_warn (LOGD_DHCP4, "(%s): error creating dhclient configuration file.", iface); - return -1; + return FALSE; } return dhclient_start (client, NULL, NULL, FALSE); } -static GPid +static gboolean ip6_start (NMDHCPClient *client, GByteArray *dhcp_anycast_addr, const char *hostname, gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, const GByteArray *duid) { NMDHCPDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (client); @@ -508,7 +512,7 @@ ip6_start (NMDHCPClient *client, priv->conf_file = create_dhclient_config (iface, TRUE, uuid, NULL, dhcp_anycast_addr, hostname); if (!priv->conf_file) { nm_log_warn (LOGD_DHCP6, "(%s): error creating dhclient6 configuration file.", iface); - return -1; + return FALSE; } return dhclient_start (client, info_only ? "-S" : "-N", duid, FALSE); @@ -533,7 +537,7 @@ stop (NMDHCPClient *client, gboolean release, const GByteArray *duid) } if (release) { - GPid rpid; + pid_t rpid; rpid = dhclient_start (client, NULL, duid, TRUE); if (rpid > 0) { diff --git a/src/dhcp-manager/nm-dhcp-dhcpcd.c b/src/dhcp-manager/nm-dhcp-dhcpcd.c index acfbee2692..5221f4362a 100644 --- a/src/dhcp-manager/nm-dhcp-dhcpcd.c +++ b/src/dhcp-manager/nm-dhcp-dhcpcd.c @@ -86,7 +86,7 @@ dhcpcd_child_setup (gpointer user_data G_GNUC_UNUSED) nm_unblock_posix_signals (NULL); } -static GPid +static gboolean ip4_start (NMDHCPClient *client, const char *dhcp_client_id, GByteArray *dhcp_anycast_addr, @@ -94,12 +94,12 @@ ip4_start (NMDHCPClient *client, { NMDHCPDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (client); GPtrArray *argv = NULL; - GPid pid = -1; + pid_t pid = -1; GError *error = NULL; char *pid_contents = NULL, *binary_name, *cmd_str; const char *iface; - g_return_val_if_fail (priv->pid_file == NULL, -1); + g_return_val_if_fail (priv->pid_file == NULL, FALSE); iface = nm_dhcp_client_get_iface (client); @@ -110,7 +110,7 @@ ip4_start (NMDHCPClient *client, if (!g_file_test (priv->path, G_FILE_TEST_EXISTS)) { nm_log_warn (LOGD_DHCP4, "%s does not exist.", priv->path); - return -1; + return FALSE; } /* Kill any existing dhcpcd from the pidfile */ @@ -152,28 +152,31 @@ ip4_start (NMDHCPClient *client, nm_log_dbg (LOGD_DHCP4, "running: %s", cmd_str); g_free (cmd_str); - if (!g_spawn_async (NULL, (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, - &dhcpcd_child_setup, NULL, &pid, &error)) { + if (g_spawn_async (NULL, (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, + &dhcpcd_child_setup, NULL, &pid, &error)) { + g_assert (pid > 0); + nm_log_info (LOGD_DHCP4, "dhcpcd started with pid %d", pid); + nm_dhcp_client_watch_child (client, pid); + } else { nm_log_warn (LOGD_DHCP4, "dhcpcd failed to start. error: '%s'", error->message); g_error_free (error); - pid = -1; - } else - nm_log_info (LOGD_DHCP4, "dhcpcd started with pid %d", pid); + } g_free (pid_contents); g_ptr_array_free (argv, TRUE); - return pid; + return pid > 0 ? TRUE : FALSE; } -static GPid +static gboolean ip6_start (NMDHCPClient *client, GByteArray *dhcp_anycast_addr, const char *hostname, gboolean info_only, + NMSettingIP6ConfigPrivacy privacy, const GByteArray *duid) { nm_log_warn (LOGD_DHCP6, "the dhcpcd backend does not support IPv6."); - return -1; + return FALSE; } static void diff --git a/src/dhcp-manager/nm-dhcp-manager.c b/src/dhcp-manager/nm-dhcp-manager.c index 195d50b074..c4de434976 100644 --- a/src/dhcp-manager/nm-dhcp-manager.c +++ b/src/dhcp-manager/nm-dhcp-manager.c @@ -142,16 +142,14 @@ get_client_for_pid (NMDHCPManager *manager, GPid pid) } static NMDHCPClient * -get_client_for_iface (NMDHCPManager *manager, - const char *iface, - gboolean ip6) +get_client_for_ifindex (NMDHCPManager *manager, int ifindex, gboolean ip6) { NMDHCPManagerPrivate *priv; GHashTableIter iter; gpointer value; g_return_val_if_fail (NM_IS_DHCP_MANAGER (manager), NULL); - g_return_val_if_fail (iface, NULL); + g_return_val_if_fail (ifindex > 0, NULL); priv = NM_DHCP_MANAGER_GET_PRIVATE (manager); @@ -159,8 +157,8 @@ get_client_for_iface (NMDHCPManager *manager, while (g_hash_table_iter_next (&iter, NULL, &value)) { NMDHCPClient *candidate = NM_DHCP_CLIENT (value); - if ( !strcmp (iface, nm_dhcp_client_get_iface (candidate)) - && (nm_dhcp_client_get_ipv6 (candidate) == ip6)) + if ( nm_dhcp_client_get_ifindex (candidate) == ifindex + && nm_dhcp_client_get_ipv6 (candidate) == ip6) return candidate; } @@ -339,49 +337,40 @@ get_client_type (const char *client, GError **error) return G_TYPE_INVALID; } -#define REMOVE_ID_TAG "remove-id" -#define TIMEOUT_ID_TAG "timeout-id" +static void client_state_changed (NMDHCPClient *client, + NMDhcpState state, + GObject *ip_config, + GHashTable *options, + NMDHCPManager *self); static void remove_client (NMDHCPManager *self, NMDHCPClient *client) { - NMDHCPManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self); - guint id; - - id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (client), REMOVE_ID_TAG)); - if (id) - g_signal_handler_disconnect (client, id); - - id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (client), TIMEOUT_ID_TAG)); - if (id) - g_signal_handler_disconnect (client, id); + g_signal_handlers_disconnect_by_func (client, client_state_changed, self); /* Stopping the client is left up to the controlling device * explicitly since we may want to quit NetworkManager but not terminate * the DHCP client. */ - g_hash_table_remove (priv->clients, client); + g_hash_table_remove (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client); } static void -add_client (NMDHCPManager *self, NMDHCPClient *client) +client_state_changed (NMDHCPClient *client, + NMDhcpState state, + GObject *ip_config, + GHashTable *options, + NMDHCPManager *self) { - NMDHCPManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self); - guint id; - - id = g_signal_connect_swapped (client, NM_DHCP_CLIENT_SIGNAL_REMOVE, G_CALLBACK (remove_client), self); - g_object_set_data (G_OBJECT (client), REMOVE_ID_TAG, GUINT_TO_POINTER (id)); - - id = g_signal_connect_swapped (client, NM_DHCP_CLIENT_SIGNAL_TIMEOUT, G_CALLBACK (remove_client), self); - g_object_set_data (G_OBJECT (client), TIMEOUT_ID_TAG, GUINT_TO_POINTER (id)); - - g_hash_table_insert (priv->clients, client, g_object_ref (client)); + if (state >= NM_DHCP_STATE_TIMEOUT) + remove_client (self, client); } static NMDHCPClient * client_start (NMDHCPManager *self, const char *iface, + int ifindex, const GByteArray *hwaddr, const char *uuid, guint priority, @@ -390,7 +379,8 @@ client_start (NMDHCPManager *self, guint32 timeout, GByteArray *dhcp_anycast_addr, const char *hostname, - gboolean info_only) + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy) { NMDHCPManagerPrivate *priv; NMDHCPClient *client; @@ -398,7 +388,7 @@ client_start (NMDHCPManager *self, g_return_val_if_fail (self, NULL); g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL); - g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (ifindex > 0, NULL); g_return_val_if_fail (uuid != NULL, NULL); priv = NM_DHCP_MANAGER_GET_PRIVATE (self); @@ -407,32 +397,34 @@ client_start (NMDHCPManager *self, g_return_val_if_fail (priv->client_type != 0, NULL); /* Kill any old client instance */ - client = get_client_for_iface (self, iface, ipv6); + client = get_client_for_ifindex (self, ifindex, ipv6); if (client) { - nm_dhcp_client_stop (client, FALSE); + g_object_ref (client); remove_client (self, client); + nm_dhcp_client_stop (client, FALSE); + g_object_unref (client); } /* And make a new one */ client = g_object_new (priv->client_type, NM_DHCP_CLIENT_INTERFACE, iface, + NM_DHCP_CLIENT_IFINDEX, ifindex, NM_DHCP_CLIENT_HWADDR, hwaddr, NM_DHCP_CLIENT_IPV6, ipv6, NM_DHCP_CLIENT_UUID, uuid, NM_DHCP_CLIENT_PRIORITY, priority, NM_DHCP_CLIENT_TIMEOUT, timeout ? timeout : DHCP_TIMEOUT, NULL); - g_return_val_if_fail (client != NULL, NULL); - add_client (self, client); + g_hash_table_insert (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client, g_object_ref (client)); + g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self); if (ipv6) - success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, hostname, info_only); + success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, hostname, info_only, privacy); else success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname); if (!success) { remove_client (self, client); - g_object_unref (client); client = NULL; } @@ -452,51 +444,50 @@ get_send_hostname (NMDHCPManager *self, const char *setting_hostname) NMDHCPClient * nm_dhcp_manager_start_ip4 (NMDHCPManager *self, const char *iface, + int ifindex, const GByteArray *hwaddr, const char *uuid, guint priority, - NMSettingIP4Config *s_ip4, + gboolean send_hostname, + const char *dhcp_hostname, + const char *dhcp_client_id, guint32 timeout, GByteArray *dhcp_anycast_addr) { - const char *hostname = NULL, *method; - gboolean send_hostname; + const char *hostname = NULL; - g_return_val_if_fail (self, NULL); g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL); - method = nm_setting_ip4_config_get_method (s_ip4); - g_return_val_if_fail (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0, NULL); - - send_hostname = nm_setting_ip4_config_get_dhcp_send_hostname (s_ip4); if (send_hostname) - hostname = get_send_hostname (self, nm_setting_ip4_config_get_dhcp_hostname (s_ip4)); - - return client_start (self, iface, hwaddr, uuid, priority, FALSE, - nm_setting_ip4_config_get_dhcp_client_id (s_ip4), - timeout, dhcp_anycast_addr, hostname, FALSE); + hostname = get_send_hostname (self, dhcp_hostname); + return client_start (self, iface, ifindex, hwaddr, uuid, priority, FALSE, + dhcp_client_id, timeout, dhcp_anycast_addr, hostname, + FALSE, 0); } /* Caller owns a reference to the NMDHCPClient on return */ NMDHCPClient * nm_dhcp_manager_start_ip6 (NMDHCPManager *self, const char *iface, + int ifindex, const GByteArray *hwaddr, const char *uuid, guint priority, - NMSettingIP6Config *s_ip6, + const char *dhcp_hostname, guint32 timeout, GByteArray *dhcp_anycast_addr, - gboolean info_only) + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy) { const char *hostname; g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL); - hostname = get_send_hostname (self, nm_setting_ip6_config_get_dhcp_hostname (s_ip6)); + hostname = dhcp_hostname ? get_send_hostname (self, dhcp_hostname) : NULL; - return client_start (self, iface, hwaddr, uuid, priority, TRUE, - NULL, timeout, dhcp_anycast_addr, hostname, info_only); + return client_start (self, iface, ifindex, hwaddr, uuid, priority, TRUE, + NULL, timeout, dhcp_anycast_addr, hostname, info_only, + privacy); } void @@ -532,35 +523,6 @@ nm_dhcp_manager_get_lease_ip_configs (NMDHCPManager *self, return NULL; } -NMIP4Config * -nm_dhcp_manager_test_ip4_options_to_config (const char *dhcp_client, - const char *iface, - GHashTable *options, - const char *reason) -{ - NMDHCPClient *client; - NMIP4Config *config; - GType client_type; - GError *error = NULL; - - client_type = get_client_type (dhcp_client, &error); - if (!client_type) { - nm_log_err (LOGD_DHCP4, "error: %s", error ? error->message : "(unknown)"); - g_clear_error (&error); - return NULL; - } - - client = (NMDHCPClient *) g_object_new (client_type, - NM_DHCP_CLIENT_INTERFACE, iface, - NULL); - g_return_val_if_fail (client != NULL, NULL); - nm_dhcp_client_new_options (client, options, reason); - config = nm_dhcp_client_get_ip4_config (client, TRUE); - g_object_unref (client); - - return config; -} - /***************************************************/ NMDHCPManager * diff --git a/src/dhcp-manager/nm-dhcp-manager.h b/src/dhcp-manager/nm-dhcp-manager.h index efdd43788b..de162abab3 100644 --- a/src/dhcp-manager/nm-dhcp-manager.h +++ b/src/dhcp-manager/nm-dhcp-manager.h @@ -25,9 +25,6 @@ #include <glib.h> #include <glib-object.h> -#include <nm-setting-ip4-config.h> -#include <nm-setting-ip6-config.h> - #include "nm-dhcp-client.h" #include "nm-ip4-config.h" #include "nm-dhcp4-config.h" @@ -66,22 +63,27 @@ void nm_dhcp_manager_set_default_hostname (NMDHCPManager *manager, NMDHCPClient * nm_dhcp_manager_start_ip4 (NMDHCPManager *manager, const char *iface, + int ifindex, const GByteArray *hwaddr, const char *uuid, guint priority, - NMSettingIP4Config *s_ip4, + gboolean send_hostname, + const char *dhcp_hostname, + const char *dhcp_client_id, guint32 timeout, GByteArray *dhcp_anycast_addr); NMDHCPClient * nm_dhcp_manager_start_ip6 (NMDHCPManager *manager, const char *iface, + int ifindex, const GByteArray *hwaddr, const char *uuid, guint priority, - NMSettingIP6Config *s_ip6, + const char *dhcp_hostname, guint32 timeout, GByteArray *dhcp_anycast_addr, - gboolean info_only); + gboolean info_only, + NMSettingIP6ConfigPrivacy privacy); GSList * nm_dhcp_manager_get_lease_ip_configs (NMDHCPManager *self, const char *iface, @@ -89,11 +91,6 @@ GSList * nm_dhcp_manager_get_lease_ip_configs (NMDHCPManager *self, gboolean ipv6); /* For testing only */ -NMIP4Config *nm_dhcp_manager_test_ip4_options_to_config (const char *dhcp_client, - const char *iface, - GHashTable *options, - const char *reason); - extern const char* nm_dhcp_helper_path; #endif /* NM_DHCP_MANAGER_H */ diff --git a/src/dhcp-manager/nm-dhcp-utils.c b/src/dhcp-manager/nm-dhcp-utils.c new file mode 100644 index 0000000000..48330e5113 --- /dev/null +++ b/src/dhcp-manager/nm-dhcp-utils.c @@ -0,0 +1,685 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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) 2005 - 2010 Red Hat, Inc. + * + */ + +#include <config.h> +#include <glib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include "nm-logging.h" +#include "nm-dhcp-utils.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" + +/********************************************/ + +static gboolean +ip4_process_dhcpcd_rfc3442_routes (const char *str, + guint priority, + NMIP4Config *ip4_config, + guint32 *gwaddr) +{ + char **routes, **r; + gboolean have_routes = FALSE; + + routes = g_strsplit (str, " ", 0); + if (g_strv_length (routes) == 0) + goto out; + + if ((g_strv_length (routes) % 2) != 0) { + nm_log_warn (LOGD_DHCP4, " classless static routes provided, but invalid"); + goto out; + } + + for (r = routes; *r; r += 2) { + char *slash; + NMPlatformIP4Route route; + int rt_cidr = 32; + guint32 rt_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_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route cidr: '%s'", slash + 1); + continue; + } + } + if (inet_pton (AF_INET, *r, &rt_addr) <= 0) { + nm_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route address: '%s'", *r); + continue; + } + if (inet_pton (AF_INET, *(r + 1), &rt_route) <= 0) { + nm_log_warn (LOGD_DHCP4, "DHCP provided invalid classless static route gateway: '%s'", *(r + 1)); + continue; + } + + have_routes = TRUE; + if (rt_cidr == 0 && rt_addr == 0) { + /* FIXME: how to handle multiple routers? */ + *gwaddr = rt_route; + } else { + nm_log_info (LOGD_DHCP4, " classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1)); + memset (&route, 0, sizeof (route)); + route.network = rt_addr; + route.plen = rt_cidr; + route.gateway = rt_route; + route.source = NM_PLATFORM_SOURCE_DHCP; + route.metric = priority; + nm_ip4_config_add_route (ip4_config, &route); + } + } + +out: + g_strfreev (routes); + return have_routes; +} + +static const char ** +process_dhclient_rfc3442_route (const char **octets, + NMPlatformIP4Route *route, + gboolean *success) +{ + const char **o = octets; + int addr_len = 0, i = 0; + long int tmp; + char *next_hop; + guint32 tmp_addr; + + *success = FALSE; + + if (!*o) + return o; /* no prefix */ + + tmp = strtol (*o, NULL, 10); + if (tmp < 0 || tmp > 32) /* 32 == max IP4 prefix length */ + return o; + + memset (route, 0, sizeof (*route)); + route->plen = 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 &= nm_utils_ip4_prefix_to_netmask ((guint32) tmp); + route->network = tmp_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; + } + route->gateway = tmp_addr; + g_free (next_hop); + + *success = TRUE; + return o + 4; /* advance to past the next hop */ + +error: + return o; +} + +static gboolean +ip4_process_dhclient_rfc3442_routes (const char *str, + guint priority, + NMIP4Config *ip4_config, + guint32 *gwaddr) +{ + char **octets, **o; + gboolean have_routes = FALSE; + NMPlatformIP4Route route; + gboolean success; + + o = octets = g_strsplit_set (str, " .", 0); + if (g_strv_length (octets) < 5) { + nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes '%s'", str); + goto out; + } + + while (*o) { + memset (&route, 0, sizeof (route)); + o = (char **) process_dhclient_rfc3442_route ((const char **) o, &route, &success); + if (!success) { + nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes"); + break; + } + + have_routes = TRUE; + if (!route.plen) { + /* gateway passed as classless static route */ + *gwaddr = route.gateway; + } else { + char addr[INET_ADDRSTRLEN]; + + /* normal route */ + route.source = NM_PLATFORM_SOURCE_DHCP; + route.metric = priority; + nm_ip4_config_add_route (ip4_config, &route); + + nm_log_info (LOGD_DHCP4, " classless static route %s/%d gw %s", + nm_utils_inet4_ntop (route.network, addr), route.plen, + nm_utils_inet4_ntop (route.gateway, NULL)); + } + } + +out: + g_strfreev (octets); + return have_routes; +} + +static gboolean +ip4_process_classless_routes (GHashTable *options, + guint priority, + NMIP4Config *ip4_config, + guint32 *gwaddr) +{ + const char *str, *p; + + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (ip4_config != NULL, FALSE); + + *gwaddr = 0; + + /* dhcpd/dhclient in Fedora has support for rfc3442 implemented using a + * slightly different format: + * + * option classless-static-routes = array of (destination-descriptor ip-address); + * + * which results in: + * + * 0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6 + * + * dhcpcd supports classless static routes natively and uses this same + * option identifier with the following format: + * + * 192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41 + */ + str = g_hash_table_lookup (options, "classless_static_routes"); + + /* 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 + */ + if (!str) + str = g_hash_table_lookup (options, "rfc3442_classless_static_routes"); + + /* Microsoft version; same as rfc3442 but with a different option # (249) */ + if (!str) + str = g_hash_table_lookup (options, "ms_classless_static_routes"); + + if (!str || !strlen (str)) + return FALSE; + + p = str; + while (*p) { + if (!g_ascii_isdigit (*p) && (*p != ' ') && (*p != '.') && (*p != '/')) { + nm_log_warn (LOGD_DHCP4, "ignoring invalid classless static routes '%s'", str); + return FALSE; + } + p++; + }; + + if (strchr (str, '/')) { + /* dhcpcd format */ + return ip4_process_dhcpcd_rfc3442_routes (str, priority, ip4_config, gwaddr); + } + + return ip4_process_dhclient_rfc3442_routes (str, priority, ip4_config, gwaddr); +} + +static void +process_classful_routes (GHashTable *options, guint priority, NMIP4Config *ip4_config) +{ + const char *str; + char **searches, **s; + + str = g_hash_table_lookup (options, "static_routes"); + if (!str) + return; + + searches = g_strsplit (str, " ", 0); + if ((g_strv_length (searches) % 2)) { + nm_log_info (LOGD_DHCP, " static routes provided, but invalid"); + goto out; + } + + for (s = searches; *s; s += 2) { + NMPlatformIP4Route route; + guint32 rt_addr, rt_route; + + if (inet_pton (AF_INET, *s, &rt_addr) <= 0) { + nm_log_warn (LOGD_DHCP, "DHCP provided invalid static route address: '%s'", *s); + continue; + } + if (inet_pton (AF_INET, *(s + 1), &rt_route) <= 0) { + nm_log_warn (LOGD_DHCP, "DHCP provided invalid static route gateway: '%s'", *(s + 1)); + continue; + } + + // FIXME: ensure the IP address and route are sane + + memset (&route, 0, sizeof (route)); + route.network = rt_addr; + /* RFC 2132, updated by RFC 3442: + The Static Routes option (option 33) does not provide a subnet mask + for each route - it is assumed that the subnet mask is implicit in + whatever network number is specified in each route entry */ + route.plen = nm_utils_ip4_get_default_prefix (rt_addr); + if (rt_addr & ~nm_utils_ip4_prefix_to_netmask (route.plen)) { + /* RFC 943: target not "this network"; using host routing */ + route.plen = 32; + } + route.gateway = rt_route; + route.source = NM_PLATFORM_SOURCE_DHCP; + route.metric = priority; + + nm_ip4_config_add_route (ip4_config, &route); + nm_log_info (LOGD_DHCP, " static route %s", + nm_platform_ip4_route_to_string (&route)); + } + +out: + g_strfreev (searches); +} + +static void +process_domain_search (const char *str, GFunc add_func, gpointer user_data) +{ + char **searches, **s; + char *unescaped, *p; + int i; + + g_return_if_fail (str != NULL); + g_return_if_fail (add_func != NULL); + + p = unescaped = g_strdup (str); + do { + p = strstr (p, "\\032"); + if (!p) + break; + + /* Clear the escaped space with real spaces */ + for (i = 0; i < 4; i++) + *p++ = ' '; + } while (*p++); + + if (strchr (unescaped, '\\')) { + nm_log_warn (LOGD_DHCP, " invalid domain search: '%s'", unescaped); + goto out; + } + + searches = g_strsplit (unescaped, " ", 0); + for (s = searches; *s; s++) { + if (strlen (*s)) { + nm_log_info (LOGD_DHCP, " domain search '%s'", *s); + add_func (*s, user_data); + } + } + g_strfreev (searches); + +out: + g_free (unescaped); +} + +static void +ip4_add_domain_search (gpointer data, gpointer user_data) +{ + nm_ip4_config_add_search (NM_IP4_CONFIG (user_data), (const char *) data); +} + +NMIP4Config * +nm_dhcp_utils_ip4_config_from_options (const char *iface, + GHashTable *options, + guint priority) +{ + NMIP4Config *ip4_config = NULL; + guint32 tmp_addr; + NMPlatformIP4Address address; + char *str = NULL; + guint32 gwaddr = 0, plen = 0; + + g_return_val_if_fail (options != NULL, NULL); + + ip4_config = nm_ip4_config_new (); + memset (&address, 0, sizeof (address)); + address.timestamp = nm_utils_get_monotonic_timestamp_s (); + + str = g_hash_table_lookup (options, "ip_address"); + if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { + address.address = tmp_addr; + nm_log_info (LOGD_DHCP4, " address %s", str); + } else + goto error; + + str = g_hash_table_lookup (options, "subnet_mask"); + if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) { + plen = nm_utils_ip4_netmask_to_prefix (tmp_addr); + nm_log_info (LOGD_DHCP4, " plen %d (%s)", plen, str); + } else { + /* Get default netmask for the IP according to appropriate class. */ + plen = nm_utils_ip4_get_default_prefix (address.address); + nm_log_info (LOGD_DHCP4, " plen %d (default)", plen); + } + address.plen = plen; + + /* Routes: if the server returns classless static routes, we MUST ignore + * the 'static_routes' option. + */ + if (!ip4_process_classless_routes (options, priority, ip4_config, &gwaddr)) + process_classful_routes (options, priority, ip4_config); + + if (gwaddr) { + nm_log_info (LOGD_DHCP4, " gateway %s", nm_utils_inet4_ntop (gwaddr, NULL)); + nm_ip4_config_set_gateway (ip4_config, 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, "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, &gwaddr) > 0) { + nm_ip4_config_set_gateway (ip4_config, gwaddr); + nm_log_info (LOGD_DHCP4, " gateway %s", *s); + break; + } else + nm_log_warn (LOGD_DHCP4, "ignoring invalid gateway '%s'", *s); + } + g_strfreev (routers); + } + } + + /* + * RFC 2132, section 9.7 + * DHCP clients use the contents of the 'server identifier' field + * as the destination address for any DHCP messages unicast to + * the DHCP server. + * + * Some ISP's provide leases from central servers that are on + * different subnets that the address offered. If the host + * does not configure the interface as the default route, the + * dhcp server may not be reachable via unicast, and a host + * specific route is needed. + **/ + str = g_hash_table_lookup (options, "dhcp_server_identifier"); + if (str) { + if (inet_pton (AF_INET, str, &tmp_addr) > 0) { + NMPlatformIP4Route route; + guint32 mask = nm_utils_ip4_prefix_to_netmask (address.plen); + + nm_log_info (LOGD_DHCP4, " server identifier %s", str); + if ((tmp_addr & mask) != (address.address & mask)) { + /* DHCP server not on assigned subnet, route needed */ + memset (&route, 0, sizeof (route)); + route.network = tmp_addr; + route.plen = 32; + /* this will be a device route if gwaddr is 0 */ + route.gateway = gwaddr; + route.source = NM_PLATFORM_SOURCE_DHCP; + route.metric = priority; + nm_ip4_config_add_route (ip4_config, &route); + nm_log_dbg (LOGD_IP, "adding route for server identifier: %s", + nm_platform_ip4_route_to_string (&route)); + } + } + else + nm_log_warn (LOGD_DHCP4, "ignoring invalid server identifier '%s'", str); + } + + str = g_hash_table_lookup (options, "dhcp_lease_time"); + if (str) { + address.lifetime = address.preferred = strtoul (str, NULL, 10); + nm_log_info (LOGD_DHCP4, " lease time %d", address.lifetime); + } + + address.source = NM_PLATFORM_SOURCE_DHCP; + nm_ip4_config_add_address (ip4_config, &address); + + str = g_hash_table_lookup (options, "host_name"); + if (str) + nm_log_info (LOGD_DHCP4, " hostname '%s'", str); + + str = g_hash_table_lookup (options, "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); + nm_log_info (LOGD_DHCP4, " nameserver '%s'", *s); + } else + nm_log_warn (LOGD_DHCP4, "ignoring invalid nameserver '%s'", *s); + } + g_strfreev (searches); + } + + str = g_hash_table_lookup (options, "domain_name"); + if (str) { + char **domains = g_strsplit (str, " ", 0); + char **s; + + for (s = domains; *s; s++) { + nm_log_info (LOGD_DHCP4, " domain name '%s'", *s); + nm_ip4_config_add_domain (ip4_config, *s); + } + g_strfreev (domains); + } + + str = g_hash_table_lookup (options, "domain_search"); + if (str) + process_domain_search (str, ip4_add_domain_search, ip4_config); + + str = g_hash_table_lookup (options, "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); + nm_log_info (LOGD_DHCP4, " wins '%s'", *s); + } else + nm_log_warn (LOGD_DHCP4, "ignoring invalid WINS server '%s'", *s); + } + g_strfreev (searches); + } + + str = g_hash_table_lookup (options, "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 > 576) + nm_ip4_config_set_mtu (ip4_config, int_mtu); + } + + str = g_hash_table_lookup (options, "nis_domain"); + if (str) { + nm_log_info (LOGD_DHCP4, " NIS domain '%s'", str); + nm_ip4_config_set_nis_domain (ip4_config, str); + } + + str = g_hash_table_lookup (options, "nis_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_nis_server (ip4_config, tmp_addr); + nm_log_info (LOGD_DHCP4, " nis '%s'", *s); + } else + nm_log_warn (LOGD_DHCP4, "ignoring invalid NIS server '%s'", *s); + } + g_strfreev (searches); + } + + return ip4_config; + +error: + g_object_unref (ip4_config); + return NULL; +} + +/********************************************/ + +static void +ip6_add_domain_search (gpointer data, gpointer user_data) +{ + nm_ip6_config_add_search (NM_IP6_CONFIG (user_data), (const char *) data); +} + +NMIP6Config * +nm_dhcp_utils_ip6_config_from_options (const char *iface, + GHashTable *options, + guint priority, + gboolean info_only) +{ + NMIP6Config *ip6_config = NULL; + struct in6_addr tmp_addr; + NMPlatformIP6Address address; + char *str = NULL; + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (options != NULL, NULL); + + memset (&address, 0, sizeof (address)); + address.plen = 128; + address.timestamp = nm_utils_get_monotonic_timestamp_s (); + + g_hash_table_iter_init (&iter, options); + while (g_hash_table_iter_next (&iter, &key, &value)) { + nm_log_dbg (LOGD_DHCP6, "(%s): option '%s'=>'%s'", + iface, (const char *) key, (const char *) value); + } + + ip6_config = nm_ip6_config_new (); + + str = g_hash_table_lookup (options, "max_life"); + if (str) { + address.lifetime = strtoul (str, NULL, 10); + nm_log_info (LOGD_DHCP6, " valid_lft %d", address.lifetime); + } + + str = g_hash_table_lookup (options, "preferred_life"); + if (str) { + address.preferred = strtoul (str, NULL, 10); + nm_log_info (LOGD_DHCP6, " preferred_lft %d", address.preferred); + } + + str = g_hash_table_lookup (options, "ip6_address"); + if (str) { + if (!inet_pton (AF_INET6, str, &tmp_addr)) { + nm_log_warn (LOGD_DHCP6, "(%s): DHCP returned invalid address '%s'", + iface, str); + goto error; + } + + address.address = tmp_addr; + address.source = NM_PLATFORM_SOURCE_DHCP; + nm_ip6_config_add_address (ip6_config, &address); + nm_log_info (LOGD_DHCP6, " address %s", str); + } else if (info_only == FALSE) { + /* No address in Managed mode is a hard error */ + goto error; + } + + str = g_hash_table_lookup (options, "host_name"); + if (str) + nm_log_info (LOGD_DHCP6, " hostname '%s'", str); + + str = g_hash_table_lookup (options, "dhcp6_name_servers"); + if (str) { + char **searches = g_strsplit (str, " ", 0); + char **s; + + for (s = searches; *s; s++) { + if (inet_pton (AF_INET6, *s, &tmp_addr) > 0) { + nm_ip6_config_add_nameserver (ip6_config, &tmp_addr); + nm_log_info (LOGD_DHCP6, " nameserver '%s'", *s); + } else + nm_log_warn (LOGD_DHCP6, "ignoring invalid nameserver '%s'", *s); + } + g_strfreev (searches); + } + + str = g_hash_table_lookup (options, "dhcp6_domain_search"); + if (str) + process_domain_search (str, ip6_add_domain_search, ip6_config); + + return ip6_config; + +error: + g_object_unref (ip6_config); + return NULL; +} + +char * +nm_dhcp_utils_duid_to_string (const GByteArray *duid) +{ + guint32 i = 0; + GString *s; + + g_return_val_if_fail (duid != NULL, NULL); + + s = g_string_sized_new (MIN (duid->len * 3, 50)); + while (i < duid->len) { + if (s->len) + g_string_append_c (s, ':'); + g_string_append_printf (s, "%02x", duid->data[i++]); + } + return g_string_free (s, FALSE); +} + diff --git a/src/dhcp-manager/nm-dhcp-utils.h b/src/dhcp-manager/nm-dhcp-utils.h new file mode 100644 index 0000000000..be6b28106a --- /dev/null +++ b/src/dhcp-manager/nm-dhcp-utils.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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) 2014 Red Hat, Inc. + */ + +#ifndef NM_DHCP_UTILS_H +#define NM_DHCP_UTILS_H + +#include <stdlib.h> +#include <glib.h> +#include <nm-ip4-config.h> +#include <nm-ip6-config.h> + +NMIP4Config *nm_dhcp_utils_ip4_config_from_options (const char *iface, + GHashTable *options, + guint priority); + +NMIP6Config *nm_dhcp_utils_ip6_config_from_options (const char *iface, + GHashTable *options, + guint priority, + gboolean info_only); + +char * nm_dhcp_utils_duid_to_string (const GByteArray *duid); + +#endif /* NM_DHCP_UTILS_H */ + diff --git a/src/dhcp-manager/tests/Makefile.am b/src/dhcp-manager/tests/Makefile.am index 4701b3f1b9..e9f9a61013 100644 --- a/src/dhcp-manager/tests/Makefile.am +++ b/src/dhcp-manager/tests/Makefile.am @@ -3,6 +3,7 @@ AM_CPPFLAGS = \ -I${top_srcdir}/libnm-util \ -I${top_builddir}/libnm-util \ -I$(top_srcdir)/src/dhcp-manager \ + -I$(top_srcdir)/src/logging \ -I$(top_srcdir)/src \ -I$(top_srcdir)/src/platform \ -DG_LOG_DOMAIN=\""NetworkManager"\" \ @@ -10,9 +11,11 @@ AM_CPPFLAGS = \ $(GLIB_CFLAGS) \ -DTESTDIR="\"$(abs_srcdir)\"" -noinst_PROGRAMS = test-dhcp-dhclient +noinst_PROGRAMS = \ + test-dhcp-dhclient \ + test-dhcp-options -####### policy /etc/hosts test ####### +####### dhclient leases test ####### test_dhcp_dhclient_SOURCES = \ test-dhcp-dhclient.c @@ -20,7 +23,17 @@ test_dhcp_dhclient_SOURCES = \ test_dhcp_dhclient_LDADD = \ $(top_builddir)/src/libNetworkManager.la -TESTS = test-dhcp-dhclient +####### DHCP options test ####### + +test_dhcp_options_SOURCES = \ + test-dhcp-options.c + +test_dhcp_options_LDADD = \ + $(top_builddir)/src/libNetworkManager.la + +################################# + +TESTS = test-dhcp-dhclient test-dhcp-options EXTRA_DIST = \ test-dhclient-duid.leases \ diff --git a/src/dhcp-manager/tests/test-dhcp-options.c b/src/dhcp-manager/tests/test-dhcp-options.c new file mode 100644 index 0000000000..ac6c22a6c3 --- /dev/null +++ b/src/dhcp-manager/tests/test-dhcp-options.c @@ -0,0 +1,683 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* 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 - 2014 Red Hat, Inc. + * + */ + +#include <glib.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include <nm-utils.h> + +#include "nm-dhcp-utils.h" +#include "nm-logging.h" + +#include "nm-test-utils.h" + +typedef struct { + const char *name; + const char *value; +} Option; + +static GHashTable * +fill_table (const Option *test_options, GHashTable *table) +{ + const Option *opt; + + if (!table) + table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + for (opt = test_options; opt->name; opt++) + g_hash_table_insert (table, (gpointer) opt->name, (gpointer) opt->value); + return table; +} + +static const Option generic_options[] = { + { "subnet_mask", "255.255.255.0" }, + { "ip_address", "192.168.1.106" }, + { "network_number", "192.168.1.0" }, + { "expiry", "1232324877" }, + { "dhcp_lease_time", "3600" }, + { "dhcp_server_identifier", "192.168.1.1" }, + { "routers", "192.168.1.1" }, + { "domain_name_servers", "216.254.95.2 216.231.41.2" }, + { "dhcp_message_type", "5" }, + { "broadcast_address", "192.168.1.255" }, + { "domain_search", "foobar.com blah.foobar.com" }, + { "host_name", "nmreallywhipsthe" }, + { "domain_name", "lamasass.com" }, + { "interface_mtu", "987" }, + { "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; + const NMPlatformIP4Address *address; + const NMPlatformIP4Route *route; + guint32 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_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* IP4 address */ + g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1); + address = nm_ip4_config_get_address (ip4_config, 0); + g_assert (inet_pton (AF_INET, expected_addr, &tmp) > 0); + g_assert (address->address == tmp); + g_assert (address->peer_address == 0); + g_assert_cmpint (address->plen, ==, 24); + + /* Gateway */ + g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0); + g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp); + + g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 0); + + g_assert_cmpint (nm_ip4_config_get_mtu (ip4_config), ==, 987); + + /* Domain searches */ + g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 2); + g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 0), ==, expected_search1); + g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 1), ==, expected_search2); + + /* DNS servers */ + g_assert_cmpint (nm_ip4_config_get_num_nameservers (ip4_config), ==, 2); + g_assert (inet_pton (AF_INET, expected_dns1, &tmp) > 0); + g_assert (nm_ip4_config_get_nameserver (ip4_config, 0) == tmp); + g_assert (inet_pton (AF_INET, expected_dns2, &tmp) > 0); + g_assert (nm_ip4_config_get_nameserver (ip4_config, 1) == tmp); + + /* Routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2); + + /* Route #1 */ + route = nm_ip4_config_get_route (ip4_config, 0); + g_assert (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0); + g_assert (route->network == tmp); + g_assert (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0); + g_assert (route->gateway == tmp); + g_assert_cmpint (route->plen, ==, 32); + g_assert_cmpint (route->metric, ==, 0); + + /* Route #2 */ + route = nm_ip4_config_get_route (ip4_config, 1); + g_assert (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0); + g_assert (route->network == tmp); + g_assert (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0); + g_assert (route->gateway == tmp); + g_assert_cmpint (route->plen, ==, 32); + g_assert_cmpint (route->metric, ==, 0); + + g_hash_table_destroy (options); +} + +static void +test_wins_options (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const NMPlatformIP4Address *address; + guint32 tmp; + const char *expected_wins1 = "63.12.199.5"; + const char *expected_wins2 = "150.4.88.120"; + static const Option data[] = { + { "netbios_name_servers", "63.12.199.5 150.4.88.120" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* IP4 address */ + g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1); + address = nm_ip4_config_get_address (ip4_config, 0); + g_assert (address); + g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 2); + g_assert (inet_pton (AF_INET, expected_wins1, &tmp) > 0); + g_assert (nm_ip4_config_get_wins (ip4_config, 0) == tmp); + g_assert (inet_pton (AF_INET, expected_wins2, &tmp) > 0); + g_assert (nm_ip4_config_get_wins (ip4_config, 1) == tmp); + + g_hash_table_destroy (options); +} + +static void +ip4_test_route (NMIP4Config *ip4_config, + guint route_num, + const char *expected_dest, + const char *expected_gw, + guint expected_prefix) +{ + const NMPlatformIP4Route *route; + guint32 tmp; + + route = nm_ip4_config_get_route (ip4_config, route_num); + g_assert (inet_pton (AF_INET, expected_dest, &tmp) > 0); + g_assert (route->network == tmp); + g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0); + g_assert (route->gateway == tmp); + g_assert_cmpint (route->plen, ==, expected_prefix); + g_assert_cmpint (route->metric, ==, 0); +} + +static void +ip4_test_gateway (NMIP4Config *ip4_config, const char *expected_gw) +{ + guint32 tmp; + + g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1); + g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0); + g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp); +} + +static void +test_classless_static_routes_1 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + 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"; + static const Option data[] = { + /* dhclient custom format */ + { "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 8 10 10 17 66 41" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8); + + g_hash_table_destroy (options); +} + +static void +test_classless_static_routes_2 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + 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"; + static const Option data[] = { + /* dhcpcd format */ + { "classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8); + + g_hash_table_destroy (options); +} + +static void +test_fedora_dhclient_classless_static_routes (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const char *expected_route1_dest = "129.210.177.128"; + const char *expected_route1_gw = "192.168.0.113"; + const char *expected_route2_dest = "2.0.0.0"; + const char *expected_route2_gw = "10.34.255.6"; + const char *expected_gateway = "192.168.0.113"; + static const Option data[] = { + /* Fedora dhclient format */ + { "classless_static_routes", "0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 25); + ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 7); + + /* Gateway */ + ip4_test_gateway (ip4_config, expected_gateway); + + g_hash_table_destroy (options); +} + +static void +test_dhclient_invalid_classless_routes_1 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const char *expected_route1_dest = "192.168.10.0"; + const char *expected_route1_gw = "192.168.1.1"; + static const Option data[] = { + /* dhclient format */ + { "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 45 10 17 66 41" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "*ignoring invalid classless static routes*"); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + g_test_assert_expected_messages (); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + + g_hash_table_destroy (options); +} + +static void +test_dhcpcd_invalid_classless_routes_1 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + 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"; + static const Option data[] = { + /* dhcpcd format */ + { "classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.adfadf/44 10.17.66.41" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "*ignoring invalid classless static routes*"); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + g_test_assert_expected_messages (); + + /* Test falling back to old-style static routes if the classless static + * routes are invalid. + */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32); + ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32); + + g_hash_table_destroy (options); +} + +static void +test_dhclient_invalid_classless_routes_2 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + 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"; + static const Option data[] = { + { "rfc3442_classless_static_routes", "45 10 17 66 41 24 192 168 10 192 168 1 1" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "*ignoring invalid classless static routes*"); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + g_test_assert_expected_messages (); + + /* Test falling back to old-style static routes if the classless static + * routes are invalid. + */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32); + ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32); + + g_hash_table_destroy (options); +} + +static void +test_dhcpcd_invalid_classless_routes_2 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + 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"; + static const Option data[] = { + { "classless_static_routes", "10.0.adfadf/44 10.17.66.41 192.168.10.0/24 192.168.1.1" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "*ignoring invalid classless static routes*"); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + g_test_assert_expected_messages (); + + /* Test falling back to old-style static routes if the classless static + * routes are invalid. + */ + + /* Routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32); + ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32); + + g_hash_table_destroy (options); +} + +static void +test_dhclient_invalid_classless_routes_3 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const char *expected_route1_dest = "192.168.10.0"; + const char *expected_route1_gw = "192.168.1.1"; + static const Option data[] = { + { "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 32 128 10 17 66 41" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "*ignoring invalid classless static routes*"); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + g_test_assert_expected_messages (); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + + g_hash_table_destroy (options); +} + +static void +test_dhcpcd_invalid_classless_routes_3 (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const char *expected_route1_dest = "192.168.10.0"; + const char *expected_route1_gw = "192.168.1.1"; + static Option data[] = { + { "classless_static_routes", "192.168.10.0/24 192.168.1.1 128/32 10.17.66.41" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "*DHCP provided invalid classless static route*"); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + g_test_assert_expected_messages (); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + + g_hash_table_destroy (options); +} + +static void +test_dhclient_gw_in_classless_routes (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + 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"; + static Option data[] = { + { "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 0 192 2 3 4" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + + /* Gateway */ + ip4_test_gateway (ip4_config, expected_gateway); + + g_hash_table_destroy (options); +} + +static void +test_dhcpcd_gw_in_classless_routes (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + 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"; + static Option data[] = { + { "classless_static_routes", "192.168.10.0/24 192.168.1.1 0.0.0.0/0 192.2.3.4" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* IP4 routes */ + g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1); + ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24); + + /* Gateway */ + ip4_test_gateway (ip4_config, expected_gateway); + + g_hash_table_destroy (options); +} + +static void +test_escaped_domain_searches (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const char *expected_search0 = "host1"; + const char *expected_search1 = "host2"; + const char *expected_search2 = "host3"; + static const Option data[] = { + { "domain_search", "host1\\032host2\\032host3" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + /* domain searches */ + g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 3); + g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 0), ==, expected_search0); + g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 1), ==, expected_search1); + g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 2), ==, expected_search2); + + g_hash_table_destroy (options); +} + +static void +test_invalid_escaped_domain_searches (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + static const Option data[] = { + { "domain_search", "host1\\aahost2\\032host3" }, + { NULL, NULL } + }; + + options = fill_table (generic_options, NULL); + options = fill_table (data, options); + + g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, + "*invalid domain search*"); + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + g_test_assert_expected_messages (); + + /* domain searches */ + g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 0); + + g_hash_table_destroy (options); +} + +static void +test_ip4_missing_prefix (const char *ip, guint32 expected_prefix) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const NMPlatformIP4Address *address; + + options = fill_table (generic_options, NULL); + g_hash_table_insert (options, "ip_address", (gpointer) ip); + g_hash_table_remove (options, "subnet_mask"); + + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1); + address = nm_ip4_config_get_address (ip4_config, 0); + g_assert (address); + g_assert_cmpint (address->plen, ==, expected_prefix); + + g_hash_table_destroy (options); +} + +static void +test_ip4_missing_prefix_24 (void) +{ + test_ip4_missing_prefix ("192.168.1.10", 24); +} + +static void +test_ip4_missing_prefix_16 (void) +{ + test_ip4_missing_prefix ("172.16.54.50", 16); +} + +static void +test_ip4_missing_prefix_8 (void) +{ + test_ip4_missing_prefix ("10.1.2.3", 8); +} + +static void +test_ip4_prefix_classless (void) +{ + GHashTable *options; + NMIP4Config *ip4_config; + const NMPlatformIP4Address *address; + + /* Ensure that the missing-subnet-mask handler doesn't mangle classless + * subnet masks at all. The handler should trigger only if the server + * doesn't send the subnet mask. + */ + + options = fill_table (generic_options, NULL); + g_hash_table_insert (options, "ip_address", "172.16.54.22"); + g_hash_table_insert (options, "subnet_mask", "255.255.252.0"); + + ip4_config = nm_dhcp_utils_ip4_config_from_options ("eth0", options, 0); + g_assert (ip4_config); + + g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1); + address = nm_ip4_config_get_address (ip4_config, 0); + g_assert (address); + g_assert_cmpint (address->plen, ==, 22); + + g_hash_table_destroy (options); +} + +NMTST_DEFINE (); + +int main (int argc, char **argv) +{ + nmtst_init_assert_logging (&argc, &argv); + nm_logging_setup ("WARN", "DEFAULT", NULL, NULL); + + g_test_add_func ("/dhcp/generic-options", test_generic_options); + g_test_add_func ("/dhcp/wins-options", test_wins_options); + g_test_add_func ("/dhcp/classless-static-routes-1", test_classless_static_routes_1); + g_test_add_func ("/dhcp/classless-static-routes-2", test_classless_static_routes_2); + g_test_add_func ("/dhcp/fedora-dhclient-classless-static-routes", test_fedora_dhclient_classless_static_routes); + g_test_add_func ("/dhcp/dhclient-invalid-classless-routes-1", test_dhclient_invalid_classless_routes_1); + g_test_add_func ("/dhcp/dhcpcd-invalid-classless-routes-1", test_dhcpcd_invalid_classless_routes_1); + g_test_add_func ("/dhcp/dhclient-invalid-classless-routes-2", test_dhclient_invalid_classless_routes_2); + g_test_add_func ("/dhcp/dhcpcd-invalid-classless-routes-2", test_dhcpcd_invalid_classless_routes_2); + g_test_add_func ("/dhcp/dhclient-invalid-classless-routes-3", test_dhclient_invalid_classless_routes_3); + g_test_add_func ("/dhcp/dhcpcd-invalid-classless-routes-3", test_dhcpcd_invalid_classless_routes_3); + g_test_add_func ("/dhcp/dhclient-gw-in-classless-routes", test_dhclient_gw_in_classless_routes); + g_test_add_func ("/dhcp/dhcpcd-gw-in-classless-routes", test_dhcpcd_gw_in_classless_routes); + g_test_add_func ("/dhcp/escaped-domain-searches", test_escaped_domain_searches); + g_test_add_func ("/dhcp/invalid-escaped-domain-searches", test_invalid_escaped_domain_searches); + g_test_add_func ("/dhcp/ip4-missing-prefix-24", test_ip4_missing_prefix_24); + g_test_add_func ("/dhcp/ip4-missing-prefix-16", test_ip4_missing_prefix_16); + g_test_add_func ("/dhcp/ip4-missing-prefix-8", test_ip4_missing_prefix_8); + g_test_add_func ("/dhcp/ip4-prefix-classless", test_ip4_prefix_classless); + + return g_test_run (); +} + diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index 2370d57110..75e93f20ee 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -14,7 +14,6 @@ AM_CPPFLAGS = \ $(DBUS_CFLAGS) noinst_PROGRAMS = \ - test-dhcp-options \ test-general \ test-general-with-expect \ test-ip4-config \ @@ -22,19 +21,6 @@ noinst_PROGRAMS = \ test-dcb \ test-resolvconf-capture -####### DHCP options test ####### - -test_dhcp_options_SOURCES = \ - test-dhcp-options.c - -test_dhcp_options_CPPFLAGS = \ - $(AM_CPPFLAGS) \ - -DDHCLIENT_PATH=\"$(DHCLIENT_PATH)\" \ - -DDHCPCD_PATH=\"$(DHCPCD_PATH)\" - -test_dhcp_options_LDADD = \ - $(top_builddir)/src/libNetworkManager.la - ####### ip4 config test ####### test_ip4_config_SOURCES = \ diff --git a/src/tests/test-dhcp-options.c b/src/tests/test-dhcp-options.c deleted file mode 100644 index 5df17e587c..0000000000 --- a/src/tests/test-dhcp-options.c +++ /dev/null @@ -1,937 +0,0 @@ -/* -*- 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 - 2011 Red Hat, Inc. - * - */ - -#include <glib.h> -#include <dbus/dbus-glib.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <string.h> - -#include <nm-utils.h> - -#include "nm-dhcp-manager.h" -#include "nm-logging.h" - -#include "nm-test-utils.h" - -typedef struct { - const char *name; - const char *value; -} Option; - -static void -destroy_gvalue (gpointer data) -{ - GValue *value = (GValue *) data; - - g_value_unset (value); - g_slice_free (GValue, value); -} - -static GValue * -string_to_byte_array_gvalue (const char *str) -{ - GByteArray *array; - GValue *val; - - array = g_byte_array_sized_new (strlen (str)); - g_byte_array_append (array, (const guint8 *) str, strlen (str)); - - val = g_slice_new0 (GValue); - g_value_init (val, DBUS_TYPE_G_UCHAR_ARRAY); - g_value_take_boxed (val, array); - - return val; -} - -static GHashTable * -fill_table (Option *test_options, GHashTable *table) -{ - Option *opt; - - if (!table) - table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, destroy_gvalue); - for (opt = test_options; opt->name; opt++) { - g_hash_table_insert (table, - (gpointer) opt->name, - string_to_byte_array_gvalue (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 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const NMPlatformIP4Address *address; - const NMPlatformIP4Route *route; - guint32 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_test_ip4_options_to_config (client, "eth0", options, "rebind"); - 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"); - address = 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 (address->address == tmp, - "dhcp-generic", "unexpected IP address"); - ASSERT (address->peer_address == 0, - "dhcp-generic", "unexpected PTP address"); - - ASSERT (address->plen == 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_config_get_gateway (ip4_config) == tmp, - "dhcp-generic", "unexpected IP gateway"); - - 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, - "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, - "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 (route->network == tmp, - "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 (route->gateway == tmp, - "dhcp-generic", "unexpected route #1 next hop"); - - ASSERT (route->plen == 32, - "dhcp-generic", "unexpected route #1 prefix"); - ASSERT (route->metric == 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 (route->network == tmp, - "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 (route->gateway == tmp, - "dhcp-generic", "unexpected route #2 next hop"); - - ASSERT (route->plen == 32, - "dhcp-generic", "unexpected route #2 prefix"); - ASSERT (route->metric == 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 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const NMPlatformIP4Address *address; - guint32 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_test_ip4_options_to_config (client, "eth0", options, "rebind"); - 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"); - address = nm_ip4_config_get_address (ip4_config, 0); - ASSERT (address != NULL, "dhcp-wins", "unexpectedly did not get address #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, - "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, - "dhcp-wins", "unexpected WINS server #1"); - - g_hash_table_destroy (options); -} - -static void -ip4_test_route (const char *test, - NMIP4Config *ip4_config, - guint route_num, - const char *expected_dest, - const char *expected_gw, - guint expected_prefix) -{ - const NMPlatformIP4Route *route; - guint32 tmp; - - route = nm_ip4_config_get_route (ip4_config, route_num); - ASSERT (inet_pton (AF_INET, expected_dest, &tmp) > 0, - test, "couldn't convert expected route destination #1"); - ASSERT (route->network == tmp, - test, "unexpected route %d destination", route_num + 1); - - ASSERT (inet_pton (AF_INET, expected_gw, &tmp) > 0, - test, "couldn't convert expected route next hop %d", - route_num + 1); - ASSERT (route->gateway == tmp, - test, "unexpected route %d next hop", route_num + 1); - - ASSERT (route->plen == expected_prefix, - test, "unexpected route %d prefix", route_num + 1); - ASSERT (route->metric == 0, - test, "unexpected route %d metric", route_num + 1); -} - -static void -ip4_test_gateway (const char *test, - NMIP4Config *ip4_config, - const char *expected_gw) -{ - guint32 tmp; - - ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1, - test, "unexpected number of IP addresses"); - ASSERT (inet_pton (AF_INET, expected_gw, &tmp) > 0, - test, "couldn't convert expected IP gateway"); - ASSERT (nm_ip4_config_get_gateway (ip4_config) == tmp, - test, "unexpected IP gateway"); -} - -static void -test_classless_static_routes_1 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - 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"; - static Option data[] = { - /* dhclient custom format */ - { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 8 10 10 17 66 41" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-classless-1", "failed to parse DHCP4 options"); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, - "dhcp-classless-1", "unexpected number of IP routes"); - ip4_test_route ("dhcp-classless-1", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 24); - ip4_test_route ("dhcp-classless-1", ip4_config, 1, - expected_route2_dest, expected_route2_gw, 8); - - g_hash_table_destroy (options); -} - -static void -test_classless_static_routes_2 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - 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"; - static Option data[] = { - /* dhcpcd format */ - { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-classless-2", "failed to parse DHCP4 options"); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, - "dhcp-classless-2", "unexpected number of IP routes"); - ip4_test_route ("dhcp-classless-2", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 24); - ip4_test_route ("dhcp-classless-2", ip4_config, 1, - expected_route2_dest, expected_route2_gw, 8); - - g_hash_table_destroy (options); -} - -static void -test_fedora_dhclient_classless_static_routes (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const char *expected_route1_dest = "129.210.177.128"; - const char *expected_route1_gw = "192.168.0.113"; - const char *expected_route2_dest = "2.0.0.0"; - const char *expected_route2_gw = "10.34.255.6"; - const char *expected_gateway = "192.168.0.113"; - static Option data[] = { - /* Fedora dhclient format */ - { "new_classless_static_routes", "0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-fedora-dhclient-classless", "failed to parse DHCP4 options"); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, - "dhcp-fedora-dhclient-classless", "unexpected number of IP routes"); - ip4_test_route ("dhcp-fedora-dhclient-classless", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 25); - ip4_test_route ("dhcp-fedora-dhclient-classless", ip4_config, 1, - expected_route2_dest, expected_route2_gw, 7); - - /* Gateway */ - ip4_test_gateway ("dhcp-fedora-dhclient-classless", ip4_config, expected_gateway); - - g_hash_table_destroy (options); -} - -static void -test_dhclient_invalid_classless_routes_1 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const char *expected_route1_dest = "192.168.10.0"; - const char *expected_route1_gw = "192.168.1.1"; - static Option data[] = { - /* dhclient format */ - { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 45 10 17 66 41" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*ignoring invalid classless static routes*"); - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhclient-classless-invalid-1", "failed to parse DHCP4 options"); - g_test_assert_expected_messages (); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, - "dhcp-dhclient-classless-invalid-1", "unexpected number of IP routes"); - - ip4_test_route ("dhcp-dhclient-classless-invalid-1", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 24); - - g_hash_table_destroy (options); -} - -static void -test_dhcpcd_invalid_classless_routes_1 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - 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"; - static Option data[] = { - /* dhcpcd format */ - { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.adfadf/44 10.17.66.41" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*ignoring invalid classless static routes*"); - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhcpcd-classless-invalid-1", "failed to parse DHCP4 options"); - g_test_assert_expected_messages (); - - /* Test falling back to old-style static routes if the classless static - * routes are invalid. - */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, - "dhcp-dhcpcdp-classless-invalid-1", "unexpected number of routes"); - ip4_test_route ("dhcp-dhcpcdp-classless-invalid-1", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 32); - ip4_test_route ("dhcp-dhcpcdp-classless-invalid-1", ip4_config, 1, - expected_route2_dest, expected_route2_gw, 32); - - g_hash_table_destroy (options); -} - -static void -test_dhclient_invalid_classless_routes_2 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - 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"; - static Option data[] = { - { "new_rfc3442_classless_static_routes", "45 10 17 66 41 24 192 168 10 192 168 1 1" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*ignoring invalid classless static routes*"); - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhclient-classless-invalid-2", "failed to parse DHCP4 options"); - g_test_assert_expected_messages (); - - /* Test falling back to old-style static routes if the classless static - * routes are invalid. - */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 2, - "dhcp-dhclient-classless-invalid-2", "unexpected number of routes"); - ip4_test_route ("dhcp-dhclient-classless-invalid-2", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 32); - ip4_test_route ("dhcp-dhclient-classless-invalid-2", ip4_config, 1, - expected_route2_dest, expected_route2_gw, 32); - - g_hash_table_destroy (options); -} - -static void -test_dhcpcd_invalid_classless_routes_2 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - 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"; - static Option data[] = { - { "new_classless_static_routes", "10.0.adfadf/44 10.17.66.41 192.168.10.0/24 192.168.1.1" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*ignoring invalid classless static routes*"); - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhcpcd-classless-invalid-2", "failed to parse DHCP4 options"); - g_test_assert_expected_messages (); - - /* 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-dhcpcd-classless-invalid-2", "unexpected number of routes"); - ip4_test_route ("dhcp-dhcpcd-classless-invalid-2", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 32); - ip4_test_route ("dhcp-dhcpcd-classless-invalid-2", ip4_config, 1, - expected_route2_dest, expected_route2_gw, 32); - - g_hash_table_destroy (options); -} - -static void -test_dhclient_invalid_classless_routes_3 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const char *expected_route1_dest = "192.168.10.0"; - const char *expected_route1_gw = "192.168.1.1"; - static Option data[] = { - { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 32 128 10 17 66 41" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*ignoring invalid classless static routes*"); - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhclient-classless-invalid-3", "failed to parse DHCP4 options"); - g_test_assert_expected_messages (); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, - "dhcp-dhclient-classless-invalid-3", "unexpected number of IP routes"); - ip4_test_route ("dhcp-dhclient-classless-invalid-3", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 24); - - g_hash_table_destroy (options); -} - -static void -test_dhcpcd_invalid_classless_routes_3 (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const char *expected_route1_dest = "192.168.10.0"; - const char *expected_route1_gw = "192.168.1.1"; - static Option data[] = { - { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 128/32 10.17.66.41" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*DHCP provided invalid classless static route*"); - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhcpcd-classless-invalid-3", "failed to parse DHCP4 options"); - g_test_assert_expected_messages (); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, - "dhcp-dhcpcd-classless-invalid-3", "unexpected number of IP routes"); - ip4_test_route ("dhcp-dhcpcd-classless-invalid-3", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 24); - - g_hash_table_destroy (options); -} - -static void -test_dhclient_gw_in_classless_routes (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - 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"; - static Option data[] = { - { "new_rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 0 192 2 3 4" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhclient-classless-gateway", "failed to parse DHCP4 options"); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, - "dhcp-dhclient-classless-gateway", "unexpected number of IP routes"); - ip4_test_route ("dhcp-dhclient-classless-gateway", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 24); - - /* Gateway */ - ip4_test_gateway ("dhcp-dhclient-classless-gateway", ip4_config, expected_gateway); - - g_hash_table_destroy (options); -} - -static void -test_dhcpcd_gw_in_classless_routes (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - 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"; - static Option data[] = { - { "new_classless_static_routes", "192.168.10.0/24 192.168.1.1 0.0.0.0/0 192.2.3.4" }, - { NULL, NULL } - }; - - options = fill_table (generic_options, NULL); - options = fill_table (data, options); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-dhcpcd-classless-gateway", "failed to parse DHCP4 options"); - - /* IP4 routes */ - ASSERT (nm_ip4_config_get_num_routes (ip4_config) == 1, - "dhcp-dhcpcd-classless-gateway", "unexpected number of IP routes"); - ip4_test_route ("dhcp-dhcpcd-classless-gateway", ip4_config, 0, - expected_route1_dest, expected_route1_gw, 24); - - /* Gateway */ - ip4_test_gateway ("dhcp-dhcpcd-classless-gateway", ip4_config, expected_gateway); - - g_hash_table_destroy (options); -} - -static Option escaped_searches_options[] = { - { "new_domain_search", "host1\\032host2\\032host3" }, - { NULL, NULL } -}; - -static void -test_escaped_domain_searches (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const char *expected_search0 = "host1"; - const char *expected_search1 = "host2"; - const char *expected_search2 = "host3"; - - options = fill_table (generic_options, NULL); - options = fill_table (escaped_searches_options, options); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-escaped-domain-searches", "failed to parse DHCP4 options"); - - /* domain searches */ - ASSERT (nm_ip4_config_get_num_searches (ip4_config) == 3, - "dhcp-escaped-domain-searches", "unexpected number of searches"); - ASSERT (!strcmp (nm_ip4_config_get_search (ip4_config, 0), expected_search0), - "dhcp-escaped-domain-searches", "unexpected domain search #1"); - ASSERT (!strcmp (nm_ip4_config_get_search (ip4_config, 1), expected_search1), - "dhcp-escaped-domain-searches", "unexpected domain search #1"); - ASSERT (!strcmp (nm_ip4_config_get_search (ip4_config, 2), expected_search2), - "dhcp-escaped-domain-searches", "unexpected domain search #1"); - - g_hash_table_destroy (options); -} - -static Option invalid_escaped_searches_options[] = { - { "new_domain_search", "host1\\aahost2\\032host3" }, - { NULL, NULL } -}; - -static void -test_invalid_escaped_domain_searches (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - - options = fill_table (generic_options, NULL); - options = fill_table (invalid_escaped_searches_options, options); - - g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, - "*invalid domain search*"); - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-invalid-escaped-domain-searches", "failed to parse DHCP4 options"); - g_test_assert_expected_messages (); - - /* domain searches */ - ASSERT (nm_ip4_config_get_num_searches (ip4_config) == 0, - "dhcp-invalid-escaped-domain-searches", "unexpected domain searches"); - - g_hash_table_destroy (options); -} - -static void -test_ip4_missing_prefix (const char *client, const char *ip, guint32 expected_prefix) -{ - GHashTable *options; - NMIP4Config *ip4_config; - const NMPlatformIP4Address *address; - - options = fill_table (generic_options, NULL); - g_hash_table_insert (options, "new_ip_address", string_to_byte_array_gvalue (ip)); - g_hash_table_remove (options, "new_subnet_mask"); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-ip4-missing-prefix", "failed to parse DHCP4 options"); - - ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1, - "dhcp-ip4-missing-prefix", "unexpected number of IP4 addresses (not 1)"); - - address = nm_ip4_config_get_address (ip4_config, 0); - ASSERT (address, - "dhcp-ip4-missing-prefix", "missing IP4 address #1"); - - ASSERT (address->plen == expected_prefix, - "dhcp-ip4-missing-prefix", "unexpected IP4 address prefix %d (expected %d)", - address->plen, expected_prefix); - - g_hash_table_destroy (options); -} - -static void -test_ip4_missing_prefix_24 (gconstpointer test_data) -{ - const char *client = test_data; - - test_ip4_missing_prefix (client, "192.168.1.10", 24); -} - -static void -test_ip4_missing_prefix_16 (gconstpointer test_data) -{ - const char *client = test_data; - - test_ip4_missing_prefix (client, "172.16.54.50", 16); -} - -static void -test_ip4_missing_prefix_8 (gconstpointer test_data) -{ - const char *client = test_data; - - test_ip4_missing_prefix (client, "10.1.2.3", 8); -} - -static void -test_ip4_prefix_classless (gconstpointer test_data) -{ - const char *client = test_data; - GHashTable *options; - NMIP4Config *ip4_config; - const NMPlatformIP4Address *address; - - /* Ensure that the missing-subnet-mask handler doesn't mangle classless - * subnet masks at all. The handler should trigger only if the server - * doesn't send the subnet mask. - */ - - options = fill_table (generic_options, NULL); - g_hash_table_insert (options, "new_ip_address", string_to_byte_array_gvalue ("172.16.54.22")); - g_hash_table_insert (options, "new_subnet_mask", string_to_byte_array_gvalue ("255.255.252.0")); - - ip4_config = nm_dhcp_manager_test_ip4_options_to_config (client, "eth0", options, "rebind"); - ASSERT (ip4_config != NULL, - "dhcp-ip4-prefix-classless", "failed to parse DHCP4 options"); - - ASSERT (nm_ip4_config_get_num_addresses (ip4_config) == 1, - "dhcp-ip4-prefix-classless", "unexpected number of IP4 addresses (not 1)"); - - address = nm_ip4_config_get_address (ip4_config, 0); - ASSERT (address, - "dhcp-ip4-prefix-classless", "missing IP4 address #1"); - - ASSERT (address->plen == 22, - "dhcp-ip4-prefix-classless", "unexpected IP4 address prefix %d (expected 22)", - address->plen); - - g_hash_table_destroy (options); -} - -NMTST_DEFINE (); - -int main (int argc, char **argv) -{ - char *path; - const char *clients[2][2] = { {DHCLIENT_PATH, "dhclient"}, {DHCPCD_PATH, "dhcpcd"} }; - guint32 i; - - nmtst_init_assert_logging (&argc, &argv); - nm_logging_setup ("WARN", "DEFAULT", NULL, NULL); - - for (i = 0; i < 2; i++) { - const char *client_path = clients[i][0]; - const char *client = clients[i][1]; - - if (!client_path || !strlen (client_path)) - continue; - - path = g_strdup_printf ("/dhcp/%s/generic-options", client); - g_test_add_data_func (path, client, test_generic_options); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/wins-options", client); - g_test_add_data_func (path, client, test_wins_options); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/classless-static-routes-1", client); - g_test_add_data_func (path, client, test_classless_static_routes_1); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/classless-static-routes-2", client); - g_test_add_data_func (path, client, test_classless_static_routes_2); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/fedora-dhclient-classless-static-routes", client); - g_test_add_data_func (path, client, test_fedora_dhclient_classless_static_routes); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhclient-invalid-classless-routes-1", client); - g_test_add_data_func (path, client, test_dhclient_invalid_classless_routes_1); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhcpcd-invalid-classless-routes-1", client); - g_test_add_data_func (path, client, test_dhcpcd_invalid_classless_routes_1); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhclient-invalid-classless-routes-2", client); - g_test_add_data_func (path, client, test_dhclient_invalid_classless_routes_2); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhcpcd-invalid-classless-routes-2", client); - g_test_add_data_func (path, client, test_dhcpcd_invalid_classless_routes_2); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhclient-invalid-classless-routes-3", client); - g_test_add_data_func (path, client, test_dhclient_invalid_classless_routes_3); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhcpcd-invalid-classless-routes-3", client); - g_test_add_data_func (path, client, test_dhcpcd_invalid_classless_routes_3); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhclient-gw-in-classless-routes", client); - g_test_add_data_func (path, client, test_dhclient_gw_in_classless_routes); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/dhcpcd-gw-in-classless-routes", client); - g_test_add_data_func (path, client, test_dhcpcd_gw_in_classless_routes); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/escaped-domain-searches", client); - g_test_add_data_func (path, client, test_escaped_domain_searches); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/invalid-escaped-domain-searches", client); - g_test_add_data_func (path, client, test_invalid_escaped_domain_searches); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/ip4-missing-prefix-24", client); - g_test_add_data_func (path, client, test_ip4_missing_prefix_24); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/ip4-missing-prefix-16", client); - g_test_add_data_func (path, client, test_ip4_missing_prefix_16); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/ip4-missing-prefix-8", client); - g_test_add_data_func (path, client, test_ip4_missing_prefix_8); - g_free (path); - - path = g_strdup_printf ("/dhcp/%s/ip4-prefix-classless", client); - g_test_add_data_func (path, client, test_ip4_prefix_classless); - g_free (path); - } - - return g_test_run (); -} - |