summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2013-09-04 18:01:38 +0100
committerSimon Kelley <simon@thekelleys.org.uk>2013-09-04 18:01:38 +0100
commitff7eea27e7560fde52ea236111abfe319aa7ed85 (patch)
treebfbe025cb9023845daa9e4f628db051caa375275
parent6692a1a53f294f3043f9539a5b0453b4ccc32fd6 (diff)
downloaddnsmasq-2.67test10.tar.gz
Add --dhcp-relay config option.v2.67test10
-rw-r--r--CHANGELOG2
-rw-r--r--man/dnsmasq.832
-rw-r--r--src/dhcp-common.c12
-rw-r--r--src/dhcp.c206
-rw-r--r--src/dhcp6.c221
-rw-r--r--src/dnsmasq.c90
-rw-r--r--src/dnsmasq.h11
-rw-r--r--src/netlink.c10
-rw-r--r--src/network.c2
-rw-r--r--src/option.c28
-rw-r--r--src/outpacket.c4
-rw-r--r--src/rfc3315.c120
12 files changed, 545 insertions, 193 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 78b9316..3fa133c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -101,6 +101,8 @@ version 2.67
(provide TFTP to the same interfaces we provide DHCP to)
is retained. Thanks to Lonnie Abelbeck for the suggestion.
+ Add --dhcp-relay config option.
+
version 2.66
Add the ability to act as an authoritative DNS
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 4357d99..05733b8 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -973,6 +973,38 @@ DHCP options. This make extra space available in the DHCP packet for
options but can, rarely, confuse old or broken clients. This flag
forces "simple and safe" behaviour to avoid problems in such a case.
.TP
+.B --dhcp-relay=<local address>,<server address>[,<interface]
+Configure dnsmasq to do DHCP relay. The local address is an address
+allocated to an interface on the host running dnsmasq. All DHCP
+requests arriving on that interface will we relayed to a remote DHCP
+server at the server address. It is possible to relay from a single local
+address to multiple remote servers by using multiple dhcp-relay
+configs with the same local address and different server
+addresses. A server address must be an IP literal address, not a
+domain name. In the case of DHCPv6, the server address may be the
+ALL_SERVERS multicast address, ff05::1:3. In this case the interface
+must be given, not be wildcard, and is used to direct the multicast to the
+correct interface to reach the DHCP server.
+
+Access control for DHCP clients has the same rules as for the DHCP
+server, see --interface, --except-interface, etc. The optional
+interface name in the dhcp-relay config has a different function: it
+controls on which interface DHCP replies from the server will be
+accepted. This is intended for configurations which have three
+interfaces: one being relayed from, a second connecting the DHCP
+server, and a third untrusted network, typically the wider
+internet. It avoids the possibility of spoof replies arriving via this
+third interface.
+
+It is allowed to have dnsmasq act as a DHCP server on one set of
+interfaces and relay from a disjoint set of interfaces. Note that
+whilst it is quite possible to write configurations which appear to
+act as a server and a relay on the same interface, this is not
+supported: the relay function will take precedence.
+
+Both DHCPv4 and DHCPv6 relay is supported. It's not possible to relay
+DHCPv4 to a DHCPv6 server or vice-versa.
+.TP
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,]<vendor-class>
Map from a vendor-class string to a tag. Most DHCP clients provide a
"vendor class" which represents, in some sense, the type of host. This option
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index e5e136a..939f25a 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -757,6 +757,16 @@ void log_context(int family, struct dhcp_context *context)
#endif
}
-
+void log_relay(int family, struct dhcp_relay *relay)
+{
+ inet_ntop(family, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+ inet_ntop(family, &relay->server, daemon->namebuff, ADDRSTRLEN);
+
+ if (relay->interface)
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s via %s"), daemon->addrbuff, daemon->namebuff, relay->interface);
+ else
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s"), daemon->addrbuff, daemon->namebuff);
+}
+
#endif
diff --git a/src/dhcp.c b/src/dhcp.c
index b95a4ba..573de5b 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -20,6 +20,8 @@
struct iface_param {
struct dhcp_context *current;
+ struct dhcp_relay *relay;
+ struct in_addr relay_local;
int ind;
};
@@ -32,6 +34,8 @@ static int complete_context(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam);
static int check_listen_addrs(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam);
+static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index);
+static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface);
static int make_fd(int port)
{
@@ -132,6 +136,8 @@ void dhcp_packet(time_t now, int pxe_fd)
int fd = pxe_fd ? daemon->pxefd : daemon->dhcpfd;
struct dhcp_packet *mess;
struct dhcp_context *context;
+ struct dhcp_relay *relay;
+ int is_relay_reply = 0;
struct iname *tmp;
struct ifreq ifr;
struct msghdr msg;
@@ -250,57 +256,86 @@ void dhcp_packet(time_t now, int pxe_fd)
unicast_dest = 1;
#endif
- ifr.ifr_addr.sa_family = AF_INET;
- if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 )
- iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
- else
+ if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name)))
{
- my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name);
- return;
+ /* Reply from server, using us as relay. */
+ iface_index = relay->iface_index;
+ if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name))
+ return;
+ is_relay_reply = 1;
+ iov.iov_len = sz;
+#ifdef HAVE_LINUX_NETWORK
+ strncpy(arp_req.arp_dev, ifr.ifr_name, 16);
+#endif
}
-
- for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
- if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
- return;
-
- /* unlinked contexts are marked by context->current == context */
- for (context = daemon->dhcp; context; context = context->next)
- context->current = context;
-
- parm.current = NULL;
- parm.ind = iface_index;
-
- if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL))
+ else
{
- /* If we failed to match the primary address of the interface, see if we've got a --listen-address
- for a secondary */
- struct match_param match;
+ ifr.ifr_addr.sa_family = AF_INET;
+ if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 )
+ iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
+ else
+ {
+ my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name);
+ return;
+ }
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ return;
+
+ /* unlinked contexts/relays are marked by context->current == context */
+ for (context = daemon->dhcp; context; context = context->next)
+ context->current = context;
+
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ relay->current = relay;
- match.matched = 0;
- match.ind = iface_index;
+ parm.current = NULL;
+ parm.relay = NULL;
+ parm.relay_local.s_addr = 0;
+ parm.ind = iface_index;
- if (!daemon->if_addrs ||
- !iface_enumerate(AF_INET, &match, check_listen_addrs) ||
- !match.matched)
+ if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL))
+ {
+ /* If we failed to match the primary address of the interface, see if we've got a --listen-address
+ for a secondary */
+ struct match_param match;
+
+ match.matched = 0;
+ match.ind = iface_index;
+
+ if (!daemon->if_addrs ||
+ !iface_enumerate(AF_INET, &match, check_listen_addrs) ||
+ !match.matched)
+ return;
+
+ iface_addr = match.addr;
+ /* make sure secondary address gets priority in case
+ there is more than one address on the interface in the same subnet */
+ complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm);
+ }
+
+ if (!iface_enumerate(AF_INET, &parm, complete_context))
+ return;
+
+ /* We're relaying this request */
+ if (parm.relay_local.s_addr != 0 &&
+ relay_upstream4(parm.relay, (struct dhcp_packet *)daemon->dhcp_packet.iov_base, (size_t)sz, iface_index))
+ return;
+
+ /* May have configured relay, but not DHCP server */
+ if (!daemon->dhcp)
return;
- iface_addr = match.addr;
- /* make sure secondary address gets priority in case
- there is more than one address on the interface in the same subnet */
- complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm);
- }
+ lease_prune(NULL, now); /* lose any expired leases */
+ iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz,
+ now, unicast_dest, &is_inform, pxe_fd, iface_addr);
+ lease_update_file(now);
+ lease_update_dns(0);
- if (!iface_enumerate(AF_INET, &parm, complete_context))
- return;
-
- lease_prune(NULL, now); /* lose any expired leases */
- iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz,
- now, unicast_dest, &is_inform, pxe_fd, iface_addr);
- lease_update_file(now);
- lease_update_dns(0);
-
- if (iov.iov_len == 0)
- return;
+ if (iov.iov_len == 0)
+ return;
+ }
msg.msg_name = &dest;
msg.msg_namelen = sizeof(dest);
@@ -321,7 +356,7 @@ void dhcp_packet(time_t now, int pxe_fd)
if (mess->ciaddr.s_addr != 0)
dest.sin_addr = mess->ciaddr;
}
- else if (mess->giaddr.s_addr)
+ else if (mess->giaddr.s_addr && !is_relay_reply)
{
/* Send to BOOTP relay */
dest.sin_port = htons(daemon->dhcp_server_port);
@@ -334,7 +369,7 @@ void dhcp_packet(time_t now, int pxe_fd)
source port too, and send back to that. If we're replying
to a DHCPINFORM, trust the source address always. */
if ((!is_inform && dest.sin_addr.s_addr != mess->ciaddr.s_addr) ||
- dest.sin_port == 0 || dest.sin_addr.s_addr == 0)
+ dest.sin_port == 0 || dest.sin_addr.s_addr == 0 || is_relay_reply)
{
dest.sin_port = htons(daemon->dhcp_client_port);
dest.sin_addr = mess->ciaddr;
@@ -450,6 +485,7 @@ 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;
(void)label;
@@ -495,6 +531,15 @@ static int complete_context(struct in_addr local, int if_index, char *label,
}
}
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ if (if_index == param->ind && relay->local.addr.addr4.s_addr == local.s_addr && relay->current == relay &&
+ (param->relay_local.s_addr == 0 || param->relay_local.s_addr == local.s_addr))
+ {
+ relay->current = param->relay;
+ param->relay = relay;
+ param->relay_local = local;
+ }
+
return 1;
}
@@ -988,5 +1033,74 @@ char *host_from_dns(struct in_addr addr)
return NULL;
}
-#endif
+static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index)
+{
+ /* ->local is same value for all relays on ->current chain */
+ struct all_addr from;
+
+ if (mess->op != BOOTREQUEST)
+ return 0;
+
+ /* source address == relay address */
+ from.addr.addr4 = relay->local.addr.addr4;
+
+ /* already gatewayed ? */
+ if (mess->giaddr.s_addr)
+ {
+ /* if so check if by us, to stomp on loops. */
+ if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr)
+ return 1;
+ }
+ else
+ {
+ /* plug in our address */
+ mess->giaddr.s_addr = relay->local.addr.addr4.s_addr;
+ }
+
+ if ((mess->hops++) > 20)
+ return 1;
+ for (; relay; relay = relay->current)
+ {
+ union mysockaddr to;
+
+ to.sa.sa_family = AF_INET;
+ to.in.sin_addr = relay->server.addr.addr4;
+ to.in.sin_port = htons(daemon->dhcp_server_port);
+
+ send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0);
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, inet_ntoa(relay->server.addr.addr4));
+ }
+
+ /* Save this for replies */
+ relay->iface_index = iface_index;
+ }
+
+ return 1;
+}
+
+
+static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface)
+{
+ struct dhcp_relay *relay;
+
+ if (mess->giaddr.s_addr == 0 || mess->op != BOOTREPLY)
+ return NULL;
+
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ {
+ if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr)
+ {
+ if (!relay->interface || wildcard_match(relay->interface, arrival_interface))
+ return relay->iface_index != 0 ? relay : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+#endif
diff --git a/src/dhcp6.c b/src/dhcp6.c
index 3bb855f..35bb748 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -20,7 +20,8 @@
struct iface_param {
struct dhcp_context *current;
- struct in6_addr fallback;
+ struct dhcp_relay *relay;
+ struct in6_addr fallback, relay_local;
int ind, addr_match;
};
@@ -87,6 +88,7 @@ void dhcp6_init(void)
void dhcp6_packet(time_t now)
{
struct dhcp_context *context;
+ struct dhcp_relay *relay;
struct iface_param parm;
struct cmsghdr *cmptr;
struct msghdr msg;
@@ -126,56 +128,75 @@ void dhcp6_packet(time_t now)
if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
return;
-
- for (tmp = daemon->if_except; tmp; tmp = tmp->next)
- if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
- return;
-
- for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
- if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
- return;
-
- parm.current = NULL;
- parm.ind = if_index;
- parm.addr_match = 0;
- memset(&parm.fallback, 0, IN6ADDRSZ);
- for (context = daemon->dhcp6; context; context = context->next)
- if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0)
- {
- /* wildcard context for DHCP-stateless only */
- parm.current = context;
- context->current = NULL;
- }
- else
- {
- /* unlinked contexts are marked by context->current == context */
- context->current = context;
- memset(&context->local6, 0, IN6ADDRSZ);
- }
-
- if (!iface_enumerate(AF_INET6, &parm, complete_context6))
- return;
-
- if (daemon->if_names || daemon->if_addrs)
+ if ((port = relay_reply6(&from, sz, ifr.ifr_name)) == 0)
{
- for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+ for (tmp = daemon->if_except; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
- break;
+ return;
+
+ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ return;
+
+ parm.current = NULL;
+ parm.relay = NULL;
+ memset(&parm.relay_local, 0, IN6ADDRSZ);
+ parm.ind = if_index;
+ parm.addr_match = 0;
+ memset(&parm.fallback, 0, IN6ADDRSZ);
+
+ for (context = daemon->dhcp6; context; context = context->next)
+ if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0)
+ {
+ /* wildcard context for DHCP-stateless only */
+ parm.current = context;
+ context->current = NULL;
+ }
+ else
+ {
+ /* unlinked contexts are marked by context->current == context */
+ context->current = context;
+ memset(&context->local6, 0, IN6ADDRSZ);
+ }
- if (!tmp && !parm.addr_match)
+ for (relay = daemon->relay6; relay; relay = relay->next)
+ relay->current = relay;
+
+ if (!iface_enumerate(AF_INET6, &parm, complete_context6))
return;
+
+ if (daemon->if_names || daemon->if_addrs)
+ {
+
+ for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+ if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+ break;
+
+ if (!tmp && !parm.addr_match)
+ return;
+ }
+
+ if (parm.relay)
+ {
+ relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id);
+ return;
+ }
+
+ /* May have configured relay, but not DHCP server */
+ if (!daemon->doing_dhcp6)
+ return;
+
+ lease_prune(NULL, now); /* lose any expired leases */
+
+ port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
+ sz, IN6_IS_ADDR_MULTICAST(&from.sin6_addr), now);
+
+ lease_update_file(now);
+ lease_update_dns(0);
}
-
- lease_prune(NULL, now); /* lose any expired leases */
-
- port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback,
- sz, IN6_IS_ADDR_MULTICAST(&from.sin6_addr), now);
-
- lease_update_file(now);
- lease_update_dns(0);
-
+
/* The port in the source address of the original request should
be correct, but at least once client sends from the server port,
so we explicitly send to the client port to a client, and the
@@ -194,70 +215,84 @@ static int complete_context6(struct in6_addr *local, int prefix,
unsigned int valid, void *vparam)
{
struct dhcp_context *context;
+ struct dhcp_relay *relay;
struct iface_param *param = vparam;
struct iname *tmp;
(void)scope; /* warning */
- if (if_index == param->ind &&
- !IN6_IS_ADDR_LOOPBACK(local) &&
- !IN6_IS_ADDR_LINKLOCAL(local) &&
- !IN6_IS_ADDR_MULTICAST(local))
+ if (if_index == param->ind)
{
- /* 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 (!IN6_IS_ADDR_LOOPBACK(local) &&
+ !IN6_IS_ADDR_LINKLOCAL(local) &&
+ !IN6_IS_ADDR_MULTICAST(local))
{
- if ((context->flags & CONTEXT_DHCP) &&
- !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
- prefix == context->prefix &&
- is_same_net6(local, &context->start6, prefix) &&
- is_same_net6(local, &context->end6, prefix))
+ /* 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)
{
-
-
- /* link it onto the current chain if we've not seen it before */
- if (context->current == context)
+ if ((context->flags & CONTEXT_DHCP) &&
+ !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+ prefix == context->prefix &&
+ is_same_net6(local, &context->start6, prefix) &&
+ is_same_net6(local, &context->end6, prefix))
{
- struct dhcp_context *tmp, **up;
-
- /* use interface values only for contructed 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;
+ /* 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 contructed 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.addr.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;
+
+ return 1;
}
struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 71d4412..4663591 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -52,6 +52,7 @@ int main (int argc, char **argv)
cap_user_data_t data = NULL;
#endif
struct dhcp_context *context;
+ struct dhcp_relay *relay;
#ifdef LOCALEDIR
setlocale(LC_ALL, "");
@@ -166,50 +167,47 @@ int main (int argc, char **argv)
daemon->soa_sn = now;
#endif
-#ifdef HAVE_DHCP
- if (daemon->dhcp || daemon->dhcp6)
- {
+#ifdef HAVE_DHCP6
+ if (daemon->dhcp6)
+ {
+ daemon->doing_ra = option_bool(OPT_RA);
-# ifdef HAVE_DHCP6
- if (daemon->dhcp6)
+ for (context = daemon->dhcp6; context; context = context->next)
{
- daemon->doing_ra = option_bool(OPT_RA);
-
- for (context = daemon->dhcp6; context; context = context->next)
- {
- if (context->flags & CONTEXT_DHCP)
- daemon->doing_dhcp6 = 1;
- if (context->flags & CONTEXT_RA)
- daemon->doing_ra = 1;
+ if (context->flags & CONTEXT_DHCP)
+ daemon->doing_dhcp6 = 1;
+ if (context->flags & CONTEXT_RA)
+ daemon->doing_ra = 1;
#ifndef HAVE_LINUX_NETWORK
- if (context->flags & CONTEXT_TEMPLATE)
- die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF);
+ if (context->flags & CONTEXT_TEMPLATE)
+ die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF);
#endif
- }
}
-# endif
-
- /* Note that order matters here, we must call lease_init before
- creating any file descriptors which shouldn't be leaked
- to the lease-script init process. We need to call common_init
- before lease_init to allocate buffers it uses.*/
+ }
+#endif
+
+#ifdef HAVE_DHCP
+ /* Note that order matters here, we must call lease_init before
+ creating any file descriptors which shouldn't be leaked
+ to the lease-script init process. We need to call common_init
+ before lease_init to allocate buffers it uses.*/
+ if (daemon->dhcp || daemon->doing_dhcp6 || daemon->relay4 || daemon->relay6)
+ {
+ dhcp_common_init();
if (daemon->dhcp || daemon->doing_dhcp6)
- {
- dhcp_common_init();
- lease_init(now);
- }
-
- if (daemon->dhcp)
- dhcp_init();
-
+ lease_init(now);
+ }
+
+ if (daemon->dhcp || daemon->relay4)
+ dhcp_init();
+
# ifdef HAVE_DHCP6
- if (daemon->doing_ra)
- ra_init(now);
-
- if (daemon->doing_dhcp6)
- dhcp6_init();
+ if (daemon->doing_ra)
+ ra_init(now);
+
+ if (daemon->doing_dhcp6 || daemon->relay6)
+ dhcp6_init();
# endif
- }
#endif
@@ -239,7 +237,7 @@ int main (int argc, char **argv)
#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
/* after enumerate_interfaces() */
- if (daemon->dhcp)
+ if (daemon->dhcp || daemon->relay4)
{
bindtodevice(daemon->dhcpfd);
if (daemon->enable_pxe)
@@ -248,7 +246,7 @@ int main (int argc, char **argv)
#endif
#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6)
- if (daemon->doing_dhcp6)
+ if (daemon->doing_dhcp6 || daemon->relay6)
bindtodevice(daemon->dhcp6fd);
#endif
}
@@ -257,7 +255,7 @@ int main (int argc, char **argv)
#ifdef HAVE_DHCP6
/* after enumerate_interfaces() */
- if (daemon->doing_dhcp6 || daemon->doing_ra)
+ if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
join_multicast(1);
#endif
@@ -641,10 +639,16 @@ int main (int argc, char **argv)
for (context = daemon->dhcp; context; context = context->next)
log_context(AF_INET, context);
+ for (relay = daemon->relay4; relay; relay = relay->next)
+ log_relay(AF_INET, relay);
+
# ifdef HAVE_DHCP6
for (context = daemon->dhcp6; context; context = context->next)
log_context(AF_INET6, context);
+ for (relay = daemon->relay6; relay; relay = relay->next)
+ log_relay(AF_INET6, relay);
+
if (daemon->doing_dhcp6 || daemon->doing_ra)
dhcp_construct_contexts(now);
@@ -749,7 +753,7 @@ int main (int argc, char **argv)
#endif
#ifdef HAVE_DHCP
- if (daemon->dhcp)
+ if (daemon->dhcp || daemon->relay4)
{
FD_SET(daemon->dhcpfd, &rset);
bump_maxfd(daemon->dhcpfd, &maxfd);
@@ -762,7 +766,7 @@ int main (int argc, char **argv)
#endif
#ifdef HAVE_DHCP6
- if (daemon->doing_dhcp6)
+ if (daemon->doing_dhcp6 || daemon->relay6)
{
FD_SET(daemon->dhcp6fd, &rset);
bump_maxfd(daemon->dhcp6fd, &maxfd);
@@ -874,7 +878,7 @@ int main (int argc, char **argv)
#endif
#ifdef HAVE_DHCP
- if (daemon->dhcp)
+ if (daemon->dhcp || daemon->relay4)
{
if (FD_ISSET(daemon->dhcpfd, &rset))
dhcp_packet(now, 0);
@@ -883,7 +887,7 @@ int main (int argc, char **argv)
}
#ifdef HAVE_DHCP6
- if (daemon->doing_dhcp6 && FD_ISSET(daemon->dhcp6fd, &rset))
+ if ((daemon->doing_dhcp6 || daemon->relay6) && FD_ISSET(daemon->dhcp6fd, &rset))
dhcp6_packet(now);
if (daemon->doing_ra && FD_ISSET(daemon->icmp6fd, &rset))
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index f37034d..b1a1644 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -775,6 +775,12 @@ struct tftp_prefix {
struct tftp_prefix *next;
};
+struct dhcp_relay {
+ struct all_addr local, server;
+ char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */
+ int iface_index; /* working - interface in which requests arrived, for return */
+ struct dhcp_relay *current, *next;
+};
extern struct daemon {
/* datastuctures representing the command-line and
@@ -824,6 +830,7 @@ extern struct daemon {
struct pxe_service *pxe_services;
struct tag_if *tag_if;
struct addr_list *override_relays;
+ struct dhcp_relay *relay4, *relay6;
int override;
int enable_pxe;
int doing_ra, doing_dhcp6;
@@ -1217,6 +1224,9 @@ void dhcp_construct_contexts(time_t now);
#ifdef HAVE_DHCP6
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
struct in6_addr *fallback, size_t sz, int is_multicast, time_t now);
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id);
+
+unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
#endif
/* dhcp-common.c */
@@ -1243,6 +1253,7 @@ void bindtodevice(int fd);
void display_opts6(void);
# endif
void log_context(int family, struct dhcp_context *context);
+void log_relay(int family, struct dhcp_relay *relay);
#endif
/* outpacket.c */
diff --git a/src/netlink.c b/src/netlink.c
index 43cd21e..2bcaf01 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -402,18 +402,18 @@ static int nl_async(struct nlmsghdr *h)
static void nl_newaddress(time_t now)
{
- if (option_bool(OPT_CLEVERBIND) || daemon->doing_dhcp6 || daemon->doing_ra)
+ if (option_bool(OPT_CLEVERBIND) || daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
enumerate_interfaces(0);
if (option_bool(OPT_CLEVERBIND))
create_bound_listeners(0);
#ifdef HAVE_DHCP6
+ if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
+ join_multicast(0);
+
if (daemon->doing_dhcp6 || daemon->doing_ra)
- {
- join_multicast(0);
- dhcp_construct_contexts(now);
- }
+ dhcp_construct_contexts(now);
if (daemon->doing_dhcp6)
lease_find_interfaces(now);
diff --git a/src/network.c b/src/network.c
index 7a5d49e..46e49ce 100644
--- a/src/network.c
+++ b/src/network.c
@@ -851,7 +851,7 @@ void join_multicast(int dienow)
inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr);
- if (daemon->doing_dhcp6 &&
+ if ((daemon->doing_dhcp6 || daemon->relay6) &&
setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
err = 1;
diff --git a/src/option.c b/src/option.c
index b03388a..b12ca39 100644
--- a/src/option.c
+++ b/src/option.c
@@ -133,6 +133,7 @@ struct myoption {
#define LOPT_PREF_CLSS 321
#endif
#define LOPT_FAST_RA 322
+#define LOPT_RELAY 323
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -271,6 +272,7 @@ static const struct myoption opts[] =
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif
{ "force-fast-ra", 0, 0, LOPT_FAST_RA },
+ { "dhcp-relay", 1, 0, LOPT_RELAY },
{ NULL, 0, 0, 0 }
};
@@ -389,6 +391,7 @@ static struct {
{ LOPT_DHCP_FQDN, OPT_DHCP_FQDN, NULL, gettext_noop("Use only fully qualified domain names for DHCP clients."), NULL },
{ LOPT_GEN_NAMES, ARG_DUP, "[=tag:<tag>]", gettext_noop("Generate hostnames based on MAC address for nameless clients."), NULL},
{ LOPT_PROXY, ARG_DUP, "[=<ipaddr>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL },
+ { LOPT_RELAY, ARG_DUP, "<local-addr>,<server>[,<interface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL},
{ LOPT_CNAME, ARG_DUP, "<alias>,<target>", gettext_noop("Specify alias name for LOCAL DNS name."), NULL },
{ LOPT_PXE_PROMT, ARG_DUP, "<prompt>,[<timeout>]", gettext_noop("Prompt to send to PXE clients."), NULL },
{ LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
@@ -3178,6 +3181,31 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
arg = comma;
}
break;
+
+ case LOPT_RELAY: /* --dhcp-relay */
+ {
+ struct dhcp_relay *new = opt_malloc(sizeof(struct dhcp_relay));
+ comma = split(arg);
+ new->interface = opt_string_alloc(split(comma));
+ new->iface_index = 0;
+ if (inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server))
+ {
+ new->next = daemon->relay4;
+ daemon->relay4 = new;
+ }
+#ifdef HAVE_DHCP6
+ else if (inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server))
+ {
+ new->next = daemon->relay6;
+ daemon->relay6 = new;
+ }
+#endif
+ else
+ ret_err(_("Bad dhcp-relay"));
+
+ break;
+ }
+
#endif
#ifdef HAVE_DHCP6
diff --git a/src/outpacket.c b/src/outpacket.c
index 7f11cd3..9d64c01 100644
--- a/src/outpacket.c
+++ b/src/outpacket.c
@@ -70,9 +70,9 @@ void *put_opt6(void *data, size_t len)
{
void *p;
- if ((p = expand(len)))
+ if ((p = expand(len)) && data)
memcpy(p, data, len);
-
+
return p;
}
diff --git a/src/rfc3315.c b/src/rfc3315.c
index e1292fb..f0e9982 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -155,7 +155,8 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **
return 0;
/* copy header stuff into reply message and set type to reply */
- outmsgtypep = put_opt6(inbuff, 34);
+ if (!(outmsgtypep = put_opt6(inbuff, 34)))
+ return 0;
*outmsgtypep = DHCP6RELAYREPL;
/* look for relay options and set tags if found. */
@@ -252,7 +253,8 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
state.tags = &v6_id;
/* copy over transaction-id, and save pointer to message type */
- outmsgtypep = put_opt6(inbuff, 4);
+ if (!(outmsgtypep = put_opt6(inbuff, 4)))
+ return 0;
start_opts = save_counter(-1);
state.xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16;
@@ -1912,4 +1914,118 @@ static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
return ret;
}
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id)
+{
+ /* ->local is same value for all relays on ->current chain */
+
+ struct all_addr from;
+ unsigned char *header;
+ unsigned char *inbuff = daemon->dhcp_packet.iov_base;
+ int msg_type = *inbuff;
+ int hopcount;
+ struct in6_addr multicast;
+
+ inet_pton(AF_INET6, ALL_SERVERS, &multicast);
+
+ /* source address == relay address */
+ from.addr.addr6 = relay->local.addr.addr6;
+
+ /* Get hop count from nested relayed message */
+ if (msg_type == DHCP6RELAYFORW)
+ hopcount = *((unsigned char *)inbuff+1) + 1;
+ else
+ hopcount = 0;
+
+ /* RFC 3315 HOP_COUNT_LIMIT */
+ if (hopcount > 32)
+ return;
+
+ save_counter(0);
+
+ if ((header = put_opt6(NULL, 34)))
+ {
+ int o;
+
+ header[0] = DHCP6RELAYFORW;
+ header[1] = hopcount;
+ memcpy(&header[2], &relay->local.addr.addr6, IN6ADDRSZ);
+ memcpy(&header[18], peer_address, IN6ADDRSZ);
+
+ o = new_opt6(OPTION6_RELAY_MSG);
+ put_opt6(inbuff, sz);
+ end_opt6(o);
+
+ for (; relay; relay = relay->current)
+ {
+ union mysockaddr to;
+
+ to.sa.sa_family = AF_INET6;
+ to.in6.sin6_addr = relay->server.addr.addr6;
+ to.in6.sin6_port = htons(DHCPV6_SERVER_PORT);
+ to.in6.sin6_flowinfo = 0;
+ to.in6.sin6_scope_id = 0;
+
+ if (IN6_ARE_ADDR_EQUAL(&relay->server.addr.addr6, &multicast))
+ {
+ int multicast_iface;
+ if (!relay->interface || strchr(relay->interface, '*') ||
+ (multicast_iface = if_nametoindex(relay->interface)) == 0 ||
+ setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &multicast_iface, sizeof(multicast_iface)) == -1)
+ my_syslog(MS_DHCP | LOG_ERR, _("Cannot multicast to DHCPv6 server without correct interface"));
+ }
+
+ send_from(daemon->dhcp6fd, 0, daemon->outpacket.iov_base, save_counter(0), &to, &from, 0);
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ inet_ntop(AF_INET6, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+ inet_ntop(AF_INET6, &relay->server, daemon->namebuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, daemon->namebuff);
+ }
+
+ /* Save this for replies */
+ relay->iface_index = scope_id;
+ }
+ }
+}
+
+unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface)
+{
+ struct dhcp_relay *relay;
+ struct in6_addr link;
+ unsigned char *inbuff = daemon->dhcp_packet.iov_base;
+
+ /* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
+ which is 1 + 1 + 16 + 16 + 2 + 2 = 38 */
+
+ if (sz < 38 || *inbuff != DHCP6RELAYREPL)
+ return 0;
+
+ memcpy(&link, &inbuff[2], IN6ADDRSZ);
+
+ for (relay = daemon->relay6; relay; relay = relay->next)
+ if (IN6_ARE_ADDR_EQUAL(&link, &relay->local.addr.addr6) &&
+ (!relay->interface || wildcard_match(relay->interface, arrival_interface)))
+ break;
+
+ save_counter(0);
+
+ if (relay)
+ {
+ void *opt, *opts = inbuff + 34;
+ void *end = inbuff + sz;
+ for (opt = opts; opt; opt = opt6_next(opt, end))
+ if (opt6_type(opt) == OPTION6_RELAY_MSG && opt6_len(opt) > 0)
+ {
+ int encap_type = *((unsigned char *)opt6_ptr(opt, 0));
+ put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
+ memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ);
+ peer->sin6_scope_id = relay->iface_index;
+ return encap_type == DHCP6RELAYREPL ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
+ }
+ }
+
+ return 0;
+}
+
#endif