From ae5b7e04a1025167f1b80840e61432a3cea9625c Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 27 Mar 2019 22:33:28 +0000 Subject: [PATCH 46/57] Add --shared-network DHCP configuration. Signed-off-by: Kevin Darbyshire-Bryant --- CHANGELOG | 5 ++ man/dnsmasq.8 | 21 ++++++ src/dhcp.c | 76 ++++++++++++++++++---- src/dhcp6.c | 175 ++++++++++++++++++++++++++++---------------------- src/dnsmasq.h | 11 ++++ src/option.c | 41 ++++++++++++ src/rfc2131.c | 97 +++++++++++++++++----------- src/rfc3315.c | 40 +++++++++--- 8 files changed, 332 insertions(+), 134 deletions(-) --- a/CHANGELOG +++ b/CHANGELOG @@ -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 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1741,6 +1741,27 @@ It is permissible to add more than one a \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=|, +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=[,
[,local]] Specifies DNS domains for the DHCP server. Domains may be be given unconditionally (without the IP range) or for limited IP ranges. This has two effects; --- a/src/dhcp.c +++ b/src/dhcp.c @@ -507,33 +507,83 @@ static int check_listen_addrs(struct in_ 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)) --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -299,89 +299,114 @@ static int complete_context6(struct in6_ 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 = ¶m->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(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->relay_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; + + 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)) { - relay->current = param->relay; - param->relay = relay; - param->relay_local = *local; + 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 = ¶m->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; } - - } - - return 1; + 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(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->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) --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -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; --- a/src/option.c +++ b/src/option.c @@ -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:]...", gettext_noop("Enable dynamic address allocation for bootp."), NULL }, { '4', ARG_DUP, "set:,", gettext_noop("Map MAC address (with wildcards) to option set."), NULL }, { LOPT_BRIDGE, ARG_DUP, ",..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL }, + { LOPT_SHARED_NET, ARG_DUP, "|,", 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, "", 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 } #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; --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -274,8 +274,9 @@ size_t dhcp_reply(struct dhcp_context *c 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 *c { addr = mess->giaddr; force = 1; + via_relay = 1; } else { @@ -302,42 +304,65 @@ size_t dhcp_reply(struct dhcp_context *c } 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; } --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -134,21 +134,41 @@ static int dhcp6_maybe_relay(struct stat 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) {