summary refs log tree commit diff
path: root/src/dnsmasq
diff options
context:
space:
mode:
Diffstat (limited to 'src/dnsmasq')
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c395
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.h55
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.c78
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.h31
-rw-r--r--src/dnsmasq/tests/test-dnsmasq-utils.c99
5 files changed, 658 insertions, 0 deletions
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
new file mode 100644
index 00000000..9b24e69e
--- /dev/null
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -0,0 +1,395 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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 of the License, 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 - 2012 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dnsmasq-manager.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+
+#include "nm-dnsmasq-utils.h"
+#include "nm-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-core-internal.h"
+
+#define CONFDIR NMCONFDIR "/dnsmasq-shared.d"
+
+/*****************************************************************************/
+
+enum {
+	STATE_CHANGED,
+	LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+	char *iface;
+	char *pidfile;
+	GPid pid;
+	guint dm_watch_id;
+} NMDnsMasqManagerPrivate;
+
+struct _NMDnsMasqManager {
+	GObject parent;
+	NMDnsMasqManagerPrivate _priv;
+};
+
+struct _NMDnsMasqManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMDnsMasqManager, nm_dnsmasq_manager, G_TYPE_OBJECT)
+
+#define NM_DNSMASQ_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDnsMasqManager, NM_IS_DNSMASQ_MANAGER)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_SHARING
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "dnsmasq-manager", __VA_ARGS__)
+
+/*****************************************************************************/
+
+typedef struct {
+	GPtrArray *array;
+	GStringChunk *chunk;
+} NMCmdLine;
+
+static NMCmdLine *
+nm_cmd_line_new (void)
+{
+	NMCmdLine *cmd;
+
+	cmd = g_slice_new (NMCmdLine);
+	cmd->array = g_ptr_array_new ();
+	cmd->chunk = g_string_chunk_new (1024);
+
+	return cmd;
+}
+
+static void
+nm_cmd_line_destroy (NMCmdLine *cmd)
+{
+	g_ptr_array_free (cmd->array, TRUE);
+	g_string_chunk_free (cmd->chunk);
+	g_slice_free (NMCmdLine, cmd);
+}
+
+static char *
+nm_cmd_line_to_str (NMCmdLine *cmd)
+{
+	char *str;
+
+	g_ptr_array_add (cmd->array, NULL);
+	str = g_strjoinv (" ", (gchar **) cmd->array->pdata);
+	g_ptr_array_remove_index (cmd->array, cmd->array->len - 1);
+
+	return str;
+}
+
+static void
+nm_cmd_line_add_string (NMCmdLine *cmd, const char *str)
+{
+	g_ptr_array_add (cmd->array, g_string_chunk_insert (cmd->chunk, str));
+}
+
+/*****************************************************************************/
+
+static void
+dm_watch_cb (GPid pid, gint status, gpointer user_data)
+{
+	NMDnsMasqManager *manager = NM_DNSMASQ_MANAGER (user_data);
+	NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+	guint err;
+
+	if (WIFEXITED (status)) {
+		err = WEXITSTATUS (status);
+		if (err != 0) {
+			_LOGW ("dnsmasq exited with error: %s",
+			       nm_utils_dnsmasq_status_to_string (err, NULL, 0));
+		}
+	} else if (WIFSTOPPED (status)) {
+		_LOGW ("dnsmasq stopped unexpectedly with signal %d", WSTOPSIG (status));
+	} else if (WIFSIGNALED (status)) {
+		_LOGW ("dnsmasq died with signal %d", WTERMSIG (status));
+	} else {
+		_LOGW ("dnsmasq died from an unknown cause");
+	}
+
+	priv->pid = 0;
+	priv->dm_watch_id = 0;
+
+	g_signal_emit (manager, signals[STATE_CHANGED], 0, NM_DNSMASQ_STATUS_DEAD);
+}
+
+static NMCmdLine *
+create_dm_cmd_line (const char *iface,
+                    const NMIP4Config *ip4_config,
+                    const char *pidfile,
+                    GError **error)
+{
+	NMCmdLine *cmd;
+	GString *s;
+	char first[INET_ADDRSTRLEN];
+	char last[INET_ADDRSTRLEN];
+	char localaddr[INET_ADDRSTRLEN];
+	char *error_desc = NULL;
+	const char *dm_binary;
+	const NMPlatformIP4Address *listen_address;
+
+	listen_address = nm_ip4_config_get_address (ip4_config, 0);
+	g_return_val_if_fail (listen_address, NULL);
+
+	dm_binary = nm_utils_find_helper ("dnsmasq", DNSMASQ_PATH, error);
+	if (!dm_binary)
+		return NULL;
+
+	/* Create dnsmasq command line */
+	cmd = nm_cmd_line_new ();
+	nm_cmd_line_add_string (cmd, dm_binary);
+
+	if (getenv ("NM_DNSMASQ_DEBUG")) {
+		nm_cmd_line_add_string (cmd, "--log-dhcp");
+		nm_cmd_line_add_string (cmd, "--log-queries");
+	}
+
+	/* dnsmasq may read from it's default config file location, which if that
+	 * location is a valid config file, it will combine with the options here
+	 * and cause undesirable side-effects.  Like sending bogus IP addresses
+	 * as the gateway or whatever.  So tell dnsmasq not to use any config file
+	 * at all.
+	 */
+	nm_cmd_line_add_string (cmd, "--conf-file");
+
+	nm_cmd_line_add_string (cmd, "--no-hosts");
+	nm_cmd_line_add_string (cmd, "--keep-in-foreground");
+	nm_cmd_line_add_string (cmd, "--bind-interfaces");
+	nm_cmd_line_add_string (cmd, "--except-interface=lo");
+	nm_cmd_line_add_string (cmd, "--clear-on-reload");
+
+	/* Use strict order since in the case of VPN connections, the VPN's
+	 * nameservers will be first in resolv.conf, and those need to be tried
+	 * first by dnsmasq to successfully resolve names from the VPN.
+	 */
+	nm_cmd_line_add_string (cmd, "--strict-order");
+
+	s = g_string_new ("--listen-address=");
+	nm_utils_inet4_ntop (listen_address->address, localaddr);
+	g_string_append (s, localaddr);
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_free (s, TRUE);
+
+	if (!nm_dnsmasq_utils_get_range (listen_address, first, last, &error_desc)) {
+		g_set_error_literal (error,
+		                     NM_MANAGER_ERROR,
+		                     NM_MANAGER_ERROR_FAILED,
+		                     error_desc);
+		_LOGW ("failed to find DHCP address ranges: %s", error_desc);
+		g_free (error_desc);
+		nm_cmd_line_destroy (cmd);
+		return NULL;
+	}
+
+	s = g_string_new ("--dhcp-range=");
+	g_string_append_printf (s, "%s,%s,60m", first, last);
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_free (s, TRUE);
+
+	if (!nm_ip4_config_get_never_default (ip4_config)) {
+		s = g_string_new ("--dhcp-option=option:router,");
+		g_string_append (s, localaddr);
+		nm_cmd_line_add_string (cmd, s->str);
+		g_string_free (s, TRUE);
+	}
+
+	nm_cmd_line_add_string (cmd, "--dhcp-lease-max=50");
+
+	s = g_string_new ("--pid-file=");
+	g_string_append (s, pidfile);
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_free (s, TRUE);
+
+	/* dnsmasq exits if the conf dir is not present */
+	if (g_file_test (CONFDIR, G_FILE_TEST_IS_DIR))
+		nm_cmd_line_add_string (cmd, "--conf-dir=" CONFDIR);
+
+	return cmd;
+}
+
+static void
+kill_existing_by_pidfile (const char *pidfile)
+{
+	char *contents = NULL;
+	pid_t pid;
+	char proc_path[250];
+	char *cmdline_contents = NULL;
+	guint64 start_time;
+	const char *exe;
+
+	if (   !pidfile
+	    || !g_file_get_contents (pidfile, &contents, NULL, NULL))
+		return;
+
+	pid = _nm_utils_ascii_str_to_int64 (contents, 10, 1, G_MAXUINT64, 0);
+	if (pid == 0)
+		goto out;
+
+	start_time = nm_utils_get_start_time_for_pid (pid, NULL, NULL);
+	if (start_time == 0)
+		goto out;
+
+	nm_sprintf_buf (proc_path, "/proc/%lld/cmdline", (long long) pid);
+	if (!g_file_get_contents (proc_path, &cmdline_contents, NULL, NULL))
+		goto out;
+
+	exe = strrchr (cmdline_contents, '/');
+	if (   (exe && strcmp (&exe[1], "dnsmasq") == 0)
+	    || (strcmp (cmdline_contents, DNSMASQ_PATH) == 0)) {
+		nm_utils_kill_process_sync (pid, start_time, SIGKILL, LOGD_SHARING,
+		                            "dnsmasq", 0, 0, 500);
+	}
+
+out:
+	unlink (pidfile);
+	g_free (cmdline_contents);
+	g_free (contents);
+}
+
+gboolean
+nm_dnsmasq_manager_start (NMDnsMasqManager *manager,
+                          NMIP4Config *ip4_config,
+                          GError **error)
+{
+	NMDnsMasqManagerPrivate *priv;
+	NMCmdLine *dm_cmd;
+	gs_free char *cmd_str = NULL;
+
+	g_return_val_if_fail (NM_IS_DNSMASQ_MANAGER (manager), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (nm_ip4_config_get_num_addresses (ip4_config) > 0, FALSE);
+
+	priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+
+	kill_existing_by_pidfile (priv->pidfile);
+
+	dm_cmd = create_dm_cmd_line (priv->iface, ip4_config, priv->pidfile, error);
+	if (!dm_cmd)
+		return FALSE;
+
+	g_ptr_array_add (dm_cmd->array, NULL);
+
+	_LOGI ("starting dnsmasq...");
+	_LOGD ("command line: %s", (cmd_str = nm_cmd_line_to_str (dm_cmd)));
+
+	priv->pid = 0;
+	if (!g_spawn_async (NULL, (char **) dm_cmd->array->pdata, NULL,
+	                    G_SPAWN_DO_NOT_REAP_CHILD,
+	                    nm_utils_setpgid, NULL,
+	                    &priv->pid, error)) {
+		goto out;
+	}
+
+	_LOGD ("dnsmasq started with pid %d", priv->pid);
+
+	priv->dm_watch_id = g_child_watch_add (priv->pid, (GChildWatchFunc) dm_watch_cb, manager);
+
+ out:
+	if (dm_cmd)
+		nm_cmd_line_destroy (dm_cmd);
+
+	return priv->pid > 0;
+}
+
+void
+nm_dnsmasq_manager_stop (NMDnsMasqManager *manager)
+{
+	NMDnsMasqManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_DNSMASQ_MANAGER (manager));
+
+	priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+
+	nm_clear_g_source (&priv->dm_watch_id);
+
+	if (priv->pid) {
+		nm_utils_kill_child_async (priv->pid, SIGTERM, LOGD_SHARING, "dnsmasq", 2000, NULL, NULL);
+		priv->pid = 0;
+	}
+
+	unlink (priv->pidfile);
+}
+
+/*****************************************************************************/
+
+static void
+nm_dnsmasq_manager_init (NMDnsMasqManager *manager)
+{
+}
+
+NMDnsMasqManager *
+nm_dnsmasq_manager_new (const char *iface)
+{
+	NMDnsMasqManager *manager;
+	NMDnsMasqManagerPrivate *priv;
+
+	manager = (NMDnsMasqManager *) g_object_new (NM_TYPE_DNSMASQ_MANAGER, NULL);
+
+	priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
+	priv->iface = g_strdup (iface);
+	priv->pidfile = g_strdup_printf (RUNSTATEDIR "/nm-dnsmasq-%s.pid", iface);
+
+	return manager;
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE ((NMDnsMasqManager *) object);
+
+	nm_dnsmasq_manager_stop (NM_DNSMASQ_MANAGER (object));
+
+	g_free (priv->iface);
+	g_free (priv->pidfile);
+
+	G_OBJECT_CLASS (nm_dnsmasq_manager_parent_class)->finalize (object);
+}
+
+static void
+nm_dnsmasq_manager_class_init (NMDnsMasqManagerClass *manager_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
+
+	object_class->finalize = finalize;
+
+	signals[STATE_CHANGED] =
+	     g_signal_new (NM_DNS_MASQ_MANAGER_STATE_CHANGED,
+	                   G_OBJECT_CLASS_TYPE (object_class),
+	                   G_SIGNAL_RUN_FIRST,
+	                   0, NULL, NULL,
+	                   g_cclosure_marshal_VOID__UINT,
+	                   G_TYPE_NONE, 1,
+	                   G_TYPE_UINT);
+}
diff --git a/src/dnsmasq/nm-dnsmasq-manager.h b/src/dnsmasq/nm-dnsmasq-manager.h
new file mode 100644
index 00000000..a0ad295c
--- /dev/null
+++ b/src/dnsmasq/nm-dnsmasq-manager.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DNSMASQ_MANAGER_H__
+#define __NETWORKMANAGER_DNSMASQ_MANAGER_H__
+
+#include "nm-ip4-config.h"
+
+#define NM_TYPE_DNSMASQ_MANAGER            (nm_dnsmasq_manager_get_type ())
+#define NM_DNSMASQ_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNSMASQ_MANAGER, NMDnsMasqManager))
+#define NM_DNSMASQ_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNSMASQ_MANAGER, NMDnsMasqManagerClass))
+#define NM_IS_DNSMASQ_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DNSMASQ_MANAGER))
+#define NM_IS_DNSMASQ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DNSMASQ_MANAGER))
+#define NM_DNSMASQ_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DNSMASQ_MANAGER, NMDnsMasqManagerClass))
+
+/* signals */
+#define NM_DNS_MASQ_MANAGER_STATE_CHANGED "state-changed"
+
+typedef enum {
+	NM_DNSMASQ_STATUS_UNKNOWN,
+	NM_DNSMASQ_STATUS_DEAD,
+	NM_DNSMASQ_STATUS_RUNNING,
+} NMDnsMasqStatus;
+
+typedef struct _NMDnsMasqManager NMDnsMasqManager;
+typedef struct _NMDnsMasqManagerClass NMDnsMasqManagerClass;
+
+GType nm_dnsmasq_manager_get_type (void);
+
+NMDnsMasqManager *nm_dnsmasq_manager_new (const char *iface);
+
+gboolean nm_dnsmasq_manager_start (NMDnsMasqManager *manager,
+                                   NMIP4Config *ip4_config,
+                                   GError **error);
+
+void     nm_dnsmasq_manager_stop  (NMDnsMasqManager *manager);
+
+#endif /* __NETWORKMANAGER_DNSMASQ_MANAGER_H__ */
diff --git a/src/dnsmasq/nm-dnsmasq-utils.c b/src/dnsmasq/nm-dnsmasq-utils.c
new file mode 100644
index 00000000..e4f4324b
--- /dev/null
+++ b/src/dnsmasq/nm-dnsmasq-utils.c
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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 of the License, 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) 2013 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "nm-dnsmasq-utils.h"
+#include "platform/nm-platform.h"
+#include "nm-utils.h"
+
+gboolean
+nm_dnsmasq_utils_get_range (const NMPlatformIP4Address *addr,
+                            char *out_first,
+                            char *out_last,
+                            char **out_error_desc)
+{
+	guint32 host = addr->address;
+	guint8 prefix = addr->plen;
+	guint32 netmask = nm_utils_ip4_prefix_to_netmask (prefix);
+	guint32 first, last, reserved;
+
+	g_return_val_if_fail (out_first != NULL, FALSE);
+	g_return_val_if_fail (out_last != NULL, FALSE);
+
+	if (prefix > 30) {
+		if (out_error_desc)
+			*out_error_desc = g_strdup_printf ("Address prefix %d is too small for DHCP.", prefix);
+		return FALSE;
+	}
+
+	/* Find the first available address *after* the local machine's IP */
+	first = (host & netmask) + htonl (1);
+
+	/* Shortcut: allow a max of 253 addresses; the - htonl(1) here is to assure
+	 * that we don't set 'last' to the broadcast address of the network. */
+	if (prefix < 24)
+		last = (host | ~nm_utils_ip4_prefix_to_netmask (24)) - htonl (1);
+	else
+		last = (host | ~netmask) - htonl(1);
+
+	/* Figure out which range (either above the host address or below it)
+	 * has more addresses.  Reserve some addresses for static IPs.
+	 */
+	if (ntohl (host) - ntohl (first) > ntohl (last) - ntohl (host)) {
+		/* Range below the host's IP address */
+		reserved = (guint32) ((ntohl (host) - ntohl (first)) / 10);
+		last = host - htonl (MIN (reserved, 8)) - htonl (1);
+	} else {
+		/* Range above host's IP address */
+		reserved = (guint32) ((ntohl (last) - ntohl (host)) / 10);
+		first = host + htonl (MIN (reserved, 8)) + htonl (1);
+	}
+
+	nm_utils_inet4_ntop (first, out_first);
+	nm_utils_inet4_ntop (last, out_last);
+
+	return TRUE;
+}
+
diff --git a/src/dnsmasq/nm-dnsmasq-utils.h b/src/dnsmasq/nm-dnsmasq-utils.h
new file mode 100644
index 00000000..54b90309
--- /dev/null
+++ b/src/dnsmasq/nm-dnsmasq-utils.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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 of the License, 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) 2013 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DNSMASQ_UTILS_H__
+#define __NETWORKMANAGER_DNSMASQ_UTILS_H__
+
+#include "platform/nm-platform.h"
+
+gboolean nm_dnsmasq_utils_get_range (const NMPlatformIP4Address *addr,
+                                     char *out_first,
+                                     char *out_last,
+                                     char **out_error_desc);
+
+#endif /* __NETWORKMANAGER_DNSMASQ_UTILS_H__ */
diff --git a/src/dnsmasq/tests/test-dnsmasq-utils.c b/src/dnsmasq/tests/test-dnsmasq-utils.c
new file mode 100644
index 00000000..00ff6610
--- /dev/null
+++ b/src/dnsmasq/tests/test-dnsmasq-utils.c
@@ -0,0 +1,99 @@
+/* -*- 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) 2013 Red Hat, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include <arpa/inet.h>
+
+#include "dnsmasq/nm-dnsmasq-utils.h"
+
+#include "nm-test-utils-core.h"
+
+static void
+test_address_ranges (void)
+{
+	NMPlatformIP4Address addr;
+	char first[INET_ADDRSTRLEN];
+	char last[INET_ADDRSTRLEN];
+	char *error_desc = NULL;
+
+	addr = *nmtst_platform_ip4_address ("192.168.0.1", NULL, 24);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
+	g_assert (error_desc == NULL);
+	g_assert_cmpstr (first, ==, "192.168.0.10");
+	g_assert_cmpstr (last, ==, "192.168.0.254");
+
+	addr = *nmtst_platform_ip4_address ("192.168.0.99", NULL, 24);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
+	g_assert (error_desc == NULL);
+	g_assert_cmpstr (first, ==, "192.168.0.108");
+	g_assert_cmpstr (last, ==, "192.168.0.254");
+
+	addr = *nmtst_platform_ip4_address ("192.168.0.254", NULL, 24);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
+	g_assert (error_desc == NULL);
+	g_assert_cmpstr (first, ==, "192.168.0.1");
+	g_assert_cmpstr (last, ==, "192.168.0.245");
+
+	/* Smaller networks */
+	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 30);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
+	g_assert (error_desc == NULL);
+	g_assert_cmpstr (first, ==, "1.2.3.2");
+	g_assert_cmpstr (last, ==, "1.2.3.2");
+
+	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 29);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
+	g_assert (error_desc == NULL);
+	g_assert_cmpstr (first, ==, "1.2.3.2");
+	g_assert_cmpstr (last, ==, "1.2.3.6");
+
+	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 28);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
+	g_assert (error_desc == NULL);
+	g_assert_cmpstr (first, ==, "1.2.3.3");
+	g_assert_cmpstr (last, ==, "1.2.3.14");
+
+	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 26);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
+	g_assert (error_desc == NULL);
+	g_assert_cmpstr (first, ==, "1.2.3.8");
+	g_assert_cmpstr (last, ==, "1.2.3.62");
+
+	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 31);
+	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc) == FALSE);
+	g_assert (error_desc);
+	g_free (error_desc);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+
+	g_test_add_func ("/dnsmasq/address-ranges", test_address_ranges);
+
+	return g_test_run ();
+}
+