From 32a001f526c182fa70ede92ffc360112eb7fe9c6 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 12 Oct 2012 15:25:23 -0500 Subject: [PATCH] core: allow custom IP address ranges for Shared connections (bgo #675973) Given an IPv4 address and prefix for a shared config, figure out the DHCP address range automatically. To keep things simple we allow a max of 252 addresses (not including network address, broadcast address, and the hotspot) no matter what prefix you use, so if the address is 10.0.10.1, you still only get a range of 10.0.10.2 -> 10.0.10.254. But we also leave some addresses available above the host address for static stuff, like we did before. This is done on a sliding scale from 0 to 8 addresses, where about 1/10th the number of available addresses are reserved. https://bugzilla.gnome.org/show_bug.cgi?id=675973 --- .gitignore | 2 + configure.ac | 1 + libnm-util/nm-setting-ip4-config.c | 19 +-- src/Makefile.am | 3 + src/devices/nm-device.c | 79 +++++++----- src/dnsmasq-manager/nm-dnsmasq-manager.c | 59 +++------ src/dnsmasq-manager/nm-dnsmasq-utils.c | 77 ++++++++++++ src/dnsmasq-manager/nm-dnsmasq-utils.h | 32 +++++ src/dnsmasq-manager/tests/Makefile.am | 22 ++++ .../tests/test-dnsmasq-utils.c | 113 ++++++++++++++++++ 10 files changed, 326 insertions(+), 81 deletions(-) create mode 100644 src/dnsmasq-manager/nm-dnsmasq-utils.c create mode 100644 src/dnsmasq-manager/nm-dnsmasq-utils.h create mode 100644 src/dnsmasq-manager/tests/Makefile.am create mode 100644 src/dnsmasq-manager/tests/test-dnsmasq-utils.c diff --git a/.gitignore b/.gitignore index 36666d0607..8cdf3eb98a 100644 --- a/.gitignore +++ b/.gitignore @@ -186,7 +186,9 @@ valgrind-*.log /src/tests/test-policy-hosts /src/tests/test-wifi-ap-utils /src/tests/test-resolvconf-capture +/src/dnsmasq-manager/tests/test-dnsmasq-utils /src/dhcp-manager/tests/test-dhcp-dhclient +/src/dhcp-manager/tests/test-dnsmasq-utils /src/config/tests/test-config /src/settings/plugins/keyfile/tests/test-keyfile diff --git a/configure.ac b/configure.ac index 398b05a5d6..87126f20d1 100644 --- a/configure.ac +++ b/configure.ac @@ -739,6 +739,7 @@ src/tests/Makefile src/config/tests/Makefile src/dhcp-manager/Makefile src/dhcp-manager/tests/Makefile +src/dnsmasq-manager/tests/Makefile src/supplicant-manager/tests/Makefile src/ppp-manager/Makefile src/settings/plugins/Makefile diff --git a/libnm-util/nm-setting-ip4-config.c b/libnm-util/nm-setting-ip4-config.c index 4c21d0fb58..8a07293e40 100644 --- a/libnm-util/nm-setting-ip4-config.c +++ b/libnm-util/nm-setting-ip4-config.c @@ -739,14 +739,17 @@ verify (NMSetting *setting, GSList *all_settings, GError **error) return FALSE; } - if (g_slist_length (priv->addresses)) { - g_set_error (error, - NM_SETTING_IP4_CONFIG_ERROR, - NM_SETTING_IP4_CONFIG_ERROR_NOT_ALLOWED_FOR_METHOD, - _("this property is not allowed for '%s=%s'"), - NM_SETTING_IP4_CONFIG_METHOD, priv->method); - g_prefix_error (error, "%s.%s: ", NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_SETTING_IP4_CONFIG_ADDRESSES); - return FALSE; + /* Shared allows IP addresses; link-local and disabled do not */ + if (strcmp (priv->method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0) { + if (g_slist_length (priv->addresses)) { + g_set_error (error, + NM_SETTING_IP4_CONFIG_ERROR, + NM_SETTING_IP4_CONFIG_ERROR_NOT_ALLOWED_FOR_METHOD, + _("this property is not allowed for '%s=%s'"), + NM_SETTING_IP4_CONFIG_METHOD, priv->method); + g_prefix_error (error, "%s.%s: ", NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_SETTING_IP4_CONFIG_ADDRESSES); + return FALSE; + } } } else if (!strcmp (priv->method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { /* nothing to do */ diff --git a/src/Makefile.am b/src/Makefile.am index 5002abaeb4..3b0cd3f115 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,6 +16,7 @@ if ENABLE_TESTS SUBDIRS += \ config/tests \ dhcp-manager/tests \ + dnsmasq-manager/tests \ platform \ rdisc \ settings/tests \ @@ -125,6 +126,8 @@ nm_sources = \ \ dnsmasq-manager/nm-dnsmasq-manager.c \ dnsmasq-manager/nm-dnsmasq-manager.h \ + dnsmasq-manager/nm-dnsmasq-utils.c \ + dnsmasq-manager/nm-dnsmasq-utils.h \ \ firewall-manager/nm-firewall-manager.c \ firewall-manager/nm-firewall-manager.h \ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 4684efe967..9e8a169fd0 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -2782,51 +2782,64 @@ release_shared_ip (gpointer data) g_hash_table_remove (shared_ips, data); } -static guint32 -reserve_shared_ip (void) +static gboolean +reserve_shared_ip (NMSettingIP4Config *s_ip4, NMPlatformIP4Address *address) { - guint32 start = (guint32) ntohl (0x0a2a0001); /* 10.42.0.1 */ - guint32 count = 0; - - while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) { - count += ntohl (0x100); - if (count > ntohl (0xFE00)) { - nm_log_err (LOGD_SHARING, "ran out of shared IP addresses!"); - return 0; - } - } - - g_hash_table_insert (shared_ips, GUINT_TO_POINTER (start + count), GUINT_TO_POINTER (TRUE)); - return start + count; -} - -static NMIP4Config * -shared4_new_config (NMDevice *self, NMDeviceStateReason *reason) -{ - NMIP4Config *config = NULL; - NMPlatformIP4Address address; - guint32 tmp_addr; - - g_return_val_if_fail (self != NULL, NULL); - if (G_UNLIKELY (shared_ips == NULL)) shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal); - tmp_addr = reserve_shared_ip (); - if (!tmp_addr) { + memset (address, 0, sizeof (*address)); + + if (s_ip4 && nm_setting_ip4_config_get_num_addresses (s_ip4)) { + /* Use the first user-supplied address */ + NMIP4Address *user = nm_setting_ip4_config_get_address (s_ip4, 0); + + g_assert (user); + address->address = nm_ip4_address_get_address (user); + address->plen = nm_ip4_address_get_prefix (user); + } else { + /* Find an unused address in the 10.42.x.x range */ + guint32 start = (guint32) ntohl (0x0a2a0001); /* 10.42.0.1 */ + guint32 count = 0; + + while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) { + count += ntohl (0x100); + if (count > ntohl (0xFE00)) { + nm_log_err (LOGD_SHARING, "ran out of shared IP addresses!"); + return FALSE; + } + } + address->address = start + count; + address->plen = 24; + + g_hash_table_insert (shared_ips, + GUINT_TO_POINTER (address->address), + GUINT_TO_POINTER (TRUE)); + } + + return TRUE; +} + +static NMIP4Config * +shared4_new_config (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason) +{ + NMIP4Config *config = NULL; + NMPlatformIP4Address address; + + g_return_val_if_fail (self != NULL, NULL); + + if (!reserve_shared_ip (nm_connection_get_setting_ip4_config (connection), &address)) { *reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE; return NULL; } config = nm_ip4_config_new (); - memset (&address, 0, sizeof (address)); - address.address = tmp_addr; - address.plen = 24; nm_ip4_config_add_address (config, &address); /* Remove the address lock when the object gets disposed */ g_object_set_data_full (G_OBJECT (config), "shared-ip", - GUINT_TO_POINTER (tmp_addr), release_shared_ip); + GUINT_TO_POINTER (address.address), + release_shared_ip); return config; } @@ -2915,7 +2928,7 @@ act_stage3_ip4_config_start (NMDevice *self, g_assert (*out_config); ret = NM_ACT_STAGE_RETURN_SUCCESS; } else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) { - *out_config = shared4_new_config (self, reason); + *out_config = shared4_new_config (self, connection, reason); if (*out_config) { priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self)); ret = NM_ACT_STAGE_RETURN_SUCCESS; diff --git a/src/dnsmasq-manager/nm-dnsmasq-manager.c b/src/dnsmasq-manager/nm-dnsmasq-manager.c index fb1a367c99..21bbd23a51 100644 --- a/src/dnsmasq-manager/nm-dnsmasq-manager.c +++ b/src/dnsmasq-manager/nm-dnsmasq-manager.c @@ -28,9 +28,11 @@ #include #include "nm-dnsmasq-manager.h" +#include "nm-dnsmasq-utils.h" #include "nm-logging.h" #include "nm-glib-compat.h" #include "nm-posix-signals.h" +#include "nm-utils.h" typedef struct { char *iface; @@ -53,6 +55,7 @@ static guint signals[LAST_SIGNAL] = { 0 }; typedef enum { NM_DNSMASQ_MANAGER_ERROR_NOT_FOUND, + NM_DNSMASQ_MANAGER_ERROR_INVALID_IP_RANGE, } NMDnsMasqManagerError; GQuark @@ -250,9 +253,10 @@ create_dm_cmd_line (const char *iface, NMCmdLine *cmd; GString *s; const NMPlatformIP4Address *tmp; - guint32 addr; - char buf[INET_ADDRSTRLEN + 15]; - char localaddr[INET_ADDRSTRLEN + 1]; + char first[INET_ADDRSTRLEN]; + char last[INET_ADDRSTRLEN]; + char localaddr[INET_ADDRSTRLEN]; + char *error_desc = NULL; dm_binary = nm_find_dnsmasq (); if (!dm_binary) { @@ -294,48 +298,23 @@ create_dm_cmd_line (const char *iface, nm_cmd_line_add_string (cmd, "--strict-order"); s = g_string_new ("--listen-address="); - addr = tmp->address; - if (!inet_ntop (AF_INET, &addr, &localaddr[0], INET_ADDRSTRLEN)) { - char *err_msg = g_strdup_printf ("error converting IP4 address 0x%X", - ntohl (addr)); - g_set_error_literal (error, NM_DNSMASQ_MANAGER_ERROR, NM_DNSMASQ_MANAGER_ERROR_NOT_FOUND, err_msg); - nm_log_warn (LOGD_SHARING, "%s", err_msg); - g_free (err_msg); - goto error; - } + nm_utils_inet4_ntop (tmp->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 (tmp, first, last, &error_desc)) { + g_set_error_literal (error, + NM_DNSMASQ_MANAGER_ERROR, + NM_DNSMASQ_MANAGER_ERROR_INVALID_IP_RANGE, + error_desc); + nm_log_warn (LOGD_SHARING, "Failed to find DHCP address ranges: %s", error_desc); + g_free (error_desc); + goto error; + } + s = g_string_new ("--dhcp-range="); - - /* Add start of address range */ - addr = tmp->address + htonl (9); - if (!inet_ntop (AF_INET, &addr, &buf[0], INET_ADDRSTRLEN)) { - char *err_msg = g_strdup_printf ("error converting IP4 address 0x%X", - ntohl (addr)); - g_set_error_literal (error, NM_DNSMASQ_MANAGER_ERROR, NM_DNSMASQ_MANAGER_ERROR_NOT_FOUND, err_msg); - nm_log_warn (LOGD_SHARING, "%s", err_msg); - g_free (err_msg); - goto error; - } - g_string_append (s, buf); - - g_string_append_c (s, ','); - - /* Add end of address range */ - addr = tmp->address + htonl (99); - if (!inet_ntop (AF_INET, &addr, &buf[0], INET_ADDRSTRLEN)) { - char *err_msg = g_strdup_printf ("error converting IP4 address 0x%X", - ntohl (addr)); - g_set_error_literal (error, NM_DNSMASQ_MANAGER_ERROR, NM_DNSMASQ_MANAGER_ERROR_NOT_FOUND, err_msg); - nm_log_warn (LOGD_SHARING, "%s", err_msg); - g_free (err_msg); - goto error; - } - g_string_append (s, buf); - - g_string_append (s, ",60m"); + g_string_append_printf (s, "%s,%s,60m", first, last); nm_cmd_line_add_string (cmd, s->str); g_string_free (s, TRUE); diff --git a/src/dnsmasq-manager/nm-dnsmasq-utils.c b/src/dnsmasq-manager/nm-dnsmasq-utils.c new file mode 100644 index 0000000000..538a9e5266 --- /dev/null +++ b/src/dnsmasq-manager/nm-dnsmasq-utils.c @@ -0,0 +1,77 @@ +/* -*- 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 +#include +#include + +#include "nm-dnsmasq-utils.h" +#include "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; + guint32 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 (CLAMP (reserved, 0, 8)) - htonl (1); + } else { + /* Range above host's IP address */ + reserved = (guint32) ((ntohl (last) - ntohl (host)) / 10); + first = host + htonl (CLAMP (reserved, 0, 8)) + htonl (1); + } + + nm_utils_inet4_ntop (first, out_first); + nm_utils_inet4_ntop (last, out_last); + + return TRUE; +} + diff --git a/src/dnsmasq-manager/nm-dnsmasq-utils.h b/src/dnsmasq-manager/nm-dnsmasq-utils.h new file mode 100644 index 0000000000..4534c6ef24 --- /dev/null +++ b/src/dnsmasq-manager/nm-dnsmasq-utils.h @@ -0,0 +1,32 @@ +/* -*- 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 NM_DNSMASQ_UTILS_H +#define NM_DNSMASQ_UTILS_H + +#include +#include "nm-platform.h" + +gboolean nm_dnsmasq_utils_get_range (const NMPlatformIP4Address *addr, + char *out_first, + char *out_last, + char **out_error_desc); + +#endif /* NM_DNSMASQ_UTILS_H */ diff --git a/src/dnsmasq-manager/tests/Makefile.am b/src/dnsmasq-manager/tests/Makefile.am new file mode 100644 index 0000000000..df62a2bffe --- /dev/null +++ b/src/dnsmasq-manager/tests/Makefile.am @@ -0,0 +1,22 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I${top_srcdir}/libnm-util \ + -I${top_builddir}/libnm-util \ + -I$(top_srcdir)/src/dnsmasq-manager \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/platform \ + $(GLIB_CFLAGS) \ + -DTESTDIR="\"$(abs_srcdir)\"" + +noinst_PROGRAMS = test-dnsmasq-utils + +test_dnsmasq_utils_SOURCES = \ + test-dnsmasq-utils.c + +test_dnsmasq_utils_LDADD = \ + $(top_builddir)/src/libNetworkManager.la + +check-local: test-dnsmasq-utils + $(abs_builddir)/test-dnsmasq-utils + diff --git a/src/dnsmasq-manager/tests/test-dnsmasq-utils.c b/src/dnsmasq-manager/tests/test-dnsmasq-utils.c new file mode 100644 index 0000000000..5854309884 --- /dev/null +++ b/src/dnsmasq-manager/tests/test-dnsmasq-utils.c @@ -0,0 +1,113 @@ +/* -*- 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 +#include + +#include "nm-dnsmasq-utils.h" + +static guint32 +addr_to_num (const char *addr) +{ + guint n; + + g_assert (inet_pton (AF_INET, addr, (void *) &n) == 1); + return n; +} + +static void +test_address_ranges (void) +{ + NMPlatformIP4Address addr; + char first[INET_ADDRSTRLEN]; + char last[INET_ADDRSTRLEN]; + char *error_desc = NULL; + + addr.address = addr_to_num ("192.168.0.1"); + addr.plen = 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.address = addr_to_num ("192.168.0.99"); + addr.plen = 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.address = addr_to_num ("192.168.0.254"); + addr.plen = 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.address = addr_to_num ("1.2.3.1"); + addr.plen = 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.address = addr_to_num ("1.2.3.1"); + addr.plen = 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.address = addr_to_num ("1.2.3.1"); + addr.plen = 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.address = addr_to_num ("1.2.3.1"); + addr.plen = 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.address = addr_to_num ("1.2.3.1"); + addr.plen = 31; + g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc) == FALSE); + g_assert (error_desc); + g_free (error_desc); +} + +/*******************************************/ + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + + g_type_init (); + + g_test_add_func ("/dnsmasq-manager/address-ranges", test_address_ranges); + + return g_test_run (); +} +