summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/nm-dhcp-client-logging.h4
-rw-r--r--src/dhcp/nm-dhcp-client.c27
-rw-r--r--src/dhcp/nm-dhcp-client.h14
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c85
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h6
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c30
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c32
-rw-r--r--src/dhcp/nm-dhcp-listener.c2
-rw-r--r--src/dhcp/nm-dhcp-manager.c54
-rw-r--r--src/dhcp/nm-dhcp-systemd.c29
-rw-r--r--src/dhcp/nm-dhcp-utils.h4
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c129
12 files changed, 283 insertions, 133 deletions
diff --git a/src/dhcp/nm-dhcp-client-logging.h b/src/dhcp/nm-dhcp-client-logging.h
index 8dd18bf2..1047a7d7 100644
--- a/src/dhcp/nm-dhcp-client-logging.h
+++ b/src/dhcp/nm-dhcp-client-logging.h
@@ -42,7 +42,7 @@
                                             ? LOGD_DHCP \
                                             : (nm_dhcp_client_get_ipv6 (_self) ? LOGD_DHCP6 : LOGD_DHCP4); \
             \
-            nm_log (_level, _domain, \
+            nm_log (_level, _domain, __ifname, NULL, \
                     "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     _NMLOG_PREFIX_NAME, \
                     (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")), \
@@ -65,7 +65,7 @@
         if (nm_logging_enabled (_level, _domain)) { \
             const char *__ifname = (ifname); \
             \
-            nm_log (_level, _domain, \
+            nm_log (_level, _domain, __ifname, NULL, \
                     "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     _NMLOG_PREFIX_NAME, \
                     (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")), \
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index ba7c6dbf..0906f5be 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -68,7 +68,7 @@ typedef struct _NMDhcpClientPrivate {
 	GByteArray * duid;
 	GBytes *     client_id;
 	char *       hostname;
-	char *       fqdn;
+	gboolean     use_fqdn;
 
 	NMDhcpState  state;
 	pid_t        pid;
@@ -147,6 +147,14 @@ nm_dhcp_client_get_priority (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->priority;
 }
 
+guint32
+nm_dhcp_client_get_timeout (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), 0);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->timeout;
+}
+
 GBytes *
 nm_dhcp_client_get_client_id (NMDhcpClient *self)
 {
@@ -178,12 +186,12 @@ nm_dhcp_client_get_hostname (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->hostname;
 }
 
-const char *
-nm_dhcp_client_get_fqdn (NMDhcpClient *self)
+gboolean
+nm_dhcp_client_get_use_fqdn (NMDhcpClient *self)
 {
-	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
 
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->fqdn;
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->use_fqdn;
 }
 
 /*****************************************************************************/
@@ -298,7 +306,6 @@ nm_dhcp_client_set_state (NMDhcpClient *self,
 		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 {
 		g_assert (ip_config == NULL);
 		g_assert (options == NULL);
@@ -409,7 +416,7 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                           const char *dhcp_client_id,
                           const char *dhcp_anycast_addr,
                           const char *hostname,
-                          const char *fqdn,
+                          gboolean use_fqdn,
                           const char *last_ip4_address)
 {
 	NMDhcpClientPrivate *priv;
@@ -430,8 +437,7 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 
 	g_clear_pointer (&priv->hostname, g_free);
 	priv->hostname = g_strdup (hostname);
-	g_free (priv->fqdn);
-	priv->fqdn = g_strdup (fqdn);
+	priv->use_fqdn = use_fqdn;
 
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, dhcp_anycast_addr, last_ip4_address);
 }
@@ -571,7 +577,7 @@ nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name)
 	if (start_time == 0)
 		goto out;
 
-	nm_sprintf_buf (proc_path, "/proc/%lu/cmdline", (long unsigned) pid);
+	nm_sprintf_buf (proc_path, "/proc/%lu/cmdline", (unsigned long) pid);
 	if (!g_file_get_contents (proc_path, &proc_contents, NULL, NULL))
 		goto out;
 
@@ -904,7 +910,6 @@ dispose (GObject *object)
 
 	g_clear_pointer (&priv->iface, g_free);
 	g_clear_pointer (&priv->hostname, g_free);
-	g_clear_pointer (&priv->fqdn, g_free);
 	g_clear_pointer (&priv->uuid, g_free);
 	g_clear_pointer (&priv->client_id, g_bytes_unref);
 
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 7a083ae7..e41a59a2 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -19,10 +19,10 @@
 #ifndef __NETWORKMANAGER_DHCP_CLIENT_H__
 #define __NETWORKMANAGER_DHCP_CLIENT_H__
 
-#include <nm-setting-ip4-config.h>
-#include <nm-setting-ip6-config.h>
-#include <nm-ip4-config.h>
-#include <nm-ip6-config.h>
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-ip4-config.h"
+#include "nm-ip6-config.h"
 
 #define NM_TYPE_DHCP_CLIENT            (nm_dhcp_client_get_type ())
 #define NM_DHCP_CLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClient))
@@ -117,17 +117,19 @@ const GByteArray *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_priority (NMDhcpClient *self);
 
+guint32 nm_dhcp_client_get_timeout (NMDhcpClient *self);
+
 GBytes *nm_dhcp_client_get_client_id (NMDhcpClient *self);
 
 const char *nm_dhcp_client_get_hostname (NMDhcpClient *self);
 
-const char *nm_dhcp_client_get_fqdn (NMDhcpClient *self);
+gboolean nm_dhcp_client_get_use_fqdn (NMDhcpClient *self);
 
 gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                                    const char *dhcp_client_id,
                                    const char *dhcp_anycast_addr,
                                    const char *hostname,
-                                   const char *fqdn,
+                                   gboolean use_fqdn,
                                    const char *last_ip4_address);
 
 gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index f36451b2..216319b3 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -93,29 +93,21 @@ grab_request_options (GPtrArray *store, const char* line)
 
 
 static void
-add_hostname4 (GString *str, const char *hostname, const char *fqdn)
+add_hostname4 (GString *str, const char *hostname, gboolean use_fqdn)
 {
-	char *plain_hostname, *dot;
-
-	if (fqdn) {
-		g_string_append_printf (str, FQDN_FORMAT "\n", fqdn);
-		g_string_append (str,
-		                 "send fqdn.encoded on;\n"
-		                 "send fqdn.server-update on;\n");
-	} else if (hostname) {
-		plain_hostname = g_strdup (hostname);
-		dot = strchr (plain_hostname, '.');
-		/* get rid of the domain */
-		if (dot)
-			*dot = '\0';
-
-		g_string_append_printf (str, HOSTNAME4_FORMAT "\n", plain_hostname);
-		g_free (plain_hostname);
+	if (hostname) {
+		if (use_fqdn) {
+			g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
+			g_string_append (str,
+			                 "send fqdn.encoded on;\n"
+			                 "send fqdn.server-update on;\n");
+		} else
+			g_string_append_printf (str, HOSTNAME4_FORMAT "\n", hostname);
 	}
 }
 
 static void
-add_ip4_config (GString *str, GBytes *client_id, const char *hostname, const char *fqdn)
+add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean use_fqdn)
 {
 	if (client_id) {
 		const char *p;
@@ -150,7 +142,7 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, const cha
 		g_string_append (str, "; # added by NetworkManager\n");
 	}
 
-	add_hostname4 (str, hostname, fqdn);
+	add_hostname4 (str, hostname, use_fqdn);
 
 	g_string_append_c (str, '\n');
 
@@ -232,13 +224,46 @@ nm_dhcp_dhclient_get_client_id_from_config_file (const char *path)
 	return NULL;
 }
 
+static gboolean
+read_interface (const char *line, char *interface, guint size)
+{
+	gs_free char *dup = g_strdup (line + NM_STRLEN ("interface"));
+	char *ptr = dup, *end;
+
+	while (g_ascii_isspace (*ptr))
+		ptr++;
+
+	if (*ptr == '"') {
+		ptr++;
+		end = strchr (ptr, '"');
+		if (!end)
+			return FALSE;
+		*end = '\0';
+	} else {
+		end = strchr (ptr, ' ');
+		if (!end)
+			end = strchr (ptr, '{');
+		if (!end)
+			return FALSE;
+		*end = '\0';
+	}
+
+	if (   ptr[0] == '\0'
+	    || strlen (ptr) + 1 > size)
+		return FALSE;
+
+	snprintf (interface, size, "%s", ptr);
+
+	return TRUE;
+}
+
 char *
 nm_dhcp_dhclient_create_config (const char *interface,
                                 gboolean is_ip6,
                                 GBytes *client_id,
                                 const char *anycast_addr,
                                 const char *hostname,
-                                const char *fqdn,
+                                gboolean use_fqdn,
                                 const char *orig_path,
                                 const char *orig_contents,
                                 GBytes **out_new_client_id)
@@ -258,8 +283,10 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		char **lines, **line;
 		gboolean in_alsoreq = FALSE;
 		gboolean in_req = FALSE;
+		char intf[IFNAMSIZ];
 
 		g_string_append_printf (new_contents, _("# Merged from %s\n\n"), orig_path);
+		intf[0] = '\0';
 
 		lines = g_strsplit_set (orig_contents, "\n\r", 0);
 		for (line = lines; lines && *line; line++) {
@@ -268,6 +295,20 @@ nm_dhcp_dhclient_create_config (const char *interface,
 			if (!strlen (g_strstrip (p)))
 				continue;
 
+			if (   !intf[0]
+			    && g_str_has_prefix (p, "interface")) {
+				if (read_interface (p, intf, sizeof (intf)))
+					continue;
+			}
+
+			if (intf[0] && strchr (p, '}')) {
+				intf[0] = '\0';
+				continue;
+			}
+
+			if (intf[0] && !nm_streq (intf, interface))
+				continue;
+
 			if (!strncmp (p, CLIENTID_TAG, strlen (CLIENTID_TAG))) {
 				/* Override config file "dhcp-client-id" and use one from the connection */
 				if (client_id)
@@ -279,7 +320,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 			}
 
 			/* Override config file hostname and use one from the connection */
-			if (hostname || fqdn) {
+			if (hostname) {
 				if (strncmp (p, HOSTNAME4_TAG, strlen (HOSTNAME4_TAG)) == 0)
 					continue;
 				if (strncmp (p, FQDN_TAG, strlen (FQDN_TAG)) == 0)
@@ -339,7 +380,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		add_request (reqs, "dhcp6.domain-search");
 		add_request (reqs, "dhcp6.client-id");
 	} else {
-		add_ip4_config (new_contents, client_id, hostname, fqdn);
+		add_ip4_config (new_contents, client_id, hostname, use_fqdn);
 		add_request (reqs, "rfc3442-classless-static-routes");
 		add_request (reqs, "ms-classless-static-routes");
 		add_request (reqs, "static-routes");
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index 83d5a23d..994b1b9f 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -19,15 +19,15 @@
 #ifndef __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__
 #define __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__
 
-#include <nm-setting-ip4-config.h>
-#include <nm-setting-ip6-config.h>
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
 
 char *nm_dhcp_dhclient_create_config (const char *interface,
                                       gboolean is_ip6,
                                       GBytes *client_id,
                                       const char *anycast_addr,
                                       const char *hostname,
-                                      const char *fqdn,
+                                      gboolean use_fqdn,
                                       const char *orig_path,
                                       const char *orig_contents,
                                       GBytes **out_new_client_id);
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 64d93744..a56e5a3c 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -182,7 +182,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
                        GBytes *client_id,
                        const char *anycast_addr,
                        const char *hostname,
-                       const char *fqdn,
+                       gboolean use_fqdn,
                        const char *orig_path,
                        GBytes **out_new_client_id,
                        GError **error)
@@ -206,7 +206,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
 	if (is_ip6 && hostname && !strchr (hostname, '.'))
 		_LOGW ("hostname is not a FQDN, it will be ignored");
 
-	new = nm_dhcp_dhclient_create_config (iface, is_ip6, client_id, anycast_addr, hostname, fqdn, orig_path, orig, out_new_client_id);
+	new = nm_dhcp_dhclient_create_config (iface, is_ip6, client_id, anycast_addr, hostname, use_fqdn, orig_path, orig, out_new_client_id);
 	g_assert (new);
 	success = g_file_set_contents (conf_file, new, -1, error);
 	g_free (new);
@@ -294,7 +294,7 @@ create_dhclient_config (NMDhcpDhclient *self,
                         GBytes *client_id,
                         const char *dhcp_anycast_addr,
                         const char *hostname,
-                        const char *fqdn,
+                        gboolean use_fqdn,
                         GBytes **out_new_client_id)
 {
 	char *orig = NULL, *new = NULL;
@@ -314,7 +314,7 @@ create_dhclient_config (NMDhcpDhclient *self,
 
 	error = NULL;
 	success = merge_dhclient_config (self, iface, new, is_ip6, client_id, dhcp_anycast_addr,
-			                         hostname, fqdn, orig, out_new_client_id, &error);
+			                         hostname, use_fqdn, orig, out_new_client_id, &error);
 	if (!success) {
 		_LOGW ("error creating dhclient configuration: %s", error->message);
 		g_error_free (error);
@@ -342,6 +342,8 @@ dhclient_start (NMDhcpClient *client,
 	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
 	gboolean ipv6, success;
 	char *escaped, *preferred_leasefile_path = NULL;
+	guint32 timeout;
+	char timeout_str[64];
 
 	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
 
@@ -444,6 +446,17 @@ dhclient_start (NMDhcpClient *client,
 		g_ptr_array_add (argv, (gpointer) priv->conf_file);
 	}
 
+	/* Specify a timeout longer than configuration's one,
+	 * so that dhclient doesn't send back a FAIL event before
+	 * that time.
+	 */
+	timeout = nm_dhcp_client_get_timeout (client);
+	if (timeout >= 60) {
+		timeout = timeout < G_MAXINT32 ? timeout + 1 : G_MAXINT32;
+		g_ptr_array_add (argv, (gpointer) "-timeout");
+		g_ptr_array_add (argv, (gpointer) nm_sprintf_buf (timeout_str, "%u", (unsigned) timeout));
+	}
+
 	/* Usually the system bus address is well-known; but if it's supposed
 	 * to be something else, we need to push it to dhclient, since dhclient
 	 * sanitizes the environment it gives the action scripts.
@@ -492,17 +505,18 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 	GBytes *client_id;
 	gs_unref_bytes GBytes *new_client_id = NULL;
-	const char *iface, *uuid, *hostname, *fqdn;
+	const char *iface, *uuid, *hostname;
 	gboolean success = FALSE;
+	gboolean use_fqdn;
 
 	iface = nm_dhcp_client_get_iface (client);
 	uuid = nm_dhcp_client_get_uuid (client);
 	client_id = nm_dhcp_client_get_client_id (client);
 	hostname = nm_dhcp_client_get_hostname (client);
-	fqdn = nm_dhcp_client_get_fqdn (client);
+	use_fqdn = nm_dhcp_client_get_use_fqdn (client);
 
 	priv->conf_file = create_dhclient_config (self, iface, FALSE, uuid, client_id, dhcp_anycast_addr,
-	                                          hostname, fqdn, &new_client_id);
+	                                          hostname, use_fqdn, &new_client_id);
 	if (priv->conf_file) {
 		if (new_client_id)
 			nm_dhcp_client_set_client_id (client, new_client_id);
@@ -530,7 +544,7 @@ ip6_start (NMDhcpClient *client,
 	uuid = nm_dhcp_client_get_uuid (client);
 	hostname = nm_dhcp_client_get_hostname (client);
 
-	priv->conf_file = create_dhclient_config (self, iface, TRUE, uuid, NULL, dhcp_anycast_addr, hostname, NULL, NULL);
+	priv->conf_file = create_dhclient_config (self, iface, TRUE, uuid, NULL, dhcp_anycast_addr, hostname, TRUE, NULL);
 	if (!priv->conf_file) {
 		_LOGW ("error creating dhclient configuration file");
 		return FALSE;
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index c8643881..66a31acf 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -88,9 +88,8 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	GPtrArray *argv = NULL;
 	pid_t pid = -1;
 	GError *error = NULL;
-	char *pid_contents = NULL, *binary_name, *cmd_str, *dot;
-	const char *iface, *dhcpcd_path, *hostname, *fqdn;
-	gs_free char *prefix = NULL;
+	char *pid_contents = NULL, *binary_name, *cmd_str;
+	const char *iface, *dhcpcd_path, *hostname;
 
 	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
 
@@ -138,22 +137,17 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 #endif
 
 	hostname = nm_dhcp_client_get_hostname (client);
-	fqdn = nm_dhcp_client_get_fqdn (client);
-
-	if (fqdn) {
-		g_ptr_array_add (argv, (gpointer) "-h");
-		g_ptr_array_add (argv, (gpointer) fqdn);
-		g_ptr_array_add (argv, (gpointer) "-F");
-		g_ptr_array_add (argv, (gpointer) "both");
-	} else if (hostname) {
-		prefix = strdup (hostname);
-		dot = strchr (prefix, '.');
-		/* get rid of the domain */
-		if (dot)
-			*dot = '\0';
-
-		g_ptr_array_add (argv, (gpointer) "-h");	/* Send hostname to DHCP server */
-		g_ptr_array_add (argv, (gpointer) prefix);
+
+	if (hostname) {
+		if (nm_dhcp_client_get_use_fqdn (client)) {
+			g_ptr_array_add (argv, (gpointer) "-h");
+			g_ptr_array_add (argv, (gpointer) hostname);
+			g_ptr_array_add (argv, (gpointer) "-F");
+			g_ptr_array_add (argv, (gpointer) "both");
+		} else {
+			g_ptr_array_add (argv, (gpointer) "-h");
+			g_ptr_array_add (argv, (gpointer) hostname);
+		}
 	}
 
 	g_ptr_array_add (argv, (gpointer) iface);
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index 56bd9d17..ca697ab3 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -92,7 +92,7 @@ NM_DEFINE_SINGLETON_GETTER (NMDhcpListener, nm_dhcp_listener_get, NM_TYPE_DHCP_L
         const NMDhcpListener *_self = (self); \
         char _prefix[64]; \
         \
-        nm_log ((level), (_NMLOG_DOMAIN), \
+        nm_log ((level), (_NMLOG_DOMAIN), NULL, NULL, \
                 "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
                 (_self != singleton_instance \
                     ? nm_sprintf_buf (_prefix, "%s[%p]", _NMLOG_PREFIX_NAME, _self) \
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index 9c1fbb38..fff9f9ec 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -163,7 +163,7 @@ client_start (NMDhcpManager *self,
               guint32 timeout,
               const char *dhcp_anycast_addr,
               const char *hostname,
-              const char *fqdn,
+              gboolean hostname_use_fqdn,
               gboolean info_only,
               NMSettingIP6ConfigPrivacy privacy,
               const char *last_ip4_address,
@@ -209,7 +209,7 @@ client_start (NMDhcpManager *self,
 	if (ipv6)
 		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
 	else
-		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, fqdn, last_ip4_address);
+		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, hostname_use_fqdn, last_ip4_address);
 
 	if (!success) {
 		remove_client (self, client);
@@ -219,15 +219,6 @@ client_start (NMDhcpManager *self,
 	return client;
 }
 
-static const char *
-get_send_hostname (NMDhcpManager *self, const char *setting_hostname)
-{
-	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
-
-	/* Always prefer the explicit dhcp-send-hostname if given */
-	return setting_hostname ? setting_hostname : priv->default_hostname;
-}
-
 /* Caller owns a reference to the NMDhcpClient on return */
 NMDhcpClient *
 nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
@@ -244,18 +235,41 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            const char *dhcp_anycast_addr,
                            const char *last_ip_address)
 {
+	NMDhcpManagerPrivate *priv;
 	const char *hostname = NULL;
-	const char *fqdn = NULL;
+	gs_free char *hostname_tmp = NULL;
+	gboolean use_fqdn = FALSE;
+	char *dot;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 
 	if (send_hostname) {
-		hostname = get_send_hostname (self, dhcp_hostname);
-		fqdn = dhcp_fqdn;
+		/* Use, in order of preference:
+		 *  1. FQDN from configuration
+		 *  2. hostname from configuration
+		 *  3. system hostname (only host part)
+		 */
+		if (dhcp_fqdn) {
+			hostname = dhcp_fqdn;
+			use_fqdn = TRUE;
+		} else if (dhcp_hostname)
+			hostname = dhcp_hostname;
+		else {
+			hostname = priv->default_hostname;
+			if (hostname) {
+				hostname_tmp = g_strdup (hostname);
+				dot = strchr (hostname_tmp, '.');
+				if (dot)
+					*dot = '\0';
+				hostname = hostname_tmp;
+			}
+		}
 	}
+
 	return client_start (self, iface, ifindex, hwaddr, uuid, priority, FALSE, NULL,
 	                     dhcp_client_id, timeout, dhcp_anycast_addr, hostname,
-	                     fqdn, FALSE, 0, last_ip_address, 0);
+	                     use_fqdn, FALSE, 0, last_ip_address, 0);
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -275,14 +289,18 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            NMSettingIP6ConfigPrivacy privacy,
                            guint needed_prefixes)
 {
+	NMDhcpManagerPrivate *priv;
 	const char *hostname = NULL;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 
-	if (send_hostname)
-		hostname = get_send_hostname (self, dhcp_hostname);
+	if (send_hostname) {
+		/* Always prefer the explicit dhcp-hostname if given */
+		hostname = dhcp_hostname ? dhcp_hostname : priv->default_hostname;
+	}
 	return client_start (self, iface, ifindex, hwaddr, uuid, priority, TRUE,
-	                     ll_addr, NULL, timeout, dhcp_anycast_addr, hostname, NULL, info_only,
+	                     ll_addr, NULL, timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
 	                     privacy, NULL, needed_prefixes);
 }
 
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 7067275b..aa902701 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -580,7 +580,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	const uint8_t *client_id = NULL;
 	size_t client_id_len = 0;
 	struct in_addr last_addr = { 0 };
-	const char *hostname, *fqdn;
+	const char *hostname;
 	int r, i;
 	gboolean success = FALSE;
 	guint16 arp_type;
@@ -687,28 +687,13 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 
 	hostname = nm_dhcp_client_get_hostname (client);
 	if (hostname) {
-		char *prefix, *dot;
-
-		prefix = strdup (hostname);
-		dot = strchr (prefix, '.');
-		/* get rid of the domain */
-		if (dot)
-			*dot = '\0';
-
-		r = sd_dhcp_client_set_hostname (priv->client4, prefix);
-		free (prefix);
-
-		if (r < 0) {
-			_LOGW ("failed to set DHCP hostname (%d)", r);
-			goto error;
-		}
-	}
-
-	fqdn = nm_dhcp_client_get_fqdn (client);
-	if (fqdn) {
-		r = sd_dhcp_client_set_hostname (priv->client4, fqdn);
+		/* FIXME: sd-dhcp decides which hostname/FQDN option to send (12 or 81)
+		 * only based on whether the hostname has a domain part or not. At the
+		 * moment there is no way to force one or another.
+		 */
+		r = sd_dhcp_client_set_hostname (priv->client4, hostname);
 		if (r < 0) {
-			_LOGW ("failed to set DHCP FQDN (%d)", r);
+			_LOGW ("failed to set DHCP hostname to '%s' (%d)", hostname, r);
 			goto error;
 		}
 	}
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index b45c5e89..05982b16 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -21,8 +21,8 @@
 
 #include <stdlib.h>
 
-#include <nm-ip4-config.h>
-#include <nm-ip6-config.h>
+#include "nm-ip4-config.h"
+#include "nm-ip6-config.h"
 
 NMIP4Config *nm_dhcp_utils_ip4_config_from_options (int ifindex,
                                                     const char *iface,
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index f4cf9c9f..40a3e072 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -40,7 +40,7 @@ test_config (const char *orig,
              const char *expected,
              gboolean ipv6,
              const char *hostname,
-             const char *fqdn,
+             gboolean use_fqdn,
              const char *dhcp_client_id,
              GBytes *expected_new_client_id,
              const char *iface,
@@ -60,7 +60,7 @@ test_config (const char *orig,
 	                                      client_id,
 	                                      anycast_addr,
 	                                      hostname,
-	                                      fqdn,
+	                                      use_fqdn,
 	                                      "/path/to/dhclient.conf",
 	                                      orig,
 	                                      &new_client_id);
@@ -105,7 +105,7 @@ static const char *orig_missing_expected = \
 static void
 test_orig_missing (void)
 {
-	test_config (NULL, orig_missing_expected, FALSE, NULL, NULL, NULL, NULL, "eth0", NULL);
+	test_config (NULL, orig_missing_expected, FALSE, NULL, FALSE, NULL, NULL, "eth0", NULL);
 }
 
 /*****************************************************************************/
@@ -134,7 +134,7 @@ static void
 test_override_client_id (void)
 {
 	test_config (override_client_id_orig, override_client_id_expected,
-	             FALSE, NULL, NULL,
+	             FALSE, NULL, FALSE,
 	             "11:22:33:44:55:66",
 	             NULL,
 	             "eth0",
@@ -163,7 +163,7 @@ static void
 test_quote_client_id (void)
 {
 	test_config (NULL, quote_client_id_expected,
-	             FALSE, NULL, NULL,
+	             FALSE, NULL, FALSE,
 	             "1234",
 	             NULL,
 	             "eth0",
@@ -192,7 +192,7 @@ static void
 test_ascii_client_id (void)
 {
 	test_config (NULL, ascii_client_id_expected,
-	             FALSE, NULL, NULL,
+	             FALSE, NULL, FALSE,
 	             "qb:cd:ef:12:34:56",
 	             NULL,
 	             "eth0",
@@ -221,7 +221,7 @@ static void
 test_hex_single_client_id (void)
 {
 	test_config (NULL, hex_single_client_id_expected,
-	             FALSE, NULL, NULL,
+	             FALSE, NULL, FALSE,
 	             "ab:cd:e:12:34:56",
 	             NULL,
 	             "eth0",
@@ -258,7 +258,7 @@ test_existing_hex_client_id (void)
 
 	new_client_id = g_bytes_new (bytes, sizeof (bytes));
 	test_config (existing_hex_client_id_orig, existing_hex_client_id_expected,
-	             FALSE, NULL, NULL,
+	             FALSE, NULL, FALSE,
 	             NULL,
 	             new_client_id,
 	             "eth0",
@@ -298,7 +298,7 @@ test_existing_ascii_client_id (void)
 	memcpy (buf + 1, EACID, NM_STRLEN (EACID));
 	new_client_id = g_bytes_new (buf, sizeof (buf));
 	test_config (existing_ascii_client_id_orig, existing_ascii_client_id_expected,
-	             FALSE, NULL, NULL,
+	             FALSE, NULL, FALSE,
 	             NULL,
 	             new_client_id,
 	             "eth0",
@@ -327,8 +327,8 @@ static void
 test_fqdn (void)
 {
 	test_config (NULL, fqdn_expected,
-	             FALSE, NULL,
-	             "foo.bar.com", NULL,
+	             FALSE, "foo.bar.com",
+	             TRUE, NULL,
 	             NULL,
 	             "eth0",
 	             NULL);
@@ -367,8 +367,8 @@ test_fqdn_options_override (void)
 {
 	test_config (fqdn_options_override_orig,
 	             fqdn_options_override_expected,
-	             FALSE, NULL,
-	             "example2.com", NULL,
+	             FALSE, "example2.com",
+	             TRUE, NULL,
 	             NULL,
 	             "eth0",
 	             NULL);
@@ -400,7 +400,7 @@ static void
 test_override_hostname (void)
 {
 	test_config (override_hostname_orig, override_hostname_expected,
-	             FALSE, "blahblah", NULL,
+	             FALSE, "blahblah", FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -429,7 +429,7 @@ static void
 test_override_hostname6 (void)
 {
 	test_config (override_hostname6_orig, override_hostname6_expected,
-	             TRUE, "blahblah.local", NULL,
+	             TRUE, "blahblah.local", TRUE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -452,7 +452,7 @@ test_nonfqdn_hostname6 (void)
 	/* Non-FQDN hostname can't be used with dhclient */
 	test_config (NULL, nonfqdn_hostname6_expected,
 	             TRUE, "blahblah",
-	             NULL, NULL,
+	             TRUE, NULL,
 	             NULL,
 	             "eth0",
 	             NULL);
@@ -487,7 +487,7 @@ test_existing_alsoreq (void)
 {
 	test_config (existing_alsoreq_orig, existing_alsoreq_expected,
 	             FALSE, NULL,
-	             NULL,
+	             FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -526,7 +526,7 @@ test_existing_req (void)
 {
 	test_config (existing_req_orig, existing_req_expected,
 	             FALSE, NULL,
-	             NULL,
+	             FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -565,7 +565,7 @@ static void
 test_existing_multiline_alsoreq (void)
 {
 	test_config (existing_multiline_alsoreq_orig, existing_multiline_alsoreq_expected,
-	             FALSE, NULL, NULL,
+	             FALSE, NULL, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -744,6 +744,95 @@ test_write_existing_commented_duid (void)
 
 /*****************************************************************************/
 
+static const char *interface1_orig = \
+	"interface \"eth0\" {\n"
+	"	also request my-option;\n"
+	"	initial-delay 5;\n"
+	"}\n"
+	"interface \"eth1\" {\n"
+	"	also request another-option;\n"
+	"	initial-delay 0;\n"
+	"}\n"
+	"\n"
+	"also request yet-another-option;\n";
+
+static const char *interface1_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"initial-delay 5;\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request my-option;\n"
+	"also request yet-another-option;\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_interface1 (void)
+{
+	test_config (interface1_orig, interface1_expected,
+	             FALSE, NULL, FALSE,
+	             NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *interface2_orig = \
+	"interface eth0 {\n"
+	"	also request my-option;\n"
+	"	initial-delay 5;\n"
+	" }\n"
+	"interface eth1 {\n"
+	"	initial-delay 0;\n"
+	"	request another-option;\n"
+	" } \n"
+	"\n"
+	"also request yet-another-option;\n";
+
+static const char *interface2_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"initial-delay 0;\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"request; # override dhclient defaults\n"
+	"also request another-option;\n"
+	"also request yet-another-option;\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_interface2 (void)
+{
+	test_config (interface2_orig, interface2_expected,
+	             FALSE, NULL, FALSE,
+	             NULL,
+	             NULL,
+	             "eth1",
+	             NULL);
+}
+
+/*****************************************************************************/
+
 static void
 test_read_lease_ip4_config_basic (void)
 {
@@ -891,6 +980,8 @@ main (int argc, char **argv)
 	g_test_add_func ("/dhcp/dhclient/existing_alsoreq", test_existing_alsoreq);
 	g_test_add_func ("/dhcp/dhclient/existing_multiline_alsoreq", test_existing_multiline_alsoreq);
 	g_test_add_func ("/dhcp/dhclient/duids", test_duids);
+	g_test_add_func ("/dhcp/dhclient/interface/1", test_interface1);
+	g_test_add_func ("/dhcp/dhclient/interface/2", test_interface2);
 
 	g_test_add_func ("/dhcp/dhclient/read_duid_from_leasefile", test_read_duid_from_leasefile);
 	g_test_add_func ("/dhcp/dhclient/read_commented_duid_from_leasefile", test_read_commented_duid_from_leasefile);