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
This commit is contained in:
Dan Williams 2012-10-12 15:25:23 -05:00
parent 13f4a00d4d
commit 32a001f526
10 changed files with 326 additions and 81 deletions

2
.gitignore vendored
View file

@ -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

View file

@ -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

View file

@ -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 */

View file

@ -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 \

View file

@ -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;

View file

@ -28,9 +28,11 @@
#include <stdlib.h>
#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);

View file

@ -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 <config.h>
#include <string.h>
#include <arpa/inet.h>
#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;
}

View file

@ -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 <glib.h>
#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 */

View file

@ -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

View file

@ -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 <glib.h>
#include <arpa/inet.h>
#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 ();
}