Commit ff7eea27 authored by Simon Kelley's avatar Simon Kelley

Add --dhcp-relay config option.

parent 6692a1a5
...@@ -101,6 +101,8 @@ version 2.67 ...@@ -101,6 +101,8 @@ version 2.67
(provide TFTP to the same interfaces we provide DHCP to) (provide TFTP to the same interfaces we provide DHCP to)
is retained. Thanks to Lonnie Abelbeck for the suggestion. is retained. Thanks to Lonnie Abelbeck for the suggestion.
Add --dhcp-relay config option.
version 2.66 version 2.66
Add the ability to act as an authoritative DNS Add the ability to act as an authoritative DNS
......
...@@ -973,6 +973,38 @@ DHCP options. This make extra space available in the DHCP packet for ...@@ -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 options but can, rarely, confuse old or broken clients. This flag
forces "simple and safe" behaviour to avoid problems in such a case. forces "simple and safe" behaviour to avoid problems in such a case.
.TP .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> .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 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 "vendor class" which represents, in some sense, the type of host. This option
......
...@@ -757,6 +757,16 @@ void log_context(int family, struct dhcp_context *context) ...@@ -757,6 +757,16 @@ void log_context(int family, struct dhcp_context *context)
#endif #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 #endif
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
struct iface_param { struct iface_param {
struct dhcp_context *current; struct dhcp_context *current;
struct dhcp_relay *relay;
struct in_addr relay_local;
int ind; int ind;
}; };
...@@ -32,6 +34,8 @@ static int complete_context(struct in_addr local, int if_index, char *label, ...@@ -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); struct in_addr netmask, struct in_addr broadcast, void *vparam);
static int check_listen_addrs(struct in_addr local, int if_index, char *label, static int check_listen_addrs(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam); 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) static int make_fd(int port)
{ {
...@@ -132,6 +136,8 @@ void dhcp_packet(time_t now, int pxe_fd) ...@@ -132,6 +136,8 @@ void dhcp_packet(time_t now, int pxe_fd)
int fd = pxe_fd ? daemon->pxefd : daemon->dhcpfd; int fd = pxe_fd ? daemon->pxefd : daemon->dhcpfd;
struct dhcp_packet *mess; struct dhcp_packet *mess;
struct dhcp_context *context; struct dhcp_context *context;
struct dhcp_relay *relay;
int is_relay_reply = 0;
struct iname *tmp; struct iname *tmp;
struct ifreq ifr; struct ifreq ifr;
struct msghdr msg; struct msghdr msg;
...@@ -250,57 +256,86 @@ void dhcp_packet(time_t now, int pxe_fd) ...@@ -250,57 +256,86 @@ void dhcp_packet(time_t now, int pxe_fd)
unicast_dest = 1; unicast_dest = 1;
#endif #endif
ifr.ifr_addr.sa_family = AF_INET; if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name)))
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); /* Reply from server, using us as relay. */
return; 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
} }
else
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))
{ {
/* If we failed to match the primary address of the interface, see if we've got a --listen-address ifr.ifr_addr.sa_family = AF_INET;
for a secondary */ if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 )
struct match_param match; 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; parm.current = NULL;
match.ind = iface_index; parm.relay = NULL;
parm.relay_local.s_addr = 0;
parm.ind = iface_index;
if (!daemon->if_addrs || if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL))
!iface_enumerate(AF_INET, &match, check_listen_addrs) || {
!match.matched) /* 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; return;
iface_addr = match.addr; lease_prune(NULL, now); /* lose any expired leases */
/* make sure secondary address gets priority in case iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz,
there is more than one address on the interface in the same subnet */ now, unicast_dest, &is_inform, pxe_fd, iface_addr);
complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm); lease_update_file(now);
} lease_update_dns(0);
if (!iface_enumerate(AF_INET, &parm, complete_context)) if (iov.iov_len == 0)
return; 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;
msg.msg_name = &dest; msg.msg_name = &dest;
msg.msg_namelen = sizeof(dest); msg.msg_namelen = sizeof(dest);
...@@ -321,7 +356,7 @@ void dhcp_packet(time_t now, int pxe_fd) ...@@ -321,7 +356,7 @@ void dhcp_packet(time_t now, int pxe_fd)
if (mess->ciaddr.s_addr != 0) if (mess->ciaddr.s_addr != 0)
dest.sin_addr = mess->ciaddr; dest.sin_addr = mess->ciaddr;
} }
else if (mess->giaddr.s_addr) else if (mess->giaddr.s_addr && !is_relay_reply)
{ {
/* Send to BOOTP relay */ /* Send to BOOTP relay */
dest.sin_port = htons(daemon->dhcp_server_port); dest.sin_port = htons(daemon->dhcp_server_port);
...@@ -334,7 +369,7 @@ void dhcp_packet(time_t now, int pxe_fd) ...@@ -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 source port too, and send back to that. If we're replying
to a DHCPINFORM, trust the source address always. */ to a DHCPINFORM, trust the source address always. */
if ((!is_inform && dest.sin_addr.s_addr != mess->ciaddr.s_addr) || 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_port = htons(daemon->dhcp_client_port);
dest.sin_addr = mess->ciaddr; dest.sin_addr = mess->ciaddr;
...@@ -450,6 +485,7 @@ static int complete_context(struct in_addr local, int if_index, char *label, ...@@ -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 in_addr netmask, struct in_addr broadcast, void *vparam)
{ {
struct dhcp_context *context; struct dhcp_context *context;
struct dhcp_relay *relay;
struct iface_param *param = vparam; struct iface_param *param = vparam;
(void)label; (void)label;
...@@ -495,6 +531,15 @@ static int complete_context(struct in_addr local, int if_index, char *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; return 1;
} }
...@@ -988,5 +1033,74 @@ char *host_from_dns(struct in_addr addr) ...@@ -988,5 +1033,74 @@ char *host_from_dns(struct in_addr addr)
return NULL; 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
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
struct iface_param { struct iface_param {
struct dhcp_context *current; struct dhcp_context *current;
struct in6_addr fallback; struct dhcp_relay *relay;
struct in6_addr fallback, relay_local;
int ind, addr_match; int ind, addr_match;
}; };
...@@ -87,6 +88,7 @@ void dhcp6_init(void) ...@@ -87,6 +88,7 @@ void dhcp6_init(void)
void dhcp6_packet(time_t now) void dhcp6_packet(time_t now)
{ {
struct dhcp_context *context; struct dhcp_context *context;
struct dhcp_relay *relay;
struct iface_param parm; struct iface_param parm;
struct cmsghdr *cmptr; struct cmsghdr *cmptr;
struct msghdr msg; struct msghdr msg;
...@@ -126,56 +128,75 @@ void dhcp6_packet(time_t now) ...@@ -126,56 +128,75 @@ void dhcp6_packet(time_t now)
if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
return; 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 ((port = relay_reply6(&from, sz, ifr.ifr_name)) == 0)
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)
{ {
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)) 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; 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 /* The port in the source address of the original request should
be correct, but at least once client sends from the server port, 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 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, ...@@ -194,70 +215,84 @@ static int complete_context6(struct in6_addr *local, int prefix,
unsigned int valid, void *vparam) unsigned int valid, void *vparam)
{ {
struct dhcp_context *context; struct dhcp_context *context;
struct dhcp_relay *relay;
struct iface_param *param = vparam; struct iface_param *param = vparam;
struct iname *tmp; struct iname *tmp;
(void)scope; /* warning */ (void)scope; /* warning */
if (if_index == param->ind && if (if_index == param->ind)
!IN6_IS_ADDR_LOOPBACK(local) &&
!IN6_IS_ADDR_LINKLOCAL(local) &&
!IN6_IS_ADDR_MULTICAST(local))
{ {
/* if we have --listen-address config, see if the if (!IN6_IS_ADDR_LOOPBACK(local) &&
arrival interface has a matching address. */ !IN6_IS_ADDR_LINKLOCAL(local) &&
for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) !IN6_IS_ADDR_MULTICAST(local))
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) && /* if we have --listen-address config, see if the
!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && arrival interface has a matching address. */
prefix == context->prefix && for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
is_same_net6(local, &context->start6, prefix) && if (tmp->addr.sa.sa_family == AF_INET6 &&
is_same_net6(local, &context->end6, prefix)) 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)) &&
/* link it onto the current chain if we've not seen it before */ prefix == context->prefix &&
if (context->current == context) 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; /* link it onto the current chain if we've not seen it before */
*up = context; if (context->current == context)
context->local6 = *local; {
context->preferred = preferred; struct dhcp_context *tmp, **up;
context->valid = valid;
/* 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) struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)
......
...@@ -52,6 +52,7 @@ int main (int argc, char **argv) ...@@ -52,6 +52,7 @@ int main (int argc, char **argv)
cap_user_data_t data = NULL; cap_user_data_t data = NULL;
#endif #endif
struct dhcp_context *context; struct dhcp_context *context;
struct dhcp_relay *relay;
#ifdef LOCALEDIR #ifdef LOCALEDIR
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
...@@ -166,50 +167,47 @@ int main (int argc, char **argv) ...@@ -166,50 +167,47 @@ int main (int argc, char **argv)
daemon->soa_sn = now; daemon->soa_sn = now;
#endif #endif
#ifdef HAVE_DHCP #ifdef HAVE_DHCP6
if (daemon->dhcp || daemon->dhcp6) if (daemon->dhcp6)
{ {
daemon->doing_ra = option_bool(OPT_RA);
# ifdef HAVE_DHCP6 for (context = daemon->dhcp6; context; context = context->next)
if (daemon->dhcp6)
{ {
daemon->doing_ra = option_bool(OPT_RA); if (context->flags & CONTEXT_DHCP)
daemon->doing_dhcp6 = 1;
for (context = daemon->dhcp6; context; context = context->next) 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 #ifndef HAVE_LINUX_NETWORK
if (context->flags & CONTEXT_TEMPLATE) if (context->flags & CONTEXT_TEMPLATE)
die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF); die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF);
#endif #endif
}
} }
# endif }
#endif
/* Note that order matters here, we must call lease_init before
creating any file descriptors which shouldn't be leaked #ifdef HAVE_DHCP
to the lease-script init process. We need to call common_init /* Note that order matters here, we must call lease_init before
before lease_init to allocate buffers it uses.*/ 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) if (daemon->dhcp || daemon->doing_dhcp6)
{ lease_init(now);
dhcp_common_init(); }
lease_init(now);
} if (daemon->dhcp || daemon->relay4)
dhcp_init();
if (daemon->dhcp)
dhcp_init();
# ifdef HAVE_DHCP6 # ifdef HAVE_DHCP6
if (daemon->doing_ra) if (daemon->doing_ra)
ra_init(now); ra_init(now);
if (daemon->doing_dhcp6) if (daemon->doing_dhcp6 || daemon->relay6)
dhcp6_init(); dhcp6_init();
# endif # endif
}
#endif #endif
...@@ -239,7 +237,7 @@ int main (int argc, char **argv) ...@@ -239,7 +237,7 @@ int main (int argc, char **argv)
#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP) #if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
/* after enumerate_interfaces() */ /* after enumerate_interfaces() */
if (daemon->dhcp) if (daemon->dhcp || daemon->relay4)
{ {
bindtodevice(daemon->dhcpfd); bindtodevice(daemon->dhcpfd);
if (daemon->enable_pxe) if (daemon->enable_pxe)
...@@ -248,7 +246,7 @@ int main (int argc, char **argv) ...@@ -248,7 +246,7 @@ int main (int argc, char **argv)
#endif #endif
#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6) #if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6)
if (daemon->doing_dhcp6) if (daemon->doing_dhcp6 || daemon->relay6)
bindtodevice(daemon->dhcp6fd); bindtodevice(daemon->dhcp6fd);
#endif #endif
} }
...@@ -257,7 +255,7 @@ int main (int argc, char **argv) ...@@ -257,7 +255,7 @@ int main (int argc, char **argv)
#ifdef HAVE_DHCP6 #ifdef HAVE_DHCP6
/* after enumerate_interfaces() */ /* after enumerate_interfaces() */
if (daemon->doing_dhcp6 || daemon->doing_ra) if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
join_multicast(1); join_multicast(1);
#endif #endif
...@@ -641,10 +639,16 @@ int main (int argc, char **argv) ...@@ -641,10 +639,16 @@ int main (int argc, char **argv)
for (context = daemon->dhcp; context; context = context->next) for (context = daemon->dhcp; context; context = context->next)
log_context(AF_INET, context); log_context(AF_INET, context);
for (relay = daemon->relay4; relay; relay = relay->next)
log_relay(AF_INET, relay);
# ifdef HAVE_DHCP6 # ifdef HAVE_DHCP6
for (context = daemon->dhcp6; context; context = context->next) for (context = daemon->dhcp6; context; context = context->next)
log_context(AF_INET6, context); 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) if (daemon->doing_dhcp6 || daemon->doing_ra)
dhcp_construct_contexts(now); dhcp_construct_contexts(now);
...@@ -749,7 +753,7 @@ int main (int argc, char **argv) ...@@ -749,7 +753,7 @@ int main (int argc, char **argv)
#endif #endif
#ifdef HAVE_DHCP #ifdef HAVE_DHCP
if (daemon->dhcp) if (daemon->dhcp || daemon->relay4)
{ {
FD_SET(daemon->dhcpfd, &rset); FD_SET(daemon->dhcpfd, &rset);
bump_maxfd(daemon->dhcpfd, &maxfd); bump_maxfd(daemon->dhcpfd, &maxfd);
...@@ -762,7 +766,7 @@ int main (int argc, char **argv) ...@@ -762,7 +766,7 @@ int main (int argc, char **argv)
#endif #endif
#ifdef HAVE_DHCP6 #ifdef HAVE_DHCP6
if (daemon->doing_dhcp6) if (daemon->doing_dhcp6 || daemon->relay6)
{ {
FD_SET(daemon->dhcp6fd, &rset); FD_SET(daemon->dhcp6fd, &rset);
bump_maxfd(daemon->dhcp6fd, &maxfd); bump_maxfd(daemon->dhcp6fd, &maxfd);
...@@ -874,7 +878,7 @@ int main (int argc, char **argv) ...@@ -874,7 +878,7 @@ int main (int argc, char **argv)
#endif #endif
#ifdef HAVE_DHCP #ifdef HAVE_DHCP
if (daemon->dhcp) if (daemon->dhcp || daemon->relay4)
{ {
if (FD_ISSET(daemon->dhcpfd, &rset)) if (FD_ISSET(daemon->dhcpfd, &rset))
dhcp_packet(now, 0); dhcp_packet(now, 0);
...@@ -883,7 +887,7 @@ int main (int argc, char **argv) ...@@ -883,7 +887,7 @@ int main (int argc, char **argv)
} }
#ifdef HAVE_DHCP6 #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); dhcp6_packet(now);
if (daemon->doing_ra && FD_ISSET(daemon->icmp6fd, &rset)) if (daemon->doing_ra && FD_ISSET(daemon->icmp6fd, &rset))
......
...@@ -775,6 +775,12 @@ struct tftp_prefix { ...@@ -775,6 +775,12 @@ struct tftp_prefix {
struct tftp_prefix *next; 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 { extern struct daemon {
/* datastuctures representing the command-line and /* datastuctures representing the command-line and
...@@ -824,6 +830,7 @@ extern struct daemon { ...@@ -824,6 +830,7 @@ extern struct daemon {
struct pxe_service *pxe_services; struct pxe_service *pxe_services;
struct tag_if *tag_if; struct tag_if *tag_if;
struct addr_list *override_relays; struct addr_list *override_relays;
struct dhcp_relay *relay4, *relay6;
int override; int override;
int enable_pxe; int enable_pxe;
int doing_ra, doing_dhcp6; int doing_ra, doing_dhcp6;
...@@ -1217,6 +1224,9 @@ void dhcp_construct_contexts(time_t now); ...@@ -1217,6 +1224,9 @@ void dhcp_construct_contexts(time_t now);
#ifdef HAVE_DHCP6 #ifdef HAVE_DHCP6
unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name, 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); 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 #endif
/* dhcp-common.c */ /* dhcp-common.c */
...@@ -1243,6 +1253,7 @@ void bindtodevice(int fd); ...@@ -1243,6 +1253,7 @@ void bindtodevice(int fd);
void display_opts6(void); void display_opts6(void);
# endif # endif
void log_context(int family, struct dhcp_context *context); void log_context(int family, struct dhcp_context *context);
void log_relay(int family, struct dhcp_relay *relay);
#endif #endif
/* outpacket.c */ /* outpacket.c */
......
...@@ -402,18 +402,18 @@ static int nl_async(struct nlmsghdr *h) ...@@ -402,18 +402,18 @@ static int nl_async(struct nlmsghdr *h)
static void nl_newaddress(time_t now) 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); enumerate_interfaces(0);
if (option_bool(OPT_CLEVERBIND)) if (option_bool(OPT_CLEVERBIND))
create_bound_listeners(0); create_bound_listeners(0);
#ifdef HAVE_DHCP6 #ifdef HAVE_DHCP6
if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
join_multicast(0);
if (daemon->doing_dhcp6 || daemon->doing_ra) if (daemon->doing_dhcp6 || daemon->doing_ra)
{ dhcp_construct_contexts(now);
join_multicast(0);
dhcp_construct_contexts(now);
}
if (daemon->doing_dhcp6) if (daemon->doing_dhcp6)
lease_find_interfaces(now); lease_find_interfaces(now);
......
...@@ -851,7 +851,7 @@ void join_multicast(int dienow) ...@@ -851,7 +851,7 @@ void join_multicast(int dienow)
inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr); 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) setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
err = 1; err = 1;
......
...@@ -133,6 +133,7 @@ struct myoption { ...@@ -133,6 +133,7 @@ struct myoption {
#define LOPT_PREF_CLSS 321 #define LOPT_PREF_CLSS 321
#endif #endif
#define LOPT_FAST_RA 322 #define LOPT_FAST_RA 322
#define LOPT_RELAY 323
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
static const struct option opts[] = static const struct option opts[] =
...@@ -271,6 +272,7 @@ static const struct myoption opts[] = ...@@ -271,6 +272,7 @@ static const struct myoption opts[] =
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
#endif #endif
{ "force-fast-ra", 0, 0, LOPT_FAST_RA }, { "force-fast-ra", 0, 0, LOPT_FAST_RA },
{ "dhcp-relay", 1, 0, LOPT_RELAY },
{ NULL, 0, 0, 0 } { NULL, 0, 0, 0 }
}; };
...@@ -389,6 +391,7 @@ static struct { ...@@ -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_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_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_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_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_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 }, { 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 ...@@ -3178,6 +3181,31 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
arg = comma; arg = comma;
} }
break; 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 #endif
#ifdef HAVE_DHCP6 #ifdef HAVE_DHCP6
......
...@@ -70,9 +70,9 @@ void *put_opt6(void *data, size_t len) ...@@ -70,9 +70,9 @@ void *put_opt6(void *data, size_t len)
{ {
void *p; void *p;
if ((p = expand(len))) if ((p = expand(len)) && data)
memcpy(p, data, len); memcpy(p, data, len);
return p; return p;
} }
......
...@@ -155,7 +155,8 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid ** ...@@ -155,7 +155,8 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **
return 0; return 0;
/* copy header stuff into reply message and set type to reply */ /* 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; *outmsgtypep = DHCP6RELAYREPL;
/* look for relay options and set tags if found. */ /* 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 ...@@ -252,7 +253,8 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
state.tags = &v6_id; state.tags = &v6_id;
/* copy over transaction-id, and save pointer to message type */ /* 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); start_opts = save_counter(-1);
state.xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16; 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) ...@@ -1912,4 +1914,118 @@ static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
return ret; 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 #endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment