Add --shared-network DHCP configuration.

This commit is contained in:
Simon Kelley
2019-03-27 22:33:28 +00:00
parent 305ffb5ef0
commit ae5b7e04a1
8 changed files with 333 additions and 135 deletions

View File

@@ -34,6 +34,11 @@ version 2.81
tries not to request capabilities not required by its
configuration.
Add --shared-network config. This enables allocation of addresses
the DHCP server in subnets where the server (or relay) doesn't
have an interface on the network in that subnet. Many thanks to
plank.de for sponsoring this feature.
version 2.80
Add support for RFC 4039 DHCP rapid commit. Thanks to Ashram Method

View File

@@ -1740,6 +1740,27 @@ wildcard can be used in each <alias>.
It is permissible to add more than one alias using more than one \fB--bridge-interface\fP option since
\fB--bridge-interface=int1,alias1,alias2\fP is exactly equivalent to
\fB--bridge-interface=int1,alias1 --bridge-interface=int1,alias2\fP
.TP
.B --shared-network=<interface>|<addr>,<addr>
The DHCP server determines which dhcp ranges are useable for allocating and
address to a DHCP client based on the network from which the DHCP request arrives,
and the IP configuration of the server's interface on that network. The shared-network
option extends the available subnets (and therefore dhcp ranges) beyond the
subnets configured on the arrival interface. The first argument is either the
name of an interface or an address which is configured on a local interface, and the
second argument is an address which defines another subnet on which addresses can be allocated.
To be useful, there must be suitable dhcp-range which allows address allocation on this subnet
and this dhcp-range MUST include the netmask. Use shared-network also needs extra
consideration of routing. Dnsmasq doesn't have the usual information which it uses to
determine the default route, so the default route option (or other routing) MUST be
manually configured. The client must have a route to the server: if the two-address form
of shared-network is used, this will be to the first specified address. If the interface,address
form is used, there must be a route to all of the addresses configured on the interface.
The two-address form of shared-network is also usable with a DHCP relay: the first address
is the address of the relay and the second, as before, specifies an extra subnet which
may be allocated.
.TP
.B \-s, --domain=<domain>[,<address range>[,local]]
Specifies DNS domains for the DHCP server. Domains may be be given

View File

@@ -507,33 +507,83 @@ static int check_listen_addrs(struct in_addr local, int if_index, char *label,
Note that the current chain may be superseded later for configured hosts or those coming via gateways. */
static int complete_context(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam)
static void guess_range_netmask(struct in_addr addr, struct in_addr netmask)
{
struct dhcp_context *context;
struct dhcp_relay *relay;
struct iface_param *param = vparam;
(void)label;
for (context = daemon->dhcp; context; context = context->next)
{
if (!(context->flags & CONTEXT_NETMASK) &&
(is_same_net(local, context->start, netmask) ||
is_same_net(local, context->end, netmask)))
if (!(context->flags & CONTEXT_NETMASK) &&
(is_same_net(addr, context->start, netmask) ||
is_same_net(addr, context->end, netmask)))
{
if (context->netmask.s_addr != netmask.s_addr &&
!(is_same_net(local, context->start, netmask) &&
is_same_net(local, context->end, netmask)))
!(is_same_net(addr, context->start, netmask) &&
is_same_net(addr, context->end, netmask)))
{
strcpy(daemon->dhcp_buff, inet_ntoa(context->start));
strcpy(daemon->dhcp_buff2, inet_ntoa(context->end));
my_syslog(MS_DHCP | LOG_WARNING, _("DHCP range %s -- %s is not consistent with netmask %s"),
daemon->dhcp_buff, daemon->dhcp_buff2, inet_ntoa(netmask));
}
context->netmask = netmask;
context->netmask = netmask;
}
}
static int complete_context(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam)
{
struct dhcp_context *context;
struct dhcp_relay *relay;
struct iface_param *param = vparam;
struct shared_network *share;
(void)label;
for (share = daemon->shared_networks; share; share = share->next)
{
#ifdef HAVE_DHCP6
if (share->shared_addr.s_addr == 0)
continue;
#endif
if (share->if_index != 0)
{
if (share->if_index != if_index)
continue;
}
else
{
if (share->match_addr.s_addr != local.s_addr)
continue;
}
for (context = daemon->dhcp; context; context = context->next)
{
if (context->netmask.s_addr != 0 &&
is_same_net(share->shared_addr, context->start, context->netmask) &&
is_same_net(share->shared_addr, context->end, context->netmask))
{
/* link it onto the current chain if we've not seen it before */
if (context->current == context)
{
/* For a shared network, we have no way to guess what the default route should be. */
context->router.s_addr = 0;
context->local = local; /* Use configured address for Server Identifier */
context->current = param->current;
param->current = context;
}
if (!(context->flags & CONTEXT_BRDCAST))
context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr;
}
}
}
guess_range_netmask(local, netmask);
for (context = daemon->dhcp; context; context = context->next)
{
if (context->netmask.s_addr != 0 &&
is_same_net(local, context->start, context->netmask) &&
is_same_net(local, context->end, context->netmask))

View File

@@ -299,89 +299,114 @@ static int complete_context6(struct in6_addr *local, int prefix,
unsigned int valid, void *vparam)
{
struct dhcp_context *context;
struct shared_network *share;
struct dhcp_relay *relay;
struct iface_param *param = vparam;
struct iname *tmp;
(void)scope; /* warning */
if (if_index == param->ind)
{
if (IN6_IS_ADDR_LINKLOCAL(local))
param->ll_addr = *local;
else if (IN6_IS_ADDR_ULA(local))
param->ula_addr = *local;
if (!IN6_IS_ADDR_LOOPBACK(local) &&
!IN6_IS_ADDR_LINKLOCAL(local) &&
!IN6_IS_ADDR_MULTICAST(local))
{
/* if we have --listen-address config, see if the
arrival interface has a matching address. */
for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
if (tmp->addr.sa.sa_family == AF_INET6 &&
IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local))
param->addr_match = 1;
/* Determine a globally address on the arrival interface, even
if we have no matching dhcp-context, because we're only
allocating on remote subnets via relays. This
is used as a default for the DNS server option. */
param->fallback = *local;
for (context = daemon->dhcp6; context; context = context->next)
{
if ((context->flags & CONTEXT_DHCP) &&
!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
prefix <= context->prefix &&
is_same_net6(local, &context->start6, context->prefix) &&
is_same_net6(local, &context->end6, context->prefix))
{
/* link it onto the current chain if we've not seen it before */
if (context->current == context)
{
struct dhcp_context *tmp, **up;
/* use interface values only for constructed contexts */
if (!(context->flags & CONTEXT_CONSTRUCTED))
preferred = valid = 0xffffffff;
else if (flags & IFACE_DEPRECATED)
preferred = 0;
if (context->flags & CONTEXT_DEPRECATE)
preferred = 0;
/* order chain, longest preferred time first */
for (up = &param->current, tmp = param->current; tmp; tmp = tmp->current)
if (tmp->preferred <= preferred)
break;
else
up = &tmp->current;
context->current = *up;
*up = context;
context->local6 = *local;
context->preferred = preferred;
context->valid = valid;
}
}
}
}
for (relay = daemon->relay6; relay; relay = relay->next)
if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6) && relay->current == relay &&
(IN6_IS_ADDR_UNSPECIFIED(&param->relay_local) || IN6_ARE_ADDR_EQUAL(local, &param->relay_local)))
{
relay->current = param->relay;
param->relay = relay;
param->relay_local = *local;
}
if (if_index != param->ind)
return 1;
if (IN6_IS_ADDR_LINKLOCAL(local))
param->ll_addr = *local;
else if (IN6_IS_ADDR_ULA(local))
param->ula_addr = *local;
}
return 1;
if (IN6_IS_ADDR_LOOPBACK(local) ||
IN6_IS_ADDR_LINKLOCAL(local) ||
IN6_IS_ADDR_MULTICAST(local))
return 1;
/* if we have --listen-address config, see if the
arrival interface has a matching address. */
for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
if (tmp->addr.sa.sa_family == AF_INET6 &&
IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local))
param->addr_match = 1;
/* Determine a globally address on the arrival interface, even
if we have no matching dhcp-context, because we're only
allocating on remote subnets via relays. This
is used as a default for the DNS server option. */
param->fallback = *local;
for (context = daemon->dhcp6; context; context = context->next)
if ((context->flags & CONTEXT_DHCP) &&
!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
prefix <= context->prefix &&
context->current == context)
{
if (is_same_net6(local, &context->start6, context->prefix) &&
is_same_net6(local, &context->end6, context->prefix))
{
struct dhcp_context *tmp, **up;
/* use interface values only for constructed contexts */
if (!(context->flags & CONTEXT_CONSTRUCTED))
preferred = valid = 0xffffffff;
else if (flags & IFACE_DEPRECATED)
preferred = 0;
if (context->flags & CONTEXT_DEPRECATE)
preferred = 0;
/* order chain, longest preferred time first */
for (up = &param->current, tmp = param->current; tmp; tmp = tmp->current)
if (tmp->preferred <= preferred)
break;
else
up = &tmp->current;
context->current = *up;
*up = context;
context->local6 = *local;
context->preferred = preferred;
context->valid = valid;
}
else
{
for (share = daemon->shared_networks; share; share = share->next)
{
/* IPv4 shared_address - ignore */
if (share->shared_addr.s_addr != 0)
continue;
if (share->if_index != 0)
{
if (share->if_index != if_index)
continue;
}
else
{
if (!IN6_ARE_ADDR_EQUAL(&share->match_addr6, local))
continue;
}
if (is_same_net6(&share->shared_addr6, &context->start6, context->prefix) &&
is_same_net6(&share->shared_addr6, &context->end6, context->prefix))
{
context->current = param->current;
param->current = context;
context->local6 = *local;
context->preferred = context->flags & CONTEXT_DEPRECATE ? 0 :0xffffffff;
context->valid = 0xffffffff;
}
}
}
}
for (relay = daemon->relay6; relay; relay = relay->next)
if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6) && relay->current == relay &&
(IN6_IS_ADDR_UNSPECIFIED(&param->relay_local) || IN6_ARE_ADDR_EQUAL(local, &param->relay_local)))
{
relay->current = param->relay;
param->relay = relay;
param->relay_local = *local;
}
return 1;
}
struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)

View File

@@ -910,6 +910,16 @@ struct dhcp_context {
struct dhcp_context *next, *current;
};
struct shared_network {
int if_index;
struct in_addr match_addr, shared_addr;
#ifdef HAVE_DHCP6
/* shared_addr == 0 for IP6 entries. */
struct in6_addr match_addr6, shared_addr6;
#endif
struct shared_network *next;
};
#define CONTEXT_STATIC (1u<<0)
#define CONTEXT_NETMASK (1u<<1)
#define CONTEXT_BRDCAST (1u<<2)
@@ -1107,6 +1117,7 @@ extern struct daemon {
struct ping_result *ping_results;
FILE *lease_stream;
struct dhcp_bridge *bridges;
struct shared_network *shared_networks;
#ifdef HAVE_DHCP6
int duid_len;
unsigned char *duid;

View File

@@ -166,6 +166,7 @@ struct myoption {
#define LOPT_UBUS 354
#define LOPT_NAME_MATCH 355
#define LOPT_CAA 356
#define LOPT_SHARED_NET 357
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -259,6 +260,7 @@ static const struct myoption opts[] =
{ "ptr-record", 1, 0, LOPT_PTR },
{ "naptr-record", 1, 0, LOPT_NAPTR },
{ "bridge-interface", 1, 0 , LOPT_BRIDGE },
{ "shared-network", 1, 0, LOPT_SHARED_NET },
{ "dhcp-option-force", 1, 0, LOPT_FORCE },
{ "tftp-no-blocksize", 0, 0, LOPT_NOBLOCK },
{ "log-dhcp", 0, 0, LOPT_LOG_OPTS },
@@ -431,6 +433,7 @@ static struct {
{ '3', ARG_DUP, "[=tag:<tag>]...", gettext_noop("Enable dynamic address allocation for bootp."), NULL },
{ '4', ARG_DUP, "set:<tag>,<mac address>", gettext_noop("Map MAC address (with wildcards) to option set."), NULL },
{ LOPT_BRIDGE, ARG_DUP, "<iface>,<alias>..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL },
{ LOPT_SHARED_NET, ARG_DUP, "<iface>|<addr>,<addr>", gettext_noop("Specify extra networks sharing a broadcast domain for DHCP"), NULL},
{ '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL },
{ '6', ARG_ONE, "<path>", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL },
{ LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL },
@@ -2873,6 +2876,44 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
#ifdef HAVE_DHCP
case LOPT_SHARED_NET: /* --shared-network */
{
struct shared_network *new = opt_malloc(sizeof(struct shared_network));
#ifdef HAVE_DHCP6
new->shared_addr.s_addr = 0;
#endif
new->if_index = 0;
if (!(comma = split(arg)))
{
snerr:
free(new);
ret_err(_("bad shared-network"));
}
if (inet_pton(AF_INET, comma, &new->shared_addr))
{
if (!inet_pton(AF_INET, arg, &new->match_addr) &&
!(new->if_index = if_nametoindex(arg)))
goto snerr;
}
#ifdef HAVE_DHCP6
else if (inet_pton(AF_INET6, comma, &new->shared_addr6))
{
if (!inet_pton(AF_INET6, arg, &new->match_addr6) &&
!(new->if_index = if_nametoindex(arg)))
goto snerr;
}
#endif
else
goto snerr;
new->next = daemon->shared_networks;
daemon->shared_networks = new;
break;
}
case 'F': /* --dhcp-range */
{
int k, leasepos = 2;

View File

@@ -274,8 +274,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (mess->giaddr.s_addr || subnet_addr.s_addr || mess->ciaddr.s_addr)
{
struct dhcp_context *context_tmp, *context_new = NULL;
struct shared_network *share = NULL;
struct in_addr addr;
int force = 0;
int force = 0, via_relay = 0;
if (subnet_addr.s_addr)
{
@@ -286,6 +287,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
{
addr = mess->giaddr;
force = 1;
via_relay = 1;
}
else
{
@@ -302,42 +304,65 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
if (!context_new)
for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
{
struct in_addr netmask = context_tmp->netmask;
{
for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
{
struct in_addr netmask = context_tmp->netmask;
/* guess the netmask for relayed networks */
if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0)
{
if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr)))
netmask.s_addr = htonl(0xff000000);
else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr)))
netmask.s_addr = htonl(0xffff0000);
else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr)))
netmask.s_addr = htonl(0xffffff00);
}
/* guess the netmask for relayed networks */
if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0)
{
if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr)))
netmask.s_addr = htonl(0xff000000);
else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr)))
netmask.s_addr = htonl(0xffff0000);
else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr)))
netmask.s_addr = htonl(0xffffff00);
}
/* This section fills in context mainly when a client which is on a remote (relayed)
network renews a lease without using the relay, after dnsmasq has restarted. */
if (netmask.s_addr != 0 &&
is_same_net(addr, context_tmp->start, netmask) &&
is_same_net(addr, context_tmp->end, netmask))
{
context_tmp->netmask = netmask;
if (context_tmp->local.s_addr == 0)
context_tmp->local = fallback;
if (context_tmp->router.s_addr == 0)
context_tmp->router = mess->giaddr;
/* fill in missing broadcast addresses for relayed ranges */
if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 )
context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr;
context_tmp->current = context_new;
context_new = context_tmp;
}
}
/* check to see is a context is OK because of a shared address on
the relayed subnet. */
if (via_relay)
for (share = daemon->shared_networks; share; share = share->next)
{
#ifdef HAVE_DHCP6
if (share->shared_addr.s_addr == 0)
continue;
#endif
if (share->if_index != 0 ||
share->match_addr.s_addr != mess->giaddr.s_addr)
continue;
if (netmask.s_addr != 0 &&
is_same_net(share->shared_addr, context_tmp->start, netmask) &&
is_same_net(share->shared_addr, context_tmp->end, netmask))
break;
}
/* This section fills in context mainly when a client which is on a remote (relayed)
network renews a lease without using the relay, after dnsmasq has restarted. */
if (share ||
(netmask.s_addr != 0 &&
is_same_net(addr, context_tmp->start, netmask) &&
is_same_net(addr, context_tmp->end, netmask)))
{
context_tmp->netmask = netmask;
if (context_tmp->local.s_addr == 0)
context_tmp->local = fallback;
if (context_tmp->router.s_addr == 0 && !share)
context_tmp->router = mess->giaddr;
/* fill in missing broadcast addresses for relayed ranges */
if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 )
context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr;
context_tmp->current = context_new;
context_new = context_tmp;
}
}
}
if (context_new || force)
context = context_new;
}

View File

@@ -134,21 +134,41 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz,
else
{
struct dhcp_context *c;
struct shared_network *share = NULL;
state->context = NULL;
if (!IN6_IS_ADDR_LOOPBACK(state->link_address) &&
!IN6_IS_ADDR_LINKLOCAL(state->link_address) &&
!IN6_IS_ADDR_MULTICAST(state->link_address))
for (c = daemon->dhcp6; c; c = c->next)
if ((c->flags & CONTEXT_DHCP) &&
!(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
is_same_net6(state->link_address, &c->start6, c->prefix) &&
is_same_net6(state->link_address, &c->end6, c->prefix))
{
c->preferred = c->valid = 0xffffffff;
c->current = state->context;
state->context = c;
}
{
for (share = daemon->shared_networks; share; share = share->next)
{
if (share->shared_addr.s_addr != 0)
continue;
if (share->if_index != 0 ||
!IN6_ARE_ADDR_EQUAL(state->link_address, &share->match_addr6))
continue;
if ((c->flags & CONTEXT_DHCP) &&
!(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
is_same_net6(&share->shared_addr6, &c->start6, c->prefix) &&
is_same_net6(&share->shared_addr6, &c->end6, c->prefix))
break;
}
if (share ||
((c->flags & CONTEXT_DHCP) &&
!(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
is_same_net6(state->link_address, &c->start6, c->prefix) &&
is_same_net6(state->link_address, &c->end6, c->prefix)))
{
c->preferred = c->valid = 0xffffffff;
c->current = state->context;
state->context = c;
}
}
if (!state->context)
{