summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancis Dupont <fdupont@isc.org>2012-07-18 16:34:09 +0200
committerFrancis Dupont <fdupont@isc.org>2012-07-18 16:34:09 +0200
commit6fe35fdf34d9f052d70f8e55b86367a856a213f2 (patch)
tree6db4027d2949ec692d61177efe5453fe2cd0d71f
parent355db4b687b0f66f224f40b0b754a9c500c8e41e (diff)
downloadisc-dhcp-rt28195b.tar.gz
rebase 28195 aka DHCPv4 over IPv6 to git in branch rt28195brt28195b
-rw-r--r--client/dhclient.c6
-rw-r--r--common/discover.c49
-rw-r--r--common/options.c89
-rw-r--r--common/socket.c16
-rw-r--r--dhcpctl/omshell.c1
-rw-r--r--includes/dhcp.h3
-rw-r--r--includes/dhcpd.h27
-rw-r--r--relay/.cvsignore4
-rw-r--r--relay/Makefile.am10
-rw-r--r--relay/Makefile.in32
-rw-r--r--relay/dhccra.8136
-rw-r--r--relay/dhccra.c695
-rw-r--r--relay/dhcrelay.82
-rw-r--r--relay/dhcrelay.c7
-rw-r--r--relay/dhctra.8133
-rw-r--r--relay/dhctra.c1011
-rw-r--r--server/Makefile.am2
-rw-r--r--server/Makefile.dist4
-rw-r--r--server/Makefile.in19
-rw-r--r--server/confpars.c18
-rw-r--r--server/db.c4
-rw-r--r--server/dhcp.c39
-rw-r--r--server/dhcpd.811
-rw-r--r--server/dhcpd.c129
-rw-r--r--server/dhcpd.conf.517
-rw-r--r--server/dhcpleasequery.c541
-rw-r--r--server/mdb.c10
-rw-r--r--server/stables.c4
-rw-r--r--server/tsv.c3226
-rw-r--r--tests/t_api_dhcp.c6
30 files changed, 6184 insertions, 67 deletions
diff --git a/client/dhclient.c b/client/dhclient.c
index fa8a3130..f50dce2a 100644
--- a/client/dhclient.c
+++ b/client/dhclient.c
@@ -1418,6 +1418,12 @@ void dhcp (packet)
#ifdef DHCPv6
void
+dhcp_tsv(struct packet *packet)
+{
+ return;
+}
+
+void
dhcpv6(struct packet *packet) {
struct iaddrmatchlist *ap;
struct client_state *client;
diff --git a/common/discover.c b/common/discover.c
index 1d842192..cb2e8717 100644
--- a/common/discover.c
+++ b/common/discover.c
@@ -3,7 +3,8 @@
Find and identify the network interfaces. */
/*
- * Copyright (c) 2004-2009,2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2011-2012 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2009 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1995-2003 by Internet Software Consortium
*
* Permission to use, copy, modify, and distribute this software for any
@@ -45,6 +46,7 @@
struct interface_info *interfaces, *dummy_interfaces, *fallback_interface;
int interfaces_invalidated;
int quiet_interface_discovery;
+int run_as_tsv;
u_int16_t local_port;
u_int16_t remote_port;
int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *);
@@ -948,21 +950,31 @@ discover_interfaces(int state) {
/* If we already have a list of interfaces, and we're running as
a DHCP server, the interfaces were requested. */
- if (interfaces && (state == DISCOVER_SERVER ||
- state == DISCOVER_RELAY ||
- state == DISCOVER_REQUESTED))
+ if (interfaces && !run_as_tsv &&
+ (state == DISCOVER_SERVER ||
+ state == DISCOVER_RELAY ||
+ state == DISCOVER_REQUESTED))
ir = 0;
else if (state == DISCOVER_UNCONFIGURED)
ir = INTERFACE_REQUESTED | INTERFACE_AUTOMATIC;
else
ir = INTERFACE_REQUESTED;
+ /* Set the address family of all interfaces. */
+ if (interfaces) {
+ for (tmp = interfaces; tmp; tmp = tmp->next) {
+ if (tmp->address_family == 0)
+ tmp->address_family = local_family;
+ }
+ }
+
/* Cycle through the list of interfaces looking for IP addresses. */
while (next_iface(&info, &err, &ifaces)) {
/* See if we've seen an interface that matches this one. */
for (tmp = interfaces; tmp; tmp = tmp->next) {
- if (!strcmp(tmp->name, info.name))
+ if ((tmp->address_family == local_family) &&
+ (!strcmp(tmp->name, info.name)))
break;
}
@@ -991,6 +1003,7 @@ discover_interfaces(int state) {
info.name, isc_result_totext(status));
}
strcpy(tmp->name, info.name);
+ tmp->address_family = local_family;
interface_snorf(tmp, ir);
interface_dereference(&tmp, MDL);
tmp = interfaces; /* XXX */
@@ -1095,10 +1108,12 @@ discover_interfaces(int state) {
interface_dereference (&next, MDL);
if (tmp -> next)
interface_reference (&next, tmp -> next, MDL);
- /* skip interfaces that are running already */
- if (tmp -> flags & INTERFACE_RUNNING) {
+ /* skip interfaces that are running already
+ or belong to the other address family */
+ if ((tmp -> flags & INTERFACE_RUNNING) ||
+ (tmp -> address_family != local_family)) {
interface_dereference(&tmp, MDL);
- if(next)
+ if (next)
interface_reference(&tmp, next, MDL);
continue;
}
@@ -1119,7 +1134,8 @@ discover_interfaces(int state) {
interface_dereference (&interfaces,
MDL);
if (next)
- interface_reference (&interfaces, next, MDL);
+ interface_reference (&interfaces,
+ next, MDL);
} else {
interface_dereference (&last -> next, MDL);
if (next)
@@ -1237,7 +1253,7 @@ discover_interfaces(int state) {
if_register_send(tmp);
#ifdef DHCPv6
} else {
- if ((state == DISCOVER_SERVER) ||
+ if (((state == DISCOVER_SERVER) && !run_as_tsv) ||
(state == DISCOVER_RELAY)) {
if_register6(tmp, 1);
} else {
@@ -1272,10 +1288,12 @@ discover_interfaces(int state) {
*/
for (tmp = interfaces; tmp; tmp = tmp -> next) {
/* not if it's been registered before */
- if (tmp -> flags & INTERFACE_RUNNING)
+ if (tmp -> registered)
continue;
if (tmp -> rfdesc == -1)
continue;
+ if (tmp -> address_family != local_family)
+ continue;
switch (local_family) {
#ifdef DHCPv6
case AF_INET6:
@@ -1295,6 +1313,7 @@ discover_interfaces(int state) {
if (status != ISC_R_SUCCESS)
log_fatal ("Can't register I/O handle for %s: %s",
tmp -> name, isc_result_totext (status));
+ tmp -> registered = 1;
#if defined(DHCPv6)
/* Only register the first interface for V6, since they all
@@ -1427,7 +1446,9 @@ isc_result_t got_one (h)
* source interface by interface index.
*/
ip = interfaces;
- while ((ip != NULL) && (if_nametoindex(ip->name) != ifindex))
+ while ((ip != NULL) &&
+ ((ip->address_family == AF_INET6) ||
+ (if_nametoindex(ip->name) != ifindex)))
ip = ip->next;
if (ip == NULL)
return ISC_R_NOTFOUND;
@@ -1492,7 +1513,9 @@ got_one_v6(omapi_object_t *h) {
/* Seek forward to find the matching source interface. */
ip = interfaces;
- while ((ip != NULL) && (if_nametoindex(ip->name) != if_idx))
+ while ((ip != NULL) &&
+ ((ip->address_family == AF_INET) ||
+ (if_nametoindex(ip->name) != if_idx)))
ip = ip->next;
if (ip == NULL)
diff --git a/common/options.c b/common/options.c
index f3a3db07..5c3ac9f8 100644
--- a/common/options.c
+++ b/common/options.c
@@ -3936,6 +3936,95 @@ do_packet6(struct interface_info *interface, const char *packet,
packet_dereference(&decoded_packet, MDL);
}
+
+void
+do_packet_tsv(struct interface_info *interface, const char *msg,
+ int len, int from_port, const struct iaddr *from,
+ isc_boolean_t was_unicast)
+{
+ struct dhcp_packet *packet;
+ struct option_cache *op;
+ struct packet *decoded_packet;
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ unsigned long previous_outstanding = dmalloc_outstanding;
+#endif
+
+ packet = (struct dhcp_packet *)msg;
+
+#if defined (TRACING)
+ trace_inpacket_stash (interface, packet,
+ (unsigned)len, (unsigned int)from_port,
+ *from, (struct hardware *)0);
+#endif
+
+ decoded_packet = (struct packet *)0;
+ if (!packet_allocate (&decoded_packet, MDL)) {
+ log_error ("do_packet: no memory for incoming packet!");
+ return;
+ }
+ decoded_packet -> raw = packet;
+ decoded_packet -> packet_length = (unsigned)len;
+ decoded_packet -> client_port = from_port;
+ decoded_packet -> client_addr = *from;
+ interface_reference (&decoded_packet -> interface, interface, MDL);
+ decoded_packet -> unicast = was_unicast;
+
+ if (packet -> hlen > sizeof packet -> chaddr) {
+ packet_dereference (&decoded_packet, MDL);
+ log_info ("Discarding packet with bogus hlen.");
+ return;
+ }
+
+ /* If there's an option buffer, try to parse it. */
+ if (decoded_packet -> packet_length >= DHCP_FIXED_NON_UDP + 4) {
+ if (!parse_options (decoded_packet)) {
+ if (decoded_packet -> options)
+ option_state_dereference
+ (&decoded_packet -> options, MDL);
+ packet_dereference (&decoded_packet, MDL);
+ return;
+ }
+
+ if (decoded_packet -> options_valid &&
+ (op = lookup_option (&dhcp_universe,
+ decoded_packet -> options,
+ DHO_DHCP_MESSAGE_TYPE))) {
+ struct data_string dp;
+ memset (&dp, 0, sizeof dp);
+ evaluate_option_cache (&dp, decoded_packet,
+ (struct lease *)0,
+ (struct client_state *)0,
+ decoded_packet -> options,
+ (struct option_state *)0,
+ (struct binding_scope **)0,
+ op, MDL);
+ if (dp.len > 0)
+ decoded_packet -> packet_type = dp.data [0];
+ else
+ decoded_packet -> packet_type = 0;
+ data_string_forget (&dp, MDL);
+ }
+ }
+
+ if (decoded_packet -> packet_type)
+ dhcp_tsv (decoded_packet);
+
+ /* If the caller kept the packet, they'll have upped the refcnt. */
+ packet_dereference (&decoded_packet, MDL);
+
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ log_info ("generation %ld: %ld new, %ld outstanding, %ld long-term",
+ dmalloc_generation,
+ dmalloc_outstanding - previous_outstanding,
+ dmalloc_outstanding, dmalloc_longterm);
+#endif
+#if defined (DEBUG_MEMORY_LEAKAGE)
+ dmalloc_dump_outstanding ();
+#endif
+#if defined (DEBUG_RC_HISTORY_EXHAUSTIVELY)
+ dump_rc_history (0);
+#endif
+}
#endif /* DHCPv6 */
int
diff --git a/common/socket.c b/common/socket.c
index f95665c3..5465a4f0 100644
--- a/common/socket.c
+++ b/common/socket.c
@@ -232,6 +232,19 @@ if_register_socket(struct interface_info *info, int family,
}
}
#endif
+#if defined(DHCPv6) && defined(IPV6_V6ONLY)
+ /*
+ * IPv6 sockets should be IPv6 only. Must be set before bind().
+ */
+ if (local_family == AF_INET6) {
+ flag = 1;
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&flag, sizeof(flag)) < 0) {
+ log_fatal("Can't set IPV6_V6ONLY option on dhcp "
+ "socket: %m");
+ }
+ }
+#endif
/* Bind the socket to this interface's IP address. */
if (bind(sock, (struct sockaddr *)&name, name_len) < 0) {
@@ -498,7 +511,8 @@ if_register6(struct interface_info *info, int do_multicast) {
if (req_multi)
if_register_multicast(info);
- get_hw_addr(info->name, &info->hw_address);
+ if (strcmp(info->name, "ipv6") != 0)
+ get_hw_addr(info->name, &info->hw_address);
if (!quiet_interface_discovery) {
if (info->shared_network != NULL) {
diff --git a/dhcpctl/omshell.c b/dhcpctl/omshell.c
index bb489d84..339ce518 100644
--- a/dhcpctl/omshell.c
+++ b/dhcpctl/omshell.c
@@ -60,6 +60,7 @@ void bootp (struct packet *packet) { }
#ifdef DHCPv6
/* XXX: should we warn or something here? */
+void dhcp_tsv(struct packet *packet) { }
void dhcpv6(struct packet *packet) { }
#endif /* DHCPv6 */
diff --git a/includes/dhcp.h b/includes/dhcp.h
index 5eb1ad8b..4162a663 100644
--- a/includes/dhcp.h
+++ b/includes/dhcp.h
@@ -188,6 +188,9 @@ struct dhcp_packet {
#define RAI_REMOTE_ID 2
#define RAI_AGENT_ID 3
#define RAI_LINK_SELECT 5
+#ifndef RAI_CRA6ADDR
+#define RAI_CRA6ADDR 46
+#endif
/* FQDN suboptions: */
#define FQDN_NO_CLIENT_UPDATE 1
diff --git a/includes/dhcpd.h b/includes/dhcpd.h
index b8792fae..d3979e53 100644
--- a/includes/dhcpd.h
+++ b/includes/dhcpd.h
@@ -720,6 +720,7 @@ struct lease_state {
#endif
#endif
#define SV_CACHE_THRESHOLD 78
+#define SV_LOCAL_ADDRESS6 79
#if !defined (DEFAULT_PING_TIMEOUT)
# define DEFAULT_PING_TIMEOUT 1
@@ -1227,6 +1228,7 @@ struct interface_info {
struct shared_network *shared_network;
/* Networks connected to this interface. */
struct hardware hw_address; /* Its physical address. */
+ int address_family; /* Its address family */
struct in_addr *addresses; /* Addresses associated with this
* interface.
*/
@@ -1261,6 +1263,8 @@ struct interface_info {
int configured; /* If set to 1, interface has at least
* one valid IP address.
*/
+ int registered; /* If set to 1, interface read file
+ descriptor is registered. */
u_int32_t flags; /* Control flags... */
#define INTERFACE_REQUESTED 1
#define INTERFACE_AUTOMATIC 2
@@ -1839,6 +1843,8 @@ void do_packet (struct interface_info *,
unsigned int, struct iaddr, struct hardware *);
void do_packet6(struct interface_info *, const char *,
int, int, const struct iaddr *, isc_boolean_t);
+void do_packet_tsv(struct interface_info *, const char *,
+ int, int, const struct iaddr *, isc_boolean_t);
int packet6_len_okay(const char *, int);
int validate_packet(struct packet *);
@@ -1849,6 +1855,7 @@ int add_option(struct option_state *options,
unsigned int data_len);
/* dhcpd.c */
+extern int run_as_tsv;
extern struct timeval cur_tv;
#define cur_time cur_tv.tv_sec
@@ -1874,6 +1881,9 @@ isc_result_t dhcp_set_control_state (control_object_state_t oldstate,
control_object_state_t newstate);
#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
void relinquish_ackqueue(void);
+#ifdef DHCPv6
+void relinquish_ackqueue_tsv(void);
+#endif
#endif
/* conflex.c */
@@ -2165,8 +2175,25 @@ void get_server_source_address(struct in_addr *from,
struct option_state *options,
struct packet *packet);
+/* tsv.c */
+void dhcp_tsv (struct packet *);
+void dhcpdiscover_tsv (struct packet *, int);
+void dhcprequest_tsv (struct packet *, int, struct lease *);
+void dhcprelease_tsv (struct packet *, int);
+void dhcpdecline_tsv (struct packet *, int);
+void dhcpinform_tsv (struct packet *, int);
+void nak_lease_tsv (struct packet *, struct iaddr *cip);
+void ack_lease_tsv (struct packet *, struct lease *,
+ unsigned int, TIME, char *, int, struct host_decl *);
+void delayed_ack_enqueue_tsv(struct lease *);
+void flush_ackqueue_tsv(void *);
+void dhcp_reply_tsv (struct lease *);
+
+int locate_network_tsv (struct packet *);
+
/* dhcpleasequery.c */
void dhcpleasequery (struct packet *, int);
+void dhcpleasequery_tsv (struct packet *, int);
void dhcpv6_leasequery (struct data_string *, struct packet *);
/* dhcpv6.c */
diff --git a/relay/.cvsignore b/relay/.cvsignore
index f45af85c..d96665da 100644
--- a/relay/.cvsignore
+++ b/relay/.cvsignore
@@ -2,3 +2,7 @@
Makefile
dhcrelay
dhcrelay.man8
+dhccra
+dhccra.man8
+dhctra
+dhctra.man8
diff --git a/relay/Makefile.am b/relay/Makefile.am
index d8757cac..71769549 100644
--- a/relay/Makefile.am
+++ b/relay/Makefile.am
@@ -1,9 +1,15 @@
AM_CPPFLAGS = -DLOCALSTATEDIR='"@localstatedir@"'
-sbin_PROGRAMS = dhcrelay
+sbin_PROGRAMS = dhcrelay dhccra dhctra
dhcrelay_SOURCES = dhcrelay.c
dhcrelay_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
../bind/lib/libdns.a ../bind/lib/libisc.a
-man_MANS = dhcrelay.8
+dhccra_SOURCES = dhccra.c
+dhccra_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
+dhctra_SOURCES = dhctra.c
+dhctra_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
+man_MANS = dhcrelay.8 dhccra.8 dhctra.8
EXTRA_DIST = $(man_MANS)
diff --git a/relay/Makefile.in b/relay/Makefile.in
index 7e8f606a..89bbf39e 100644
--- a/relay/Makefile.in
+++ b/relay/Makefile.in
@@ -30,7 +30,7 @@ POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
-sbin_PROGRAMS = dhcrelay$(EXEEXT)
+sbin_PROGRAMS = dhcrelay$(EXEEXT) dhccra$(EXEEXT) dhctra$(EXEEXT)
subdir = relay
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
@@ -43,10 +43,18 @@ CONFIG_CLEAN_FILES =
am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)"
sbinPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
PROGRAMS = $(sbin_PROGRAMS)
+am_dhccra_OBJECTS = dhccra.$(OBJEXT)
+dhccra_OBJECTS = $(am_dhccra_OBJECTS)
+dhccra_DEPENDENCIES = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
am_dhcrelay_OBJECTS = dhcrelay.$(OBJEXT)
dhcrelay_OBJECTS = $(am_dhcrelay_OBJECTS)
dhcrelay_DEPENDENCIES = ../common/libdhcp.a ../omapip/libomapi.a \
../bind/lib/libdns.a ../bind/lib/libisc.a
+am_dhctra_OBJECTS = dhctra.$(OBJEXT)
+dhctra_OBJECTS = $(am_dhctra_OBJECTS)
+dhctra_DEPENDENCIES = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes
depcomp = $(SHELL) $(top_srcdir)/depcomp
am__depfiles_maybe = depfiles
@@ -54,8 +62,8 @@ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
CCLD = $(CC)
LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
-SOURCES = $(dhcrelay_SOURCES)
-DIST_SOURCES = $(dhcrelay_SOURCES)
+SOURCES = $(dhccra_SOURCES) $(dhcrelay_SOURCES) $(dhctra_SOURCES)
+DIST_SOURCES = $(dhccra_SOURCES) $(dhcrelay_SOURCES) $(dhctra_SOURCES)
man8dir = $(mandir)/man8
NROFF = nroff
MANS = $(man_MANS)
@@ -158,7 +166,15 @@ dhcrelay_SOURCES = dhcrelay.c
dhcrelay_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
../bind/lib/libdns.a ../bind/lib/libisc.a
-man_MANS = dhcrelay.8
+dhccra_SOURCES = dhccra.c
+dhccra_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
+
+dhctra_SOURCES = dhctra.c
+dhctra_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
+ ../bind/lib/libdns.a ../bind/lib/libisc.a
+
+man_MANS = dhcrelay.8 dhccra.8 dhctra.8
EXTRA_DIST = $(man_MANS)
all: all-am
@@ -216,9 +232,15 @@ uninstall-sbinPROGRAMS:
clean-sbinPROGRAMS:
-test -z "$(sbin_PROGRAMS)" || rm -f $(sbin_PROGRAMS)
+dhccra$(EXEEXT): $(dhccra_OBJECTS) $(dhccra_DEPENDENCIES)
+ @rm -f dhccra$(EXEEXT)
+ $(LINK) $(dhccra_OBJECTS) $(dhccra_LDADD) $(LIBS)
dhcrelay$(EXEEXT): $(dhcrelay_OBJECTS) $(dhcrelay_DEPENDENCIES)
@rm -f dhcrelay$(EXEEXT)
$(LINK) $(dhcrelay_OBJECTS) $(dhcrelay_LDADD) $(LIBS)
+dhctra$(EXEEXT): $(dhctra_OBJECTS) $(dhctra_DEPENDENCIES)
+ @rm -f dhctra$(EXEEXT)
+ $(LINK) $(dhctra_OBJECTS) $(dhctra_LDADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
@@ -226,7 +248,9 @@ mostlyclean-compile:
distclean-compile:
-rm -f *.tab.c
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhccra.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcrelay.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhctra.Po@am__quote@
.c.o:
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
diff --git a/relay/dhccra.8 b/relay/dhccra.8
new file mode 100644
index 00000000..3fc1bd00
--- /dev/null
+++ b/relay/dhccra.8
@@ -0,0 +1,136 @@
+.\" dhccra.8
+.\"
+.\" Copyright (c) 2009-2012 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 1997-2003 by Internet Software Consortium
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" This software has been written for Internet Systems Consortium
+.\" by Ted Lemon in cooperation with Vixie Enterprises.
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.\" $Id$
+.\"
+.TH dhccra 8
+.SH NAME
+dhccra - DHCPv4-over-IPv6 Client Relay Agent
+.SH SYNOPSIS
+.B dhccra
+[
+.B -dq
+]
+[
+.B -p
+.I port
+]
+[
+.B -c
+.I count
+]
+[
+.B -pf
+.I pid-file
+]
+[
+.B --no-pid
+]
+[
+.B -l4
+.I local-ipv4-address
+]
+[
+.B -l6
+.I local-ipv6-address
+]
+.B -S
+.I node | link
+.B -i
+.I interface
+.I server0
+[
+.I ...serverN
+]
+.SH DESCRIPTION
+The Internet Systems Consortium DHCPv4 over IPv6 Client Relay Agent,
+dhccra, provides a means for relaying DHCPv4 requests from a subnet to
+which no DHCP server is directly connected to one or more DHCP
+IPv6-transport servers.
+.SH OPERATION
+.PP
+The DHCP Client Relay Agent listens for DHCPv4 queries from clients on
+the given interface, passing them along over IPv6 to ``upstream''
+servers or relay agents as specified on the command line. When a
+reply is received from upstream, it is broadcast or unicast back
+downstream to the source of the original request.
+.SH COMMAND LINE
+.TP
+-c COUNT
+Maximum hop count. When forwarding packets, dhccra discards packets
+which have reached a hop count of COUNT. Default is 10. Maximum is 255.
+.TP
+-d
+Force dhccra to run as a foreground process. Useful when running
+dhccra under a debugger, or running out of inittab on System V systems.
+.TP
+-p PORT
+Listen and transmit on port PORT. This is mostly useful for debugging
+purposes. Default is port 67.
+.TP
+-q
+Quiet mode. Prevents dhccra from printing its network configuration
+on startup.
+.TP
+-S \fInode|link\fR
+Serve only the colocated client (node) as a HCRA or serve the attached
+link (link) as a LCRA.
+Note this is mandatory, i.e., there is no default.
+.TP
+-pf pid-file
+Path to alternate pid file.
+.TP
+--no-pid
+Option to disable writing pid files. By default the program
+will write a pid file.
+.TP
+-l4 \fIlocal-ipv4-address\fR
+Bound the fallback to the \fIlocal-ipv4-address\fR to avoid conflicts
+with a wildcard DHCPv4 server running on the same box.
+.TP
+-l6 \fIlocal-ipv6-address\fR
+Use the \fIlocal-ipv4-address\fR as the source for the IPv6 transport.
+.TP
+-i \fIifname\fR
+Listen for DHCPv4 queries on interface \fIifname\fR.
+.TP
+server
+A list of one or more server IPv6 addresses to which DHCP queries should be
+relayed over IPv6.
+.SH SEE ALSO
+dhctra(8), dhcrelay(8), dhclient(8), dhcpd(8), RFC3315, RFC2132, RFC2131.
+.SH BUGS
+The spec limits the Client Relay Agent to only one IPv4 interface.
+To run more than one Client Relay Agent, each daemon serving a different
+interface, the -l option with different IPv6 addresses should be used.
+.SH AUTHOR
+.B dhccra(8)
+To learn more about Internet Systems Consortium, see
+.B https://www.isc.org
diff --git a/relay/dhccra.c b/relay/dhccra.c
new file mode 100644
index 00000000..2ce16ebf
--- /dev/null
+++ b/relay/dhccra.c
@@ -0,0 +1,695 @@
+/* dhccra.c
+
+ DHCP Client Relay Agent. */
+
+/*
+ * Copyright(c) 2004-2012 by Internet Systems Consortium, Inc.("ISC")
+ * Copyright(c) 1997-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This software has been written for Internet Systems Consortium
+ * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc.
+ * To learn more about Internet Systems Consortium, see
+ * ``https://www.isc.org/''. To learn more about Vixie Enterprises,
+ * see ``http://www.vix.com''. To learn more about Nominum, Inc., see
+ * ``http://www.nominum.com''.
+ */
+
+#include "dhcpd.h"
+
+#ifdef DHCPv6
+
+#include <syslog.h>
+#include <sys/time.h>
+
+TIME default_lease_time = 43200; /* 12 hours... */
+TIME max_lease_time = 86400; /* 24 hours... */
+struct tree_cache *global_options[256];
+
+struct option *requested_opts[2];
+
+/* Needed to prevent linking against conflex.c. */
+int lexline;
+int lexchar;
+char *token_line;
+char *tlname;
+
+const char *path_dhcrelay_pid = _PATH_DHCRELAY_PID;
+isc_boolean_t no_dhcrelay_pid = ISC_FALSE;
+/* False (default) => we write and use a pid file */
+isc_boolean_t no_pid_file = ISC_FALSE;
+
+struct in_addr inaddr_any;
+
+int client_packets_relayed = 0; /* Packets relayed from client to server. */
+int server_packet_errors = 0; /* Errors sending packets to servers. */
+int server_packets_relayed = 0; /* Packets relayed from server to client. */
+int client_packet_errors = 0; /* Errors sending packets to clients. */
+
+int max_hop_count = 10; /* Maximum hop count */
+int colocated_only = -1; /* Serve only the colocated client. */
+
+u_int16_t local_port;
+u_int16_t remote_port;
+
+struct interface_info *if6, *if4 = NULL;
+
+/* server list. */
+struct server_list {
+ struct server_list *next;
+ struct sockaddr_in6 to;
+} *servers;
+
+static int getrecv6();
+static void do_relay4to6(struct interface_info *, struct dhcp_packet *,
+ unsigned int, unsigned int, struct iaddr,
+ struct hardware *);
+static void do_relay6to4(struct interface_info *, const char *, int, int,
+ const struct iaddr *, isc_boolean_t);
+static int find_relay_agent_options(struct dhcp_packet *, unsigned int);
+
+static const char copyright[] =
+"Copyright 2004-2012 Internet Systems Consortium.";
+static const char arr[] = "All rights reserved.";
+static const char message[] =
+"Internet Systems Consortium DHCP Client Relay Agent";
+static const char url[] =
+"For info, please visit https://www.isc.org/software/dhcp/";
+
+#define DHCCRA_USAGE \
+"Usage: dhccra [-d] [-q] -S {node|link} [-c <hops>] [-p <port>]\n" \
+" [-pf <pid-file>] [--no-pid] [-l{4|6} <local-address>]\n" \
+" -i ifname server0 [ ... serverN]\n\n"
+
+static void usage() {
+ log_fatal(DHCCRA_USAGE);
+}
+
+int
+main(int argc, char **argv) {
+ isc_result_t status;
+ struct servent *ent;
+ struct server_list *sp = NULL;
+ struct interface_info *tmp = NULL;
+ char *service_local = NULL, *service_remote = NULL;
+ u_int16_t port_local = 0, port_remote = 0;
+ int no_daemon = 0, quiet = 0;
+ int fd;
+ int i;
+
+ /* Make sure that file descriptors 0(stdin), 1,(stdout), and
+ 2(stderr) are open. To do this, we assume that when we
+ open a file the lowest available file descriptor is used. */
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 0)
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 1)
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 2)
+ log_perror = 0; /* No sense logging to /dev/null. */
+ else if (fd != -1)
+ close(fd);
+
+ openlog("dhccra", LOG_NDELAY, LOG_DAEMON);
+
+#if !defined(DEBUG)
+ setlogmask(LOG_UPTO(LOG_INFO));
+#endif
+
+ /* Set up the isc and dns library managers */
+ status = dhcp_context_create();
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't initialize context: %s",
+ isc_result_totext(status));
+
+ /* Set up the OMAPI. */
+ status = omapi_init();
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't initialize OMAPI: %s",
+ isc_result_totext(status));
+
+ /* Set up the OMAPI wrappers for the interface object. */
+ interface_setup();
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-d")) {
+ no_daemon = 1;
+ } else if (!strcmp(argv[i], "-q")) {
+ quiet = 1;
+ quiet_interface_discovery = 1;
+ } else if (!strcmp(argv[i], "-S")) {
+ if (++i == argc)
+ usage();
+ if (strcmp(argv[i], "node") == 0)
+ colocated_only = 1;
+ else if (strcmp(argv[i], "link") == 0)
+ colocated_only = 0;
+ else
+ usage();
+ } else if (!strcmp(argv[i], "-p")) {
+ if (++i == argc)
+ usage();
+ local_port = validate_port(argv[i]);
+ log_debug("binding to user-specified port %d",
+ ntohs(local_port));
+ } else if (!strcmp(argv[i], "-l4")) {
+ if (++i == argc)
+ usage();
+ if (inet_pton(AF_INET, argv[i], &local_address) != 1)
+ log_fatal("%s: bad IPv4 local address",
+ argv[i]);
+ } else if (!strcmp(argv[i], "-l6")) {
+ if (++i == argc)
+ usage();
+ if (inet_pton(AF_INET6, argv[i], &local_address6) != 1)
+ log_fatal("%s: bad IPv6 local address",
+ argv[i]);
+ } else if (!strcmp(argv[i], "-c")) {
+ int hcount;
+ if (++i == argc)
+ usage();
+ hcount = atoi(argv[i]);
+ if (hcount <= 255)
+ max_hop_count= hcount;
+ else
+ usage();
+ } else if (!strcmp(argv[i], "-i")) {
+ if (if4 != NULL) {
+ usage();
+ }
+ status = interface_allocate(&if4, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal("%s: interface_allocate(v4): %s",
+ argv[i],
+ isc_result_totext(status));
+ if (++i == argc) {
+ usage();
+ }
+ strcpy(if4->name, argv[i]);
+ interface_snorf(if4, INTERFACE_REQUESTED);
+ tmp = if4,
+ interface_dereference(&tmp, MDL);
+ } else if (!strcmp(argv[i], "-pf")) {
+ if (++i == argc)
+ usage();
+ path_dhcrelay_pid = argv[i];
+ no_dhcrelay_pid = ISC_TRUE;
+ } else if (!strcmp(argv[i], "--no-pid")) {
+ no_pid_file = ISC_TRUE;
+ } else if (!strcmp(argv[i], "--version")) {
+ log_info("isc-dhccra-%s", PACKAGE_VERSION);
+ exit(0);
+ } else if (!strcmp(argv[i], "--help") ||
+ !strcmp(argv[i], "-h")) {
+ log_info(DHCCRA_USAGE);
+ exit(0);
+ } else if (argv[i][0] == '-') {
+ usage();
+ } else {
+ struct addrinfo hints, *ai = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ if (getaddrinfo(argv[i], NULL, &hints, &ai) != 0)
+ log_error("%s: host unknown", argv[i]);
+
+ if (ai) {
+ sp = ((struct server_list *)
+ dmalloc(sizeof *sp, MDL));
+ if (!sp)
+ log_fatal("no memory for server.\n");
+ sp->next = servers;
+ servers = sp;
+ memcpy(&sp->to.sin6_addr,
+ &((struct sockaddr_in6 *)ai->ai_addr)->
+ sin6_addr,
+ sizeof(sp->to.sin6_addr));
+ freeaddrinfo(ai);
+ }
+ }
+ }
+ if (colocated_only < 0) {
+ log_info("-S {node|link} is mandatory");
+ usage();
+ }
+
+ /*
+ * If the user didn't specify a pid file directly
+ * find one from environment variables or defaults
+ */
+ if (no_dhcrelay_pid == ISC_FALSE) {
+ path_dhcrelay_pid = getenv("PATH_DHCCRA_PID");
+ if (path_dhcrelay_pid == NULL)
+ path_dhcrelay_pid = getenv("PATH_DHCRELAY_PID");
+ if (path_dhcrelay_pid == NULL)
+ path_dhcrelay_pid = _PATH_DHCRELAY_PID;
+ }
+
+ if (!quiet) {
+ log_info("%s %s", message, PACKAGE_VERSION);
+ log_info(copyright);
+ log_info(arr);
+ log_info(url);
+ } else {
+ quiet = 0;
+ log_perror = 0;
+ }
+
+ /* Set default port */
+ service_local = "bootps";
+ service_remote = "bootpc";
+ port_local = htons(67);
+ port_remote = htons(68);
+
+ if (!local_port) {
+ ent = getservbyname(service_local, "udp");
+ if (ent)
+ local_port = ent->s_port;
+ else
+ local_port = port_local;
+
+ ent = getservbyname(service_remote, "udp");
+ if (ent)
+ remote_port = ent->s_port;
+ else
+ remote_port = port_remote;
+
+ endservent();
+ }
+
+ /* The interface is mandatory */
+ if (if4 == NULL) {
+ log_fatal("No interface specified.");
+ }
+
+ /* We need at least one server */
+ if (servers == NULL) {
+ log_fatal("No servers specified.");
+ }
+
+ /* Set up the server sockaddrs. */
+ for (sp = servers; sp; sp = sp->next) {
+ sp->to.sin6_port = local_port;
+ sp->to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ sp->to.sin6_len = sizeof sp->to;
+#endif
+ }
+
+ /* Get the current time... */
+ gettimeofday(&cur_tv, NULL);
+
+ inaddr_any.s_addr = INADDR_ANY;
+
+ /* Discover all the network interfaces. */
+ local_family = AF_INET;
+ discover_interfaces(DISCOVER_RELAY);
+
+ /* Get the IPv6 sockets. */
+ local_family = AF_INET6;
+ discover_interfaces(DISCOVER_RUNNING);
+ status = interface_allocate(&if6, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal("interface_allocate(v6): %s",
+ isc_result_totext(status));
+ strcpy(if6->name, "ipv6");
+ interface_snorf(if6, if4->flags);
+ tmp = if6;
+ interface_dereference(&tmp, MDL);
+ if_register6(if6, 0);
+
+ if6->rfdesc = getrecv6();
+ status = omapi_register_io_object((omapi_object_t *)if6,
+ if_readsocket,
+ 0, got_one_v6, 0, 0);
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't register I/O handle for IPv6-transport: %s",
+ isc_result_totext (status));
+
+ /* Become a daemon... */
+ if (!no_daemon) {
+ int pid;
+ FILE *pf;
+ int pfdesc;
+
+ log_perror = 0;
+
+ if ((pid = fork()) < 0)
+ log_fatal("Can't fork daemon: %m");
+ else if (pid)
+ exit(0);
+
+ if (no_pid_file == ISC_FALSE) {
+ pfdesc = open(path_dhcrelay_pid,
+ O_CREAT | O_TRUNC | O_WRONLY, 0644);
+
+ if (pfdesc < 0) {
+ log_error("Can't create %s: %m",
+ path_dhcrelay_pid);
+ } else {
+ pf = fdopen(pfdesc, "w");
+ if (!pf)
+ log_error("Can't fdopen %s: %m",
+ path_dhcrelay_pid);
+ else {
+ fprintf(pf, "%ld\n",(long)getpid());
+ fclose(pf);
+ }
+ }
+ }
+
+ close(0);
+ close(1);
+ close(2);
+ pid = setsid();
+
+ IGNORE_RET (chdir("/"));
+ }
+
+ /* Set up the packet handler... */
+ bootp_packet_handler = do_relay4to6;
+ dhcpv6_packet_handler = do_relay6to4;
+
+ /* Start dispatching packets and timeouts... */
+ dispatch();
+
+ /* Not reached */
+ return (0);
+}
+
+static int
+getrecv6() {
+ struct sockaddr_in6 addr;
+ int addr_len;
+ int sock;
+ int flag;
+
+ sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0)
+ log_fatal("Can't create IPv6-transport receive socket: %m");
+
+ flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&flag, sizeof(flag)) < 0)
+ log_fatal("Can't set SO_REUSEADDR option on "
+ "IPv6-transport receive socket: %m");
+
+#ifdef IPV6_V6ONLY
+ flag = 1;
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&flag, sizeof(flag)) < 0)
+ log_fatal("Can't set IPV6_V6ONLY option on "
+ "IPv6-transport receive socket: %m");
+#endif
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ addr.sin6_len = sizeof(addr);
+#endif
+ addr.sin6_port = remote_port;
+ memcpy(&addr.sin6_addr, &local_address6, sizeof(addr.sin6_addr));
+ addr_len = sizeof(addr);
+ if (bind(sock, (struct sockaddr *)&addr, addr_len) < 0) {
+ log_error("Can't bind to IPv6-transport receive port: %m");
+ log_fatal("Please make sure there is"
+ "no other dhcp agent running .");
+ }
+
+ flag = 1;
+#ifdef IPV6_RECVPKTINFO
+ /* RFC3542 */
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ (char *)&flag, sizeof(flag)) < 0)
+ log_fatal("Can't set IPV6_RECVPKTINFO option on "
+ "IPv6-transport receive socket: %m");
+#else
+ /* RFC2292 */
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
+ (char *)&flag, sizeof(flag)) < 0)
+ log_fatal("Can't set IPV6_PKTINFO option on "
+ "IPv6-transport receive socket: %m");
+#endif
+
+ return sock;
+}
+
+/* From IPv4 to IPv6 BOOTREQUEST: forward it to all the servers. */
+
+static void
+do_relay4to6(struct interface_info *ip, struct dhcp_packet *packet,
+ unsigned int length, unsigned int from_port, struct iaddr from,
+ struct hardware *hfrom) {
+ struct server_list *sp;
+
+ if (packet->hlen > sizeof packet->chaddr) {
+ log_info("Discarding packet with invalid hlen, received on "
+ "%s v4 interface.", ip->name);
+ return;
+ }
+
+ if (packet->op != BOOTREQUEST)
+ return;
+
+ /* only from a client */
+ if (packet->giaddr.s_addr)
+ return;
+
+ /* check the hardware address for colocated constraint. */
+ if (colocated_only && ip && hfrom &&
+ ((ip->hw_address.hlen != hfrom->hlen) ||
+ memcmp(ip->hw_address.hbuf, hfrom->hbuf, hfrom->hlen)))
+ return;
+
+ if (packet->hops < max_hop_count)
+ packet->hops = packet->hops + 1;
+ else
+ return;
+
+ for (sp = servers; sp; sp = sp->next) {
+ if (sendto(if6->wfdesc,
+ (unsigned char *)packet,
+ length, 0,
+ (struct sockaddr *)&sp->to,
+ sizeof(sp->to)) < 0) {
+ ++client_packet_errors;
+ } else {
+ char addrbuf[MAX_ADDRESS_STRING_LEN];
+
+ inet_ntop(AF_INET6, &sp->to.sin6_addr, addrbuf,
+ MAX_ADDRESS_STRING_LEN);
+ log_debug("Forwarded BOOTREQUEST for %s to %s",
+ print_hw_addr(packet->htype, packet->hlen,
+ packet->chaddr),
+ addrbuf);
+ ++client_packets_relayed;
+ }
+ }
+}
+
+/* From IPv6 to IPv4 BOOTREPLY: forward it to the client. */
+
+static void
+do_relay6to4(struct interface_info *ip, const char *msg,
+ int len, int from_port, const struct iaddr *from,
+ isc_boolean_t was_unicast) {
+ struct dhcp_packet *packet;
+ struct sockaddr_in to;
+ struct hardware hto, *htop;
+ struct in_addr local;
+ unsigned int length;
+
+ packet = (struct dhcp_packet *)msg;
+ length = (unsigned int)len;
+
+ if (packet->hlen > sizeof packet->chaddr) {
+ log_info("Discarding packet with invalid hlen, received on "
+ "%s v6 interface.", ip->name);
+ return;
+ }
+
+ if (packet->op != BOOTREPLY)
+ return;
+
+ memset(&to, 0, sizeof(to));
+ to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ to.sin_len = sizeof(to);
+#endif
+ if (!(packet->flags & htons(BOOTP_BROADCAST)) &&
+ can_unicast_without_arp(if4)) {
+ to.sin_addr = packet->yiaddr;
+ to.sin_port = remote_port;
+
+ /* and hardware address is not broadcast */
+ htop = &hto;
+ } else {
+ to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+ to.sin_port = remote_port;
+
+ /* hardware address is broadcast */
+ htop = NULL;
+ }
+
+ memcpy(&hto.hbuf[1], packet->chaddr, packet->hlen);
+ hto.hbuf[0] = packet->htype;
+ hto.hlen = packet->hlen + 1;
+
+ /* relay agent options are illegal */
+ if (find_relay_agent_options(packet, length))
+ return;
+
+ if (if4->address_count > 0)
+ local = if4->addresses[0];
+ else
+ local = inaddr_any;
+ if (send_packet(if4, NULL, packet, length, local, &to, htop) < 0) {
+ ++server_packet_errors;
+ } else {
+ log_debug("Forwarded BOOTREPLY for %s to %s",
+ print_hw_addr(packet->htype, packet->hlen,
+ packet->chaddr),
+ inet_ntoa(to.sin_addr));
+ ++server_packets_relayed;
+ }
+}
+
+/* Find relay agent options */
+
+static int
+find_relay_agent_options(struct dhcp_packet *packet, unsigned length) {
+ int is_dhcp = 0;
+ u_int8_t *op, *nextop, *sp, *max;
+
+ /* If there's no cookie, it's a bootp packet, so we should just
+ forward it unchanged. */
+ if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 4))
+ return (0);
+
+ max = ((u_int8_t *)packet) + length;
+ sp = op = &packet->options[4];
+
+ while (op < max) {
+ switch(*op) {
+ /* Skip padding... */
+ case DHO_PAD:
+ if (sp != op)
+ *sp = *op;
+ ++op;
+ ++sp;
+ continue;
+
+ /* If we see a message type, it's a DHCP packet. */
+ case DHO_DHCP_MESSAGE_TYPE:
+ is_dhcp = 1;
+ goto skip;
+ break;
+
+ /* Quit immediately if we hit an End option. */
+ case DHO_END:
+ if (sp != op)
+ *sp++ = *op++;
+ return (0);
+
+ case DHO_DHCP_AGENT_OPTIONS:
+ /* We shouldn't see a relay agent option in a
+ packet before we've seen the DHCP packet type,
+ but if we do, we have to leave it alone. */
+ if (!is_dhcp)
+ goto skip;
+
+ return (1);
+
+ skip:
+ /* Skip over other options. */
+ default:
+ /* Fail if processing this option will exceed the
+ * buffer(op[1] is malformed).
+ */
+ nextop = op + op[1] + 2;
+ if (nextop > max)
+ return (-1);
+
+ if (sp != op) {
+ memmove(sp, op, op[1] + 2);
+ sp += op[1] + 2;
+ op = nextop;
+ } else
+ op = sp = nextop;
+
+ break;
+ }
+ }
+ return (0);
+}
+
+/* Stub routines needed for linking with DHCP libraries. */
+void
+bootp(struct packet *packet) {
+ return;
+}
+
+void
+dhcp(struct packet *packet) {
+ return;
+}
+
+void
+dhcp_tsv(struct packet *packet) {
+ return;
+}
+
+void
+classify(struct packet *p, struct class *c) {
+ return;
+}
+
+int
+check_collection(struct packet *p, struct lease *l, struct collection *c) {
+ return 0;
+}
+
+isc_result_t
+find_class(struct class **class, const char *c1, const char *c2, int i) {
+ return ISC_R_NOTFOUND;
+}
+
+int
+parse_allow_deny(struct option_cache **oc, struct parse *p, int i) {
+ return 0;
+}
+
+isc_result_t
+dhcp_set_control_state(control_object_state_t oldstate,
+ control_object_state_t newstate) {
+ return ISC_R_SUCCESS;
+}
+
+#else
+
+int
+main(int argc, char **argv) {
+ log_error("Required DHCPv6 support was disabled.");
+ return -1;
+}
+#endif /* DHCPv6 */
diff --git a/relay/dhcrelay.8 b/relay/dhcrelay.8
index 613c8883..ec662b04 100644
--- a/relay/dhcrelay.8
+++ b/relay/dhcrelay.8
@@ -162,7 +162,7 @@ Listen and transmit on port PORT. This is mostly useful for debugging
purposes. Default is port 67 for DHCPv4/BOOTP, or port 547 for DHCPv6.
.TP
-q
-Quiet mode. Prevents dhcrelay6 from printing its network configuration
+Quiet mode. Prevents dhcrelay from printing its network configuration
on startup.
.TP
-pf pid-file
diff --git a/relay/dhcrelay.c b/relay/dhcrelay.c
index 5dcec623..4881d0bf 100644
--- a/relay/dhcrelay.c
+++ b/relay/dhcrelay.c
@@ -1652,6 +1652,13 @@ dhcp(struct packet *packet) {
return;
}
+#ifdef DHCPv6
+void
+dhcp_tsv(struct packet *packet) {
+ return;
+}
+#endif
+
void
classify(struct packet *p, struct class *c) {
return;
diff --git a/relay/dhctra.8 b/relay/dhctra.8
new file mode 100644
index 00000000..cdffa885
--- /dev/null
+++ b/relay/dhctra.8
@@ -0,0 +1,133 @@
+.\" dhctra.8
+.\"
+.\" Copyright (c) 2009-2012 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2004,2007 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 1997-2003 by Internet Software Consortium
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+.\" OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" Internet Systems Consortium, Inc.
+.\" 950 Charter Street
+.\" Redwood City, CA 94063
+.\" <info@isc.org>
+.\" https://www.isc.org/
+.\"
+.\" This software has been written for Internet Systems Consortium
+.\" by Ted Lemon in cooperation with Vixie Enterprises.
+.\"
+.\" Support and other services are available for ISC products - see
+.\" https://www.isc.org for more information or to learn more about ISC.
+.\"
+.\" $Id$
+.\"
+.TH dhctra 8
+.SH NAME
+dhctra - DHCPv4 IPv6-Transport Relay Agent
+.SH SYNOPSIS
+.B dhctra
+[
+.B -dqaD
+]
+[
+.B -p
+.I port
+]
+[
+.B -c
+.I count
+]
+[
+.B -A
+.I length
+]
+[
+.B -pf
+.I pid-file
+]
+[
+.B --no-pid
+]
+.I server0
+[
+.I ...serverN
+]
+.SH DESCRIPTION
+The Internet Systems Consortium DHCP IPv6-Transport Relay Agent,
+dhctra, provides a means for relaying DHCPv4 requests over IPv6 from a
+subnet to which no DHCP IPv6-transport server is directly connected to
+one or more DHCPv4 servers on other subnets.
+.SH OPERATION
+.PP
+The DHCP IPv6-Transport Relay Agent listens for DHCPv4 queries over
+IPv6 from Client Relay Agents at one or more IPv6 addresses, passing
+them along to ``upstream'' standard DHCPv4 servers or relay agents as
+specified on the command line.
+When a reply is received from upstream, it is relayed back over IPv6
+downstream to the source of the original request carried in a CRA6ADDR
+Relay Agent Suboption.
+.SH COMMAND LINE
+.TP
+-c COUNT
+Maximum hop count. When forwarding packets, dhctra discards packets
+which have reached a hop count of COUNT. Default is 10. Maximum is 255.
+.TP
+-d
+Force dhctra to run as a foreground process. Useful when running
+dhctra under a debugger, or running out of inittab on System V systems.
+.TP
+-p PORT
+Listen and transmit on port PORT. This is mostly useful for debugging
+purposes. Default is port 67.
+.TP
+-q
+Quiet mode. Prevents dhctra from printing its network configuration
+on startup.
+.TP
+-pf pid-file
+Path to alternate pid file.
+.TP
+--no-pid
+Option to disable writing pid files. By default the program
+will write a pid file.
+.TP
+-a
+Append extra (to CRA6ADDR) agent options to each request before
+forwarding it to the server. Agent option fields in responses sent
+from servers to clients will be stripped before forwarding such
+responses back to the client. The agent option field will contain two
+agent options: the Circuit ID suboption and the Remote ID suboption.
+Currently, the Circuit ID will be the printable name of the interface
+on which the client request was received. The client supports
+inclusion of a Remote ID suboption as well, but this is not used by
+default.
+.TP
+-A LENGTH
+Specify the maximum packet size to send to a DHCPv4 server. This
+might be done to allow sufficient space for addition of relay agent
+options while still fitting into the Ethernet MTU size.
+.TP
+-D
+Drop packets from upstream servers if they contain Relay Agent
+Information options that indicate they were generated in response to
+a query that came via a different relay agent. If this option is not
+specified, such packets will be relayed anyway.
+.TP
+server
+A list of one or more server IPv4 addresses to which DHCP queries should be
+relayed.
+.SH SEE ALSO
+dhccra(8), dhctra(8), dhclient(8), dhcpd(8), RFC3315, RFC2132, RFC2131.
+.SH AUTHOR
+.B dhctra(8)
+To learn more about Internet Systems Consortium, see
+.B https://www.isc.org
diff --git a/relay/dhctra.c b/relay/dhctra.c
new file mode 100644
index 00000000..aa274ddd
--- /dev/null
+++ b/relay/dhctra.c
@@ -0,0 +1,1011 @@
+/* dhctra.c
+
+ DHCP IPv6-Transport Relay Agent. */
+
+/*
+ * Copyright(c) 2004-2012 by Internet Systems Consortium, Inc.("ISC")
+ * Copyright(c) 1997-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This software has been written for Internet Systems Consortium
+ * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc.
+ * To learn more about Internet Systems Consortium, see
+ * ``https://www.isc.org/''. To learn more about Vixie Enterprises,
+ * see ``http://www.vix.com''. To learn more about Nominum, Inc., see
+ * ``http://www.nominum.com''.
+ */
+
+#include "dhcpd.h"
+
+#ifdef DHCPv6
+
+#include <syslog.h>
+#include <sys/time.h>
+
+TIME default_lease_time = 43200; /* 12 hours... */
+TIME max_lease_time = 86400; /* 24 hours... */
+struct tree_cache *global_options[256];
+
+struct option *requested_opts[2];
+
+/* Needed to prevent linking against conflex.c. */
+int lexline;
+int lexchar;
+char *token_line;
+char *tlname;
+
+const char *path_dhcrelay_pid = _PATH_DHCRELAY_PID;
+isc_boolean_t no_dhcrelay_pid = ISC_FALSE;
+/* False (default) => we write and use a pid file */
+isc_boolean_t no_pid_file = ISC_FALSE;
+
+int bogus_agent_drops = 0; /* Packets dropped because agent option
+ field was specified and we're not relaying
+ packets that already have an agent option
+ specified. */
+int bogus_giaddr_drops = 0; /* Packets sent to us to relay back to a
+ client, but with a bogus giaddr. */
+int client_packets_relayed = 0; /* Packets relayed from client to server. */
+int server_packet_errors = 0; /* Errors sending packets to servers. */
+int server_packets_relayed = 0; /* Packets relayed from server to client. */
+int client_packet_errors = 0; /* Errors sending packets to clients. */
+
+int add_agent_options = 0; /* If nonzero, add relay agent options. */
+
+int agent_option_errors = 0; /* Number of packets forwarded without
+ agent options because there was no room. */
+int drop_agent_mismatches = 0; /* If nonzero, drop server replies that
+ don't have matching circuit-id's. */
+int corrupt_agent_options = 0; /* Number of packets dropped because
+ relay agent information option was bad. */
+int missing_agent_option = 0; /* Number of packets dropped because no
+ RAI option matching our ID was found. */
+int bad_circuit_id = 0; /* Circuit ID option in matching RAI option
+ did not match any known circuit ID. */
+int missing_circuit_id = 0; /* Circuit ID option in matching RAI option
+ was missing. */
+int missing_cra6addr = 0; /* CRA6ADDR option in matching RAI option
+ was missing. */
+int unknown_server = 0; /* IPv4 responses from an unknown server. */
+int max_hop_count = 10; /* Maximum hop count */
+
+ /* Maximum size of a packet with agent options added. */
+int dhcp_max_agent_option_packet_length = DHCP_MTU_MIN;
+
+u_int16_t local_port;
+u_int16_t remote_port;
+
+/* Relay agent server list. */
+struct server_list {
+ struct server_list *next;
+ struct sockaddr_in to;
+ struct in_addr src;
+} *servers;
+
+struct interface_info *if6;
+
+static void do_relay6to4(struct interface_info *, const char *, int, int,
+ const struct iaddr *, isc_boolean_t);
+static void do_relay4to6(struct interface_info *, struct dhcp_packet *,
+ unsigned int, unsigned int, struct iaddr,
+ struct hardware *);
+static int add_relay_agent_options(struct interface_info *,
+ struct dhcp_packet *, unsigned,
+ const struct iaddr *);
+static int find_ipv6_by_agent_option(struct dhcp_packet *,
+ struct in6_addr *, u_int8_t *, int);
+static int strip_relay_agent_options(struct interface_info *,
+ struct in6_addr *,
+ struct dhcp_packet *, unsigned);
+static void set_server_src(struct server_list *);
+
+static const char copyright[] =
+"Copyright 2004-2012 Internet Systems Consortium.";
+static const char arr[] = "All rights reserved.";
+static const char message[] =
+"Internet Systems Consortium DHCP IPv6-Transport Relay Agent";
+static const char url[] =
+"For info, please visit https://www.isc.org/software/dhcp/";
+
+#define DHCTRA_USAGE \
+"Usage: dhctra [-d] [-q] [-a] [-D] [-A <length>] [-c <hops>] [-p <port>]\n" \
+" [-pf <pid-file>] [--no-pid] server0 [ ... serverN]\n\n"
+
+static void usage() {
+ log_fatal(DHCTRA_USAGE);
+}
+
+int
+main(int argc, char **argv) {
+ isc_result_t status;
+ struct servent *ent;
+ struct server_list *sp = NULL;
+ struct interface_info *tmp = NULL;
+ char *service_local = NULL, *service_remote = NULL;
+ u_int16_t port_local = 0, port_remote = 0;
+ int no_daemon = 0, quiet = 0;
+ int fd;
+ int i;
+
+ /* Make sure that file descriptors 0(stdin), 1,(stdout), and
+ 2(stderr) are open. To do this, we assume that when we
+ open a file the lowest available file descriptor is used. */
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 0)
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 1)
+ fd = open("/dev/null", O_RDWR);
+ if (fd == 2)
+ log_perror = 0; /* No sense logging to /dev/null. */
+ else if (fd != -1)
+ close(fd);
+
+ openlog("dhctra", LOG_NDELAY, LOG_DAEMON);
+
+#if !defined(DEBUG)
+ setlogmask(LOG_UPTO(LOG_INFO));
+#endif
+
+ /* Set up the isc and dns library managers */
+ status = dhcp_context_create();
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't initialize context: %s",
+ isc_result_totext(status));
+
+ /* Set up the OMAPI. */
+ status = omapi_init();
+ if (status != ISC_R_SUCCESS)
+ log_fatal("Can't initialize OMAPI: %s",
+ isc_result_totext(status));
+
+ /* Set up the OMAPI wrappers for the interface object. */
+ interface_setup();
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-d")) {
+ no_daemon = 1;
+ } else if (!strcmp(argv[i], "-q")) {
+ quiet = 1;
+ quiet_interface_discovery = 1;
+ } else if (!strcmp(argv[i], "-p")) {
+ if (++i == argc)
+ usage();
+ local_port = validate_port(argv[i]);
+ log_debug("binding to user-specified port %d",
+ ntohs(local_port));
+ } else if (!strcmp(argv[i], "-c")) {
+ int hcount;
+ if (++i == argc)
+ usage();
+ hcount = atoi(argv[i]);
+ if (hcount <= 255)
+ max_hop_count= hcount;
+ else
+ usage();
+ } else if (!strcmp(argv[i], "-a")) {
+ add_agent_options = 1;
+ } else if (!strcmp(argv[i], "-A")) {
+ if (++i == argc)
+ usage();
+
+ dhcp_max_agent_option_packet_length = atoi(argv[i]);
+
+ if (dhcp_max_agent_option_packet_length > DHCP_MTU_MAX)
+ log_fatal("%s: packet length exceeds "
+ "longest possible MTU\n",
+ argv[i]);
+ } else if (!strcmp(argv[i], "-D")) {
+ drop_agent_mismatches = 1;
+ } else if (!strcmp(argv[i], "-pf")) {
+ if (++i == argc)
+ usage();
+ path_dhcrelay_pid = argv[i];
+ no_dhcrelay_pid = ISC_TRUE;
+ } else if (!strcmp(argv[i], "--no-pid")) {
+ no_pid_file = ISC_TRUE;
+ } else if (!strcmp(argv[i], "--version")) {
+ log_info("isc-dhctra-%s", PACKAGE_VERSION);
+ exit(0);
+ } else if (!strcmp(argv[i], "--help") ||
+ !strcmp(argv[i], "-h")) {
+ log_info(DHCTRA_USAGE);
+ exit(0);
+ } else if (argv[i][0] == '-') {
+ usage();
+ } else {
+ struct hostent *he;
+ struct in_addr ia, *iap = NULL;
+
+ if (inet_aton(argv[i], &ia)) {
+ iap = &ia;
+ } else {
+ he = gethostbyname(argv[i]);
+ if (!he) {
+ log_error("%s: host unknown", argv[i]);
+ } else {
+ iap = ((struct in_addr *)
+ he->h_addr_list[0]);
+ }
+ }
+
+ if (iap) {
+ sp = ((struct server_list *)
+ dmalloc(sizeof *sp, MDL));
+ if (!sp)
+ log_fatal("no memory for server.\n");
+ sp->next = servers;
+ servers = sp;
+ memcpy(&sp->to.sin_addr, iap, sizeof *iap);
+ }
+ }
+ }
+
+ /*
+ * If the user didn't specify a pid file directly
+ * find one from environment variables or defaults
+ */
+ if (no_dhcrelay_pid == ISC_FALSE) {
+ path_dhcrelay_pid = getenv("PATH_DHCRELAY6_PID");
+ if (path_dhcrelay_pid == NULL)
+ path_dhcrelay_pid = getenv("PATH_DHCRELAY_PID");
+ if (path_dhcrelay_pid == NULL)
+ path_dhcrelay_pid = _PATH_DHCRELAY_PID;
+ }
+
+ if (!quiet) {
+ log_info("%s %s", message, PACKAGE_VERSION);
+ log_info(copyright);
+ log_info(arr);
+ log_info(url);
+ } else {
+ quiet = 0;
+ log_perror = 0;
+ }
+
+ /* Set default port */
+ service_local = "bootps";
+ service_remote = "bootpc";
+ port_local = htons(67);
+ port_remote = htons(68);
+
+ if (!local_port) {
+ ent = getservbyname(service_local, "udp");
+ if (ent)
+ local_port = ent->s_port;
+ else
+ local_port = port_local;
+
+ ent = getservbyname(service_remote, "udp");
+ if (ent)
+ remote_port = ent->s_port;
+ else
+ remote_port = port_remote;
+
+ endservent();
+ }
+
+ /* We need at least one server */
+ if (servers == NULL) {
+ log_fatal("No servers specified.");
+ }
+
+ /* Set up the server sockaddrs. */
+ for (sp = servers; sp; sp = sp->next) {
+ sp->to.sin_port = local_port;
+ sp->to.sin_family = AF_INET;
+#ifdef HAVE_SA_LEN
+ sp->to.sin_len = sizeof sp->to;
+#endif
+ set_server_src(sp);
+ }
+
+ /* Get the current time... */
+ gettimeofday(&cur_tv, NULL);
+
+ /* Discover all the network interfaces. */
+ local_family = AF_INET;
+ discover_interfaces(DISCOVER_RELAY);
+
+ /* Get the IPv6 socket. */
+ local_family = AF_INET6;
+ discover_interfaces(DISCOVER_RUNNING);
+ status = interface_allocate(&if6, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal("interface_allocate: %s", isc_result_totext(status));
+ strcpy(if6->name, "ipv6");
+ interface_snorf(if6, 0);
+ tmp = if6;
+ interface_dereference(&tmp, MDL);
+ if_register6(if6, 0);
+
+ /* Become a daemon... */
+ if (!no_daemon) {
+ int pid;
+ FILE *pf;
+ int pfdesc;
+
+ log_perror = 0;
+
+ if ((pid = fork()) < 0)
+ log_fatal("Can't fork daemon: %m");
+ else if (pid)
+ exit(0);
+
+ if (no_pid_file == ISC_FALSE) {
+ pfdesc = open(path_dhcrelay_pid,
+ O_CREAT | O_TRUNC | O_WRONLY, 0644);
+
+ if (pfdesc < 0) {
+ log_error("Can't create %s: %m",
+ path_dhcrelay_pid);
+ } else {
+ pf = fdopen(pfdesc, "w");
+ if (!pf)
+ log_error("Can't fdopen %s: %m",
+ path_dhcrelay_pid);
+ else {
+ fprintf(pf, "%ld\n",(long)getpid());
+ fclose(pf);
+ }
+ }
+ }
+
+ close(0);
+ close(1);
+ close(2);
+ pid = setsid();
+
+ IGNORE_RET (chdir("/"));
+ }
+
+ /* Set up the packet handler... */
+ bootp_packet_handler = do_relay4to6;
+ dhcpv6_packet_handler = do_relay6to4;
+
+ /* Start dispatching packets and timeouts... */
+ dispatch();
+
+ /* Not reached */
+ return (0);
+}
+
+/* From IPv6 to IPv4 BOOTREQUEST: forward it to all the servers. */
+
+static void
+do_relay6to4(struct interface_info *ip, const char *msg,
+ int len, int from_port, const struct iaddr *from,
+ isc_boolean_t was_unicast) {
+ struct dhcp_packet *packet;
+ struct server_list *sp;
+ unsigned int length;
+
+ packet = (struct dhcp_packet *)msg;
+ length = (unsigned int)len;
+
+ if (packet->hlen > sizeof packet->chaddr) {
+ log_info("Discarding packet with invalid hlen, received on "
+ "%s v6 interface.", ip->name);
+ return;
+ }
+
+ if (packet->op != BOOTREQUEST)
+ return;
+
+ /* only from a CRA */
+ if (packet->giaddr.s_addr)
+ return;
+
+ /* Add relay agent options. If something goes wrong,
+ drop the packet. */
+ if ((length = add_relay_agent_options(ip, packet, length, from)) == 0)
+ return;
+
+ if (packet->hops < max_hop_count)
+ packet->hops = packet->hops + 1;
+ else
+ return;
+
+ for (sp = servers; sp; sp = sp->next) {
+ packet->giaddr.s_addr = sp->src.s_addr;
+ if (send_packet((fallback_interface
+ ? fallback_interface : interfaces),
+ NULL, packet, length, sp->src,
+ &sp->to, NULL) < 0) {
+ ++client_packet_errors;
+ } else {
+ log_debug("Forwarded BOOTREQUEST for %s to %s",
+ print_hw_addr(packet->htype, packet->hlen,
+ packet->chaddr),
+ inet_ntoa(sp->to.sin_addr));
+ ++client_packets_relayed;
+ }
+ }
+
+}
+
+/* From IPv4 to IPv6 BOOTREPLY: forward it to the CRA. */
+
+static void
+do_relay4to6(struct interface_info *ip, struct dhcp_packet *packet,
+ unsigned int length, unsigned int from_port, struct iaddr from,
+ struct hardware *hfrom) {
+ struct in_addr fromin;
+ struct server_list *sp;
+ struct sockaddr_in6 to;
+ struct interface_info *out;
+
+ if (packet->hlen > sizeof packet->chaddr) {
+ log_info("Discarding packet with invalid hlen, received on "
+ "%s v4 interface.", ip->name);
+ return;
+ }
+
+ if (packet->op != BOOTREPLY)
+ return;
+
+ /* Check if it comes from a configured server. */
+ memcpy(&fromin, from.iabuf, sizeof(fromin));
+ for (sp = servers; sp; sp = sp->next)
+ if (fromin.s_addr == sp->to.sin_addr.s_addr)
+ break;
+ if (sp == NULL) {
+ log_info("Discarding packet from unknown server '%s'.",
+ inet_ntoa(fromin));
+ unknown_server++;
+ return;
+ }
+
+ /* Find the interface that corresponds to the giaddr
+ in the packet. */
+ if (packet->giaddr.s_addr) {
+ for (out = interfaces; out; out = out->next) {
+ int i;
+
+ for (i = 0 ; i < out->address_count ; i++ ) {
+ if (out->addresses[i].s_addr ==
+ packet->giaddr.s_addr)
+ i = -1;
+ break;
+ }
+
+ if (i == -1)
+ break;
+ }
+ } else {
+ out = NULL;
+ }
+
+ if (!out) {
+ log_error("Packet to bogus giaddr %s.\n",
+ inet_ntoa(packet->giaddr));
+ ++bogus_giaddr_drops;
+ return;
+ }
+
+ memset(&to, 0, sizeof(to));
+ to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ to.sin6_len = sizeof(to);
+#endif
+ to.sin6_port = remote_port;
+
+ /* Wipe out the agent relay options and, if possible, figure
+ out which IPv6 address to use based on the contents of the
+ option that we put on the request to which the server is
+ replying. */
+ if ((length = strip_relay_agent_options(ip,
+ &to.sin6_addr,
+ packet,
+ length)) == 0)
+ return;
+
+ if (sendto(if6->wfdesc, (unsigned char *)packet, length, 0,
+ (struct sockaddr *)&to, sizeof(to)) < 0) {
+ ++server_packet_errors;
+ } else {
+ char addrbuf[MAX_ADDRESS_STRING_LEN];
+
+ inet_ntop(AF_INET6, &to.sin6_addr, addrbuf,
+ MAX_ADDRESS_STRING_LEN);
+ log_debug("Forwarded BOOTREPLY for %s to %s",
+ print_hw_addr(packet->htype, packet->hlen,
+ packet->chaddr),
+ addrbuf);
+ ++server_packets_relayed;
+ }
+}
+
+/* Strip any Relay Agent Information options from the DHCP packet
+ option buffer. If there is a CRA6ADDR suboption, look up the
+ IPv6 address of the CRA based upon it. */
+
+static int
+strip_relay_agent_options(struct interface_info *in,
+ struct in6_addr *addr,
+ struct dhcp_packet *packet,
+ unsigned length) {
+ int is_dhcp = 0;
+ u_int8_t *op, *nextop, *sp, *max;
+ int good_agent_option = 0;
+ int status;
+
+ /* If there's no cookie, it's a bootp packet, drop it. */
+ if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 4))
+ return (0);
+
+ max = ((u_int8_t *)packet) + length;
+ sp = op = &packet->options[4];
+
+ while (op < max) {
+ switch(*op) {
+ /* Skip padding... */
+ case DHO_PAD:
+ if (sp != op)
+ *sp = *op;
+ ++op;
+ ++sp;
+ continue;
+
+ /* If we see a message type, it's a DHCP packet. */
+ case DHO_DHCP_MESSAGE_TYPE:
+ is_dhcp = 1;
+ goto skip;
+ break;
+
+ /* Quit immediately if we hit an End option. */
+ case DHO_END:
+ if (sp != op)
+ *sp++ = *op++;
+ goto out;
+
+ case DHO_DHCP_AGENT_OPTIONS:
+ /* We shouldn't see a relay agent option in a
+ packet before we've seen the DHCP packet type,
+ but if we do, we have to leave it alone. */
+ if (!is_dhcp)
+ goto skip;
+
+ /* Do not process an agent option if it exceeds the
+ * buffer. Fail this packet.
+ */
+ nextop = op + op[1] + 2;
+ if (nextop > max)
+ return (0);
+
+ status = find_ipv6_by_agent_option(packet, addr,
+ op + 2, op[1]);
+ if (status == -1)
+ return (0);
+ good_agent_option = 1;
+ op = nextop;
+ break;
+
+ skip:
+ /* Skip over other options. */
+ default:
+ /* Fail if processing this option will exceed the
+ * buffer(op[1] is malformed).
+ */
+ nextop = op + op[1] + 2;
+ if (nextop > max)
+ return (0);
+
+ if (sp != op) {
+ memmove(sp, op, op[1] + 2);
+ sp += op[1] + 2;
+ op = nextop;
+ } else
+ op = sp = nextop;
+
+ break;
+ }
+ }
+ out:
+
+ /* If it's not a DHCP packet, drop it. */
+ if (!is_dhcp)
+ return (0);
+
+ /* If none of the agent options we found matched, or if we didn't
+ find any agent options, count this packet as not having any
+ matching agent options, and if we're relying on agent options
+ to determine the IPv6 address, drop the packet. */
+
+ if (!good_agent_option) {
+ ++missing_agent_option;
+ return (0);
+ }
+
+ /* Adjust the length... */
+ if (sp != op) {
+ length = sp - ((u_int8_t *)packet);
+
+ /* Make sure the packet isn't short(this is unlikely,
+ but WTH) */
+ if (length < BOOTP_MIN_LEN) {
+ memset(sp, DHO_PAD, BOOTP_MIN_LEN - length);
+ length = BOOTP_MIN_LEN;
+ }
+ }
+ return (length);
+}
+
+
+/* Find the CRA IPv6 address from the CRA6ADDR suboption, and
+ find an interface that matches the circuit ID specified in the
+ Relay Agent Information option.
+
+ We actually deviate somewhat from the current specification here:
+ if the option buffer is corrupt, we suggest that the caller not
+ respond to this packet. If the circuit ID doesn't match any known
+ interface, we suggest that the caller to drop the packet. Only if
+ we find a circuit ID that matches an existing interface do we tell
+ the caller to go ahead and process the packet. */
+
+static int
+find_ipv6_by_agent_option(struct dhcp_packet *packet,
+ struct in6_addr *addr,
+ u_int8_t *buf, int len) {
+ int i = 0;
+ u_int8_t *circuit_id = 0;
+ unsigned circuit_id_len = 0;
+ unsigned got_cra6addr = 0;
+ struct interface_info *ip;
+
+ while (i < len) {
+ /* If the next agent option overflows the end of the
+ packet, the agent option buffer is corrupt. */
+ if (i + 1 == len ||
+ i + buf[i + 1] + 2 > len) {
+ ++corrupt_agent_options;
+ return (-1);
+ }
+ switch(buf[i]) {
+ /* Remember where the circuit ID is... */
+ case RAI_CIRCUIT_ID:
+ circuit_id = &buf[i + 2];
+ circuit_id_len = buf[i + 1];
+ i += circuit_id_len + 2;
+ break;
+
+ /* Require one cra6addr. */
+ case RAI_CRA6ADDR:
+ if (buf[i + 1] != 16) {
+ ++corrupt_agent_options;
+ return (-1);
+ }
+ memcpy(addr, buf + i + 2, 16);
+ ++got_cra6addr;
+ i += buf[i + 1] + 2;
+ break;
+
+ default:
+ i += buf[i + 1] + 2;
+ break;
+ }
+ }
+
+ /* If there's no cra6addr, it is bad. */
+ if (got_cra6addr != 1) {
+ ++missing_cra6addr;
+ return (-1);
+ }
+
+ /* If there's no circuit ID, it's not really ours, tell the caller
+ it's no good. */
+ if (!circuit_id) {
+ if (add_agent_options) {
+ ++missing_circuit_id;
+ return (-1);
+ }
+ return (1);
+ }
+
+ /* Scan the interface list looking for an interface whose
+ name matches the one specified in circuit_id. */
+
+ for (ip = interfaces; ip; ip = ip->next) {
+ if (ip->circuit_id &&
+ ip->circuit_id_len == circuit_id_len &&
+ !memcmp(ip->circuit_id, circuit_id, circuit_id_len))
+ return (1);
+ }
+
+ /* If we didn't get a match, the circuit ID was bogus. */
+ ++bad_circuit_id;
+ return (-1);
+}
+
+/*
+ * Examine a packet to see if it's a candidate to have a Relay
+ * Agent Information option tacked onto its tail. If it is, tack
+ * the option on.
+ */
+static int
+add_relay_agent_options(struct interface_info *ip, struct dhcp_packet *packet,
+ unsigned length, const struct iaddr *addr) {
+ int is_dhcp = 0, mms;
+ unsigned optlen;
+ u_int8_t *op, *nextop, *sp, *max, *end_pad = NULL;
+
+ /* If there's no cookie, it's a bootp packet, so drop it. */
+ if (memcmp(packet->options, DHCP_OPTIONS_COOKIE, 4))
+ return (0);
+
+ max = ((u_int8_t *)packet) + dhcp_max_agent_option_packet_length;
+
+ /* Commence processing after the cookie. */
+ sp = op = &packet->options[4];
+
+ while (op < max) {
+ switch(*op) {
+ /* Skip padding... */
+ case DHO_PAD:
+ /* Remember the first pad byte so we can commandeer
+ * padded space.
+ *
+ * XXX: Is this really a good idea? Sure, we can
+ * seemingly reduce the packet while we're looking,
+ * but if the packet was signed by the client then
+ * this padding is part of the checksum(RFC3118),
+ * and its nonpresence would break authentication.
+ */
+ if (end_pad == NULL)
+ end_pad = sp;
+
+ if (sp != op)
+ *sp++ = *op++;
+ else
+ sp = ++op;
+
+ continue;
+
+ /* If we see a message type, it's a DHCP packet. */
+ case DHO_DHCP_MESSAGE_TYPE:
+ is_dhcp = 1;
+ goto skip;
+
+ /*
+ * If there's a maximum message size option, we
+ * should pay attention to it
+ */
+ case DHO_DHCP_MAX_MESSAGE_SIZE:
+ mms = ntohs(*(op + 2));
+ if (mms < dhcp_max_agent_option_packet_length &&
+ mms >= DHCP_MTU_MIN)
+ max = ((u_int8_t *)packet) + mms;
+ goto skip;
+
+ /* Quit immediately if we hit an End option. */
+ case DHO_END:
+ goto out;
+
+ case DHO_DHCP_AGENT_OPTIONS:
+ /* We shouldn't see a relay agent option in a
+ packet before we've seen the DHCP packet type,
+ but if we do, we have to leave it alone. */
+ if (!is_dhcp)
+ goto skip;
+
+ end_pad = NULL;
+
+ /* There's already a Relay Agent Information option
+ in this packet. Drop it. */
+
+ return (0);
+
+ skip:
+ /* Skip over other options. */
+ default:
+ /* Fail if processing this option will exceed the
+ * buffer(op[1] is malformed).
+ */
+ nextop = op + op[1] + 2;
+ if (nextop > max)
+ return (0);
+
+ end_pad = NULL;
+
+ if (sp != op) {
+ memmove(sp, op, op[1] + 2);
+ sp += op[1] + 2;
+ op = nextop;
+ } else
+ op = sp = nextop;
+
+ break;
+ }
+ }
+ out:
+
+ /* If it's not a DHCP packet, drop it. */
+ if (!is_dhcp)
+ return (0);
+
+ /* If the packet was padded out, we can store the agent option
+ at the beginning of the padding. */
+
+ if (end_pad != NULL)
+ sp = end_pad;
+
+ /* Remember where the end of the packet was after parsing
+ it. */
+ op = sp;
+
+ /* Count the cra6addr (RAI_CRA6ADDR + len + IPv6 Address) */
+ optlen = 18;
+
+ /* Jump further if we want only the cra6addr. */
+ if (!add_agent_options)
+ goto mandatory_only;
+
+ /* Sanity check. Had better not ever happen. */
+ if ((ip->circuit_id_len > 255) ||(ip->circuit_id_len < 1))
+ log_fatal("Circuit ID length %d out of range [1-255] on "
+ "%s\n", ip->circuit_id_len, ip->name);
+ optlen += ip->circuit_id_len + 2; /* RAI_CIRCUIT_ID + len */
+
+ if (ip->remote_id) {
+ if (ip->remote_id_len > 255 || ip->remote_id_len < 1)
+ log_fatal("Remote ID length %d out of range [1-255] "
+ "on %s\n", ip->circuit_id_len, ip->name);
+ optlen += ip->remote_id_len + 2; /* RAI_REMOTE_ID + len */
+ }
+
+ mandatory_only:
+ /* We do not support relay option fragmenting(multiple options to
+ * support an option data exceeding 255 bytes).
+ */
+ if ((optlen < 3) ||(optlen > 255))
+ log_fatal("Total agent option length(%u) out of range "
+ "[3 - 255] on %s\n", optlen, ip->name);
+
+ /*
+ * Is there room for the option, its code+len, and DHO_END?
+ * If not, forward without adding the option.
+ */
+ if (max - sp >= optlen + 3) {
+ log_debug("Adding %d-byte relay agent option", optlen + 3);
+
+ /* Okay, cons up *our* Relay Agent Information option. */
+ *sp++ = DHO_DHCP_AGENT_OPTIONS;
+ *sp++ = optlen;
+
+ /* Copy in the cra6addr... */
+ *sp++ = RAI_CRA6ADDR;
+ *sp++ = 16;
+ memcpy(sp, addr->iabuf, 16);
+ sp += 16;
+
+ /* Copy in the circuit id... */
+ if (add_agent_options) {
+ *sp++ = RAI_CIRCUIT_ID;
+ *sp++ = ip->circuit_id_len;
+ memcpy(sp, ip->circuit_id, ip->circuit_id_len);
+ sp += ip->circuit_id_len;
+
+ /* Copy in remote ID... */
+ if (ip->remote_id) {
+ *sp++ = RAI_REMOTE_ID;
+ *sp++ = ip->remote_id_len;
+ memcpy(sp, ip->remote_id, ip->remote_id_len);
+ sp += ip->remote_id_len;
+ }
+ }
+ } else {
+ ++agent_option_errors;
+ log_error("No room in packet (used %d of %d) "
+ "for %d-byte relay agent option: dropped",
+ (int) (sp - ((u_int8_t *) packet)),
+ (int) (max - ((u_int8_t *) packet)),
+ optlen + 3);
+ return (0);
+ }
+
+ /*
+ * Deposit an END option unless the packet is full (shouldn't
+ * be possible).
+ */
+ if (sp < max)
+ *sp++ = DHO_END;
+
+ /* Recalculate total packet length. */
+ length = sp - ((u_int8_t *)packet);
+
+ /* Make sure the packet isn't short(this is unlikely, but WTH) */
+ if (length < BOOTP_MIN_LEN) {
+ memset(sp, DHO_PAD, BOOTP_MIN_LEN - length);
+ return (BOOTP_MIN_LEN);
+ }
+
+ return (length);
+}
+
+/* Find the source address to use with a server. */
+
+static void
+set_server_src(struct server_list *sp) {
+ int sock;
+ socklen_t len;
+ struct sockaddr_in src;
+
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0)
+ log_fatal("set_server_src: socket: %m");
+ len = sizeof(src);
+ if (connect(sock, (struct sockaddr *)&sp->to, len) < 0)
+ log_fatal("set_server_src: connect: %m");
+ memset(&src, 0, len);
+ if (getsockname(sock, (struct sockaddr *)&src, &len) < 0)
+ log_fatal("set_server_src: getsockname: %m");
+ (void)close(sock);
+ sp->src.s_addr = src.sin_addr.s_addr;
+}
+
+/* Stub routines needed for linking with DHCP libraries. */
+void
+bootp(struct packet *packet) {
+ return;
+}
+
+void
+dhcp(struct packet *packet) {
+ return;
+}
+
+void
+dhcp_tsv(struct packet *packet) {
+ return;
+}
+
+void
+classify(struct packet *p, struct class *c) {
+ return;
+}
+
+int
+check_collection(struct packet *p, struct lease *l, struct collection *c) {
+ return 0;
+}
+
+isc_result_t
+find_class(struct class **class, const char *c1, const char *c2, int i) {
+ return ISC_R_NOTFOUND;
+}
+
+int
+parse_allow_deny(struct option_cache **oc, struct parse *p, int i) {
+ return 0;
+}
+
+isc_result_t
+dhcp_set_control_state(control_object_state_t oldstate,
+ control_object_state_t newstate) {
+ return ISC_R_SUCCESS;
+}
+
+#else
+
+int
+main(int argc, char **argv) {
+ log_error("Required DHCPv6 support was disabled.");
+ return -1;
+}
+#endif /* DHCPv6 */
diff --git a/server/Makefile.am b/server/Makefile.am
index cdfaf471..15400593 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -4,7 +4,7 @@ dist_sysconf_DATA = dhcpd.conf
sbin_PROGRAMS = dhcpd
dhcpd_SOURCES = dhcpd.c dhcp.c bootp.c confpars.c db.c class.c failover.c \
omapi.c mdb.c stables.c salloc.c ddns.c dhcpleasequery.c \
- dhcpv6.c mdb6.c ldap.c ldap_casa.c
+ dhcpv6.c mdb6.c ldap.c ldap_casa.c tsv.c
dhcpd_CFLAGS = $(LDAP_CFLAGS)
dhcpd_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
diff --git a/server/Makefile.dist b/server/Makefile.dist
index c12f0948..933390d4 100644
--- a/server/Makefile.dist
+++ b/server/Makefile.dist
@@ -26,10 +26,10 @@ CATMANPAGES = dhcpd.cat8 dhcpd.conf.cat5 dhcpd.leases.cat5
SEDMANPAGES = dhcpd.man8 dhcpd.conf.man5 dhcpd.leases.man5
SRCS = dhcpd.c dhcp.c bootp.c confpars.c db.c class.c failover.c \
omapi.c mdb.c stables.c salloc.c ddns.c dhcpleasequery.c dhcpv6.c \
- mdb6.c
+ mdb6.c tsv.c
OBJS = dhcpd.o dhcp.o bootp.o confpars.o db.o class.o failover.o \
omapi.o mdb.o stables.o salloc.o ddns.o dhcpleasequery.o dhcpv6.o \
- mdb6.o
+ mdb6.o tsv.o
PROG = dhcpd testmdb6
MAN = dhcpd.8 dhcpd.conf.5 dhcpd.leases.5
diff --git a/server/Makefile.in b/server/Makefile.in
index 3b0426b6..277a2543 100644
--- a/server/Makefile.in
+++ b/server/Makefile.in
@@ -54,7 +54,7 @@ am_dhcpd_OBJECTS = dhcpd-dhcpd.$(OBJEXT) dhcpd-dhcp.$(OBJEXT) \
dhcpd-salloc.$(OBJEXT) dhcpd-ddns.$(OBJEXT) \
dhcpd-dhcpleasequery.$(OBJEXT) dhcpd-dhcpv6.$(OBJEXT) \
dhcpd-mdb6.$(OBJEXT) dhcpd-ldap.$(OBJEXT) \
- dhcpd-ldap_casa.$(OBJEXT)
+ dhcpd-ldap_casa.$(OBJEXT) dhcpd-tsv.$(OBJEXT)
dhcpd_OBJECTS = $(am_dhcpd_OBJECTS)
dhcpd_DEPENDENCIES = ../common/libdhcp.a ../omapip/libomapi.a \
../dhcpctl/libdhcpctl.a ../bind/lib/libdns.a \
@@ -180,7 +180,7 @@ AM_CPPFLAGS = -I.. -DLOCALSTATEDIR='"@localstatedir@"'
dist_sysconf_DATA = dhcpd.conf
dhcpd_SOURCES = dhcpd.c dhcp.c bootp.c confpars.c db.c class.c failover.c \
omapi.c mdb.c stables.c salloc.c ddns.c dhcpleasequery.c \
- dhcpv6.c mdb6.c ldap.c ldap_casa.c
+ dhcpv6.c mdb6.c ldap.c ldap_casa.c tsv.c
dhcpd_CFLAGS = $(LDAP_CFLAGS)
dhcpd_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
@@ -272,6 +272,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-omapi.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-salloc.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-stables.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcpd-tsv.Po@am__quote@
.c.o:
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@@ -524,6 +525,20 @@ dhcpd-ldap_casa.obj: ldap_casa.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ldap_casa.c' object='dhcpd-ldap_casa.obj' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-ldap_casa.obj `if test -f 'ldap_casa.c'; then $(CYGPATH_W) 'ldap_casa.c'; else $(CYGPATH_W) '$(srcdir)/ldap_casa.c'; fi`
+
+dhcpd-tsv.o: tsv.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-tsv.o -MD -MP -MF $(DEPDIR)/dhcpd-tsv.Tpo -c -o dhcpd-tsv.o `test -f 'tsv.c' || echo '$(srcdir)/'`tsv.c
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/dhcpd-tsv.Tpo $(DEPDIR)/dhcpd-tsv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tsv.c' object='dhcpd-tsv.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-tsv.o `test -f 'tsv.c' || echo '$(srcdir)/'`tsv.c
+
+dhcpd-tsv.obj: tsv.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -MT dhcpd-tsv.obj -MD -MP -MF $(DEPDIR)/dhcpd-tsv.Tpo -c -o dhcpd-tsv.obj `if test -f 'tsv.c'; then $(CYGPATH_W) 'tsv.c'; else $(CYGPATH_W) '$(srcdir)/tsv.c'; fi`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/dhcpd-tsv.Tpo $(DEPDIR)/dhcpd-tsv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tsv.c' object='dhcpd-tsv.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dhcpd_CFLAGS) $(CFLAGS) -c -o dhcpd-tsv.obj `if test -f 'tsv.c'; then $(CYGPATH_W) 'tsv.c'; else $(CYGPATH_W) '$(srcdir)/tsv.c'; fi`
install-man5: $(man5_MANS) $(man_MANS)
@$(NORMAL_INSTALL)
test -z "$(man5dir)" || $(MKDIR_P) "$(DESTDIR)$(man5dir)"
diff --git a/server/confpars.c b/server/confpars.c
index 1c9c4802..d246e64f 100644
--- a/server/confpars.c
+++ b/server/confpars.c
@@ -2453,7 +2453,8 @@ void parse_shared_net_declaration (cfile, group)
static int
common_subnet_parsing(struct parse *cfile,
struct shared_network *share,
- struct subnet *subnet) {
+ struct subnet *subnet,
+ int af) {
enum dhcp_token token;
struct subnet *t, *u;
const char *val;
@@ -2482,6 +2483,11 @@ common_subnet_parsing(struct parse *cfile,
if (!parse_semi(cfile))
break;
continue;
+ } else if (local_family != af) {
+ parse_warn(cfile, "not empty cross IP version subnet "
+ "declaration.");
+ skip_to_semi(cfile);
+ break;
}
declaration = parse_statement(cfile, subnet->group,
SUBNET_DECL,
@@ -2597,7 +2603,7 @@ void parse_subnet_declaration (cfile, share)
return;
}
- common_subnet_parsing(cfile, share, subnet);
+ common_subnet_parsing(cfile, share, subnet, AF_INET);
}
/* subnet6-declaration :==
@@ -2606,7 +2612,7 @@ void parse_subnet_declaration (cfile, share)
void
parse_subnet6_declaration(struct parse *cfile, struct shared_network *share) {
#if !defined(DHCPv6)
- parse_warn(cfile, "No DHCPv6 support.");
+ parse_warn(cfile, "No IPv6 support.");
skip_to_semi(cfile);
#else /* defined(DHCPv6) */
struct subnet *subnet;
@@ -2619,12 +2625,14 @@ parse_subnet6_declaration(struct parse *cfile, struct shared_network *share) {
0xF0, 0xF8, 0xFC, 0xFE };
struct iaddr iaddr;
+#if 0
if (local_family != AF_INET6) {
parse_warn(cfile, "subnet6 statement is only supported "
"in DHCPv6 mode.");
skip_to_semi(cfile);
return;
}
+#endif
subnet = NULL;
status = subnet_allocate(&subnet, MDL);
@@ -2708,9 +2716,7 @@ parse_subnet6_declaration(struct parse *cfile, struct shared_network *share) {
return;
}
- if (!common_subnet_parsing(cfile, share, subnet)) {
- return;
- }
+ common_subnet_parsing(cfile, share, subnet, AF_INET6);
#endif /* defined(DHCPv6) */
}
diff --git a/server/db.c b/server/db.c
index 5be1684a..520eb62f 100644
--- a/server/db.c
+++ b/server/db.c
@@ -999,6 +999,10 @@ int commit_leases ()
}
/* send out all deferred ACKs now */
+#ifdef DHCPv6
+ if (run_as_tsv)
+ flush_ackqueue_tsv(NULL);
+#endif
flush_ackqueue(NULL);
/* If we haven't rewritten the lease database in over an
diff --git a/server/dhcp.c b/server/dhcp.c
index 58072c93..03815948 100644
--- a/server/dhcp.c
+++ b/server/dhcp.c
@@ -4360,6 +4360,7 @@ int locate_network (packet)
struct data_string data;
struct subnet *subnet = (struct subnet *)0;
struct option_cache *oc;
+ int sso = 0;
/* See if there's a Relay Agent Link Selection Option, or a
* Subnet Selection Option. The Link-Select and Subnet-Select
@@ -4370,10 +4371,44 @@ int locate_network (packet)
RAI_LINK_SELECT)) == NULL)
oc = lookup_option(&dhcp_universe, packet->options,
DHO_SUBNET_SELECTION);
+ if (oc)
+ sso = 1;
+
+#ifdef DHCPv6
+ /* See if there is a Relay Agent CRA6ADDR Option. */
+ if (!sso)
+ oc = lookup_option(&agent_universe, packet->options,
+ RAI_CRA6ADDR);
+ if (!sso && oc) {
+ memset (&data, 0, sizeof data);
+ if (!evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ return 0;
+ }
+ if (data.len != 16) {
+ return 0;
+ }
+ ia.len = 16;
+ memcpy (ia.iabuf, data.data, 16);
+ data_string_forget (&data, MDL);
+
+ /* Get the subnet of this IPv6 address. */
+ if (find_subnet (&subnet, ia, MDL)) {
+ shared_network_reference (&packet -> shared_network,
+ subnet -> shared_network,
+ MDL);
+ subnet_dereference (&subnet, MDL);
+ return 1;
+ }
+ }
+#endif
/* If there's no SSO and no giaddr, then use the shared_network
from the interface, if there is one. If not, fail. */
- if (!oc && !packet -> raw -> giaddr.s_addr) {
+ if (!sso && !packet -> raw -> giaddr.s_addr) {
if (packet -> interface -> shared_network) {
shared_network_reference
(&packet -> shared_network,
@@ -4386,7 +4421,7 @@ int locate_network (packet)
/* If there's an option indicating link connection, and it's valid,
* use it to figure out the subnet. If it's not valid, fail.
*/
- if (oc) {
+ if (sso) {
memset (&data, 0, sizeof data);
if (!evaluate_option_cache (&data, packet, (struct lease *)0,
(struct client_state *)0,
diff --git a/server/dhcpd.8 b/server/dhcpd.8
index 8647169a..aa8bed65 100644
--- a/server/dhcpd.8
+++ b/server/dhcpd.8
@@ -1,6 +1,6 @@
.\" dhcpd.8
.\"
-.\" Copyright (c) 2009-2011 by Internet Systems Consortium, Inc. ("ISC")
+.\" Copyright (c) 2009-2012 by Internet Systems Consortium, Inc. ("ISC")
.\" Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC")
.\" Copyright (c) 1996-2003 by Internet Software Consortium
.\"
@@ -59,6 +59,9 @@ dhcpd - Dynamic Host Configuration Protocol Server
.B -6
]
[
+.B -tsv
+]
+[
.B -s
.I server
]
@@ -196,6 +199,12 @@ Run as a DHCP server. This is the default and cannot be combined with
.BI \-6
Run as a DHCPv6 server. This cannot be combined with \fB\-4\fR.
.TP
+.BI \-tsv
+Run as a DHCPv4 IPv6-transport server. This cannot be combined
+with \fB\-6\fR. When combined with \fB\-4\fR, the DHCPv4 server
+accepts both queries transported by IPv6 and standard queries
+over IPv4.
+.TP
.BI \-p \ port
The udp port number on which
.B dhcpd
diff --git a/server/dhcpd.c b/server/dhcpd.c
index dd48e46a..ee88ffdb 100644
--- a/server/dhcpd.c
+++ b/server/dhcpd.c
@@ -3,7 +3,7 @@
DHCP Server Daemon. */
/*
- * Copyright (c) 2004-2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2012 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1996-2003 by Internet Software Consortium
*
* Permission to use, copy, modify, and distribute this software for any
@@ -381,6 +381,8 @@ main(int argc, char **argv) {
}
local_family = AF_INET;
local_family_set = 1;
+ if (run_as_tsv)
+ run_as_tsv = 2;
} else if (!strcmp(argv[i], "-6")) {
if (local_family_set && (local_family != AF_INET6)) {
log_fatal("Server cannot run in both IPv4 and "
@@ -388,6 +390,19 @@ main(int argc, char **argv) {
}
local_family = AF_INET6;
local_family_set = 1;
+ } else if (!strcmp(argv[i], "-tsv")) {
+ if (local_family_set && (local_family != AF_INET)) {
+ log_fatal("The IPv6-transport server mode is "
+ "implies DHCPv4: server cannot run "
+ "in both IPv4 and IPv6 mode at "
+ "the same time.");
+ }
+ if (local_family_set)
+ run_as_tsv = 2;
+ else
+ run_as_tsv = 1;
+ local_family = AF_INET;
+ local_family_set = 1;
#endif /* DHCPv6 */
} else if (!strcmp (argv [i], "--version")) {
log_info("isc-dhcpd-%s", PACKAGE_VERSION);
@@ -582,6 +597,10 @@ main(int argc, char **argv) {
log_fatal("You can only specify address to send "
"replies to when running an IPv4 server.");
}
+ if (run_as_tsv) {
+ log_fatal("-s server is incompatible with "
+ "the IPv6-transport server mode(-tsv).");
+ }
if (!inet_aton (server, &limited_broadcast)) {
struct hostent *he;
he = gethostbyname (server);
@@ -623,7 +642,10 @@ main(int argc, char **argv) {
dhcp_interface_setup_hook = dhcpd_interface_setup_hook;
bootp_packet_handler = do_packet;
#ifdef DHCPv6
- dhcpv6_packet_handler = do_packet6;
+ if (!run_as_tsv)
+ dhcpv6_packet_handler = do_packet6;
+ else
+ dhcpv6_packet_handler = do_packet_tsv;
#endif /* DHCPv6 */
#if defined (NSUPDATE)
@@ -705,7 +727,19 @@ main(int argc, char **argv) {
exit (0);
/* Discover all the network interfaces and initialize them. */
- discover_interfaces(DISCOVER_SERVER);
+#ifdef DHCPv6
+ if (run_as_tsv > 1) {
+ discover_interfaces(DISCOVER_SERVER);
+ local_family = AF_INET6;
+ discover_interfaces(DISCOVER_SERVER);
+ local_family = AF_INET;
+ } else if (run_as_tsv) {
+ local_family = AF_INET6;
+ discover_interfaces(DISCOVER_SERVER);
+ local_family = AF_INET;
+ } else
+#endif
+ discover_interfaces(DISCOVER_SERVER);
#ifdef DHCPv6
/*
@@ -955,6 +989,25 @@ void postconf_initialization (int quiet)
data_string_forget (&db, MDL);
path_dhcpd_pid = s;
}
+ }
+
+ if ((local_family == AF_INET6) || run_as_tsv) {
+
+ oc = lookup_option (&server_universe, options,
+ SV_LOCAL_ADDRESS6);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 16) {
+ memcpy (&local_address6, db.data, 16);
+ } else
+ log_fatal ("invalid local address "
+ "data length");
+ data_string_forget (&db, MDL);
+ }
}
#endif /* DHCPv6 */
@@ -1019,32 +1072,38 @@ void postconf_initialization (int quiet)
data_string_forget (&db, MDL);
}
- oc = lookup_option (&server_universe, options,
- SV_LIMITED_BROADCAST_ADDRESS);
- if (oc &&
- evaluate_option_cache (&db, (struct packet *)0,
- (struct lease *)0, (struct client_state *)0,
- options, (struct option_state *)0,
- &global_scope, oc, MDL)) {
- if (db.len == 4) {
- memcpy (&limited_broadcast, db.data, 4);
- } else
- log_fatal ("invalid broadcast address data length");
- data_string_forget (&db, MDL);
- }
+ if ((local_family == AF_INET) && (run_as_tsv != 1)) {
+ oc = lookup_option (&server_universe, options,
+ SV_LIMITED_BROADCAST_ADDRESS);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 4) {
+ memcpy (&limited_broadcast, db.data, 4);
+ } else
+ log_fatal ("invalid broadcast address "
+ "data length");
+ data_string_forget (&db, MDL);
+ }
- oc = lookup_option (&server_universe, options,
- SV_LOCAL_ADDRESS);
- if (oc &&
- evaluate_option_cache (&db, (struct packet *)0,
- (struct lease *)0, (struct client_state *)0,
- options, (struct option_state *)0,
- &global_scope, oc, MDL)) {
- if (db.len == 4) {
- memcpy (&local_address, db.data, 4);
- } else
- log_fatal ("invalid local address data length");
- data_string_forget (&db, MDL);
+ oc = lookup_option (&server_universe, options,
+ SV_LOCAL_ADDRESS);
+ if (oc &&
+ evaluate_option_cache (&db, (struct packet *)0,
+ (struct lease *)0,
+ (struct client_state *)0,
+ options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ if (db.len == 4) {
+ memcpy (&local_address, db.data, 4);
+ } else
+ log_fatal ("invalid local address "
+ "data length");
+ data_string_forget (&db, MDL);
+ }
}
oc = lookup_option (&server_universe, options, SV_DDNS_UPDATE_STYLE);
@@ -1209,7 +1268,8 @@ usage(void) {
log_fatal("Usage: dhcpd [-p <UDP port #>] [-f] [-d] [-q] [-t|-T]\n"
#ifdef DHCPv6
- " [-4|-6] [-cf config-file] [-lf lease-file]\n"
+ " [-4|-6|-tsv] [-cf config-file]"
+ " [-lf lease-file]\n"
#else /* !DHCPv6 */
" [-cf config-file] [-lf lease-file]\n"
#endif /* DHCPv6 */
@@ -1284,7 +1344,12 @@ void lease_ping_timeout (vlp)
#endif
--outstanding_pings;
- dhcp_reply (lp);
+#ifdef DHCPv6
+ if (run_as_tsv && lp->state && (lp->state->from.len == 16))
+ dhcp_reply_tsv (lp);
+ else
+#endif
+ dhcp_reply (lp);
#if defined (DEBUG_MEMORY_LEAKAGE)
log_info ("generation %ld: %ld new, %ld outstanding, %ld long-term",
@@ -1343,7 +1408,9 @@ int dhcpd_interface_setup_hook (struct interface_info *ip, struct iaddr *ia)
if (!share -> interface) {
interface_reference (&share -> interface, ip, MDL);
- } else if (share -> interface != ip) {
+ } else if ((share -> interface != ip) &&
+ (strcmp (share -> interface -> name,
+ ip -> name))) {
log_error ("Multiple interfaces match the %s: %s %s",
"same shared network",
share -> interface -> name, ip -> name);
diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5
index 009e558a..eb2ba9df 100644
--- a/server/dhcpd.conf.5
+++ b/server/dhcpd.conf.5
@@ -1540,6 +1540,11 @@ It may also be used to provide subnet-specific parameters and to
specify what addresses may be dynamically allocated to clients booting
on that subnet.
.PP
+It can be used too with IPv6-Transport Relay Agents to match CRA6ADDR
+Relay Agent options. In this case, there must be no \fIparameters\fR
+nor \fIdeclarations\fR, and it should be part of a \fIshared-network\fR
+declaration.
+.PP
The
.I subnet6-number
should be an IPv6 network identifier, specified as ip6-address/bits.
@@ -2472,6 +2477,18 @@ time.
.RE
.PP
The
+.I local-address6
+statement
+.RS 0.25i
+.PP
+.B local-address6 \fIaddress\fB;\fR
+.PP
+This statement causes the DHCP server to listen for DHCP requests sent
+to the specified IPv6 \fIaddress\fR, rather than requests sent to multicast
+addresses.
+.RE
+.PP
+The
.I log-facility
statement
.RS 0.25i
diff --git a/server/dhcpleasequery.c b/server/dhcpleasequery.c
index 09913c24..abc68736 100644
--- a/server/dhcpleasequery.c
+++ b/server/dhcpleasequery.c
@@ -703,6 +703,547 @@ dhcpleasequery(struct packet *packet, int ms_nulltp) {
}
#ifdef DHCPv6
+void
+dhcpleasequery_tsv(struct packet *packet, int ms_nulltp) {
+ char msgbuf[256];
+ char dbg_info[128];
+ struct iaddr cip;
+ struct data_string uid;
+ struct hardware h;
+ struct lease *tmp_lease;
+ struct lease *lease;
+ int want_associated_ip;
+ int assoc_ip_cnt;
+ u_int32_t assoc_ips[40]; /* XXXSK: arbitrary maximum number of IPs */
+ const int nassoc_ips = sizeof(assoc_ips) / sizeof(assoc_ips[0]);
+
+ unsigned char dhcpMsgType;
+ const char *dhcp_msg_type_name;
+ struct subnet *subnet;
+ struct group *relay_group;
+ struct option_state *options;
+ struct option_cache *oc;
+ int allow_leasequery;
+ int ignorep;
+ u_int32_t lease_duration;
+ u_int32_t time_renewal;
+ u_int32_t time_rebinding;
+ u_int32_t time_expiry;
+ u_int32_t client_last_transaction_time;
+ struct sockaddr_in6 to;
+ struct data_string prl;
+ struct data_string *prl_ptr;
+ int i;
+
+ /* INSIST(packet != NULL); */
+
+ /*
+ * Prepare log information.
+ */
+ snprintf(msgbuf, sizeof(msgbuf),
+ "DHCPLEASEQUERY from %s", piaddr(packet->client_addr));
+
+ subnet = NULL;
+ find_subnet(&subnet, packet->client_addr, MDL);
+ if (subnet != NULL)
+ relay_group = subnet->group;
+ else
+ relay_group = root_group;
+
+ subnet_dereference(&subnet, MDL);
+
+ options = NULL;
+ if (!option_state_allocate(&options, MDL)) {
+ log_error("No memory for option state.");
+ log_info("%s: out of memory, no reply sent", msgbuf);
+ return;
+ }
+
+ execute_statements_in_scope(NULL,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ relay_group,
+ NULL);
+
+ for (i=packet->class_count-1; i>=0; i--) {
+ execute_statements_in_scope(NULL,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ packet->classes[i]->group,
+ relay_group);
+ }
+
+ /*
+ * Because LEASEQUERY has some privacy concerns, default to deny.
+ */
+ allow_leasequery = 0;
+
+ /*
+ * See if we are authorized to do LEASEQUERY.
+ */
+ oc = lookup_option(&server_universe, options, SV_LEASEQUERY);
+ if (oc != NULL) {
+ allow_leasequery = evaluate_boolean_option_cache(&ignorep,
+ packet, NULL, NULL, packet->options,
+ options, &global_scope, oc, MDL);
+ }
+
+ if (!allow_leasequery) {
+ log_info("%s: LEASEQUERY not allowed, query ignored", msgbuf);
+ option_state_dereference(&options, MDL);
+ return;
+ }
+
+
+ /*
+ * Copy out the client IP address.
+ */
+ cip.len = sizeof(packet->raw->ciaddr);
+ memcpy(cip.iabuf, &packet->raw->ciaddr, sizeof(packet->raw->ciaddr));
+
+ /*
+ * If the client IP address is valid (not all zero), then we
+ * are looking for information about that IP address.
+ */
+ assoc_ip_cnt = 0;
+ lease = tmp_lease = NULL;
+ if (memcmp(cip.iabuf, "\0\0\0", 4)) {
+
+ want_associated_ip = 0;
+
+ snprintf(dbg_info, sizeof(dbg_info), "IP %s", piaddr(cip));
+ find_lease_by_ip_addr(&lease, cip, MDL);
+
+
+ } else {
+
+ want_associated_ip = 1;
+
+ /*
+ * If the client IP address is all zero, then we will
+ * either look up by the client identifier (if we have
+ * one), or by the MAC address.
+ */
+
+ memset(&uid, 0, sizeof(uid));
+ if (get_option(&uid,
+ &dhcp_universe,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ NULL,
+ packet->options,
+ &global_scope,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ MDL)) {
+
+ snprintf(dbg_info,
+ sizeof(dbg_info),
+ "client-id %s",
+ print_hex_1(uid.len, uid.data, 60));
+
+ find_lease_by_uid(&tmp_lease, uid.data, uid.len, MDL);
+ data_string_forget(&uid, MDL);
+ get_newest_lease(&lease, tmp_lease, next_uid);
+ assoc_ip_cnt = get_associated_ips(tmp_lease,
+ next_uid,
+ lease,
+ assoc_ips,
+ nassoc_ips);
+
+ } else {
+
+ if (packet->raw->hlen+1 > sizeof(h.hbuf)) {
+ log_info("%s: hardware length too long, "
+ "no reply sent", msgbuf);
+ option_state_dereference(&options, MDL);
+ return;
+ }
+
+ h.hlen = packet->raw->hlen + 1;
+ h.hbuf[0] = packet->raw->htype;
+ memcpy(&h.hbuf[1],
+ packet->raw->chaddr,
+ packet->raw->hlen);
+
+ snprintf(dbg_info,
+ sizeof(dbg_info),
+ "MAC address %s",
+ print_hw_addr(h.hbuf[0],
+ h.hlen - 1,
+ &h.hbuf[1]));
+
+ find_lease_by_hw_addr(&tmp_lease, h.hbuf, h.hlen, MDL);
+ get_newest_lease(&lease, tmp_lease, next_hw);
+ assoc_ip_cnt = get_associated_ips(tmp_lease,
+ next_hw,
+ lease,
+ assoc_ips,
+ nassoc_ips);
+
+ }
+
+ lease_dereference(&tmp_lease, MDL);
+
+ if (lease != NULL) {
+ memcpy(&packet->raw->ciaddr,
+ lease->ip_addr.iabuf,
+ sizeof(packet->raw->ciaddr));
+ }
+
+ /*
+ * Log if we have too many IP addresses associated
+ * with this client.
+ */
+ if (want_associated_ip && (assoc_ip_cnt > nassoc_ips)) {
+ log_info("%d IP addresses associated with %s, "
+ "only %d sent in reply.",
+ assoc_ip_cnt, dbg_info, nassoc_ips);
+ }
+ }
+
+ /*
+ * We now know the query target too, so can report this in
+ * our log message.
+ */
+ snprintf(msgbuf, sizeof(msgbuf),
+ "DHCPLEASEQUERY from %s for %s",
+ piaddr(packet->client_addr), dbg_info);
+
+ /*
+ * Figure our our return type.
+ */
+ if (lease == NULL) {
+ dhcpMsgType = DHCPLEASEUNKNOWN;
+ dhcp_msg_type_name = "DHCPLEASEUNKNOWN";
+ } else {
+ if (lease->binding_state == FTS_ACTIVE) {
+ dhcpMsgType = DHCPLEASEACTIVE;
+ dhcp_msg_type_name = "DHCPLEASEACTIVE";
+ } else {
+ dhcpMsgType = DHCPLEASEUNASSIGNED;
+ dhcp_msg_type_name = "DHCPLEASEUNASSIGNED";
+ }
+ }
+
+ /*
+ * Set options that only make sense if we have an active lease.
+ */
+
+ if (dhcpMsgType == DHCPLEASEACTIVE)
+ {
+ /*
+ * RFC 4388 uses the PRL to request options for the agent to
+ * receive that are "about" the client. It is confusing
+ * because in some cases it wants to know what was sent to
+ * the client (lease times, adjusted), and in others it wants
+ * to know information the client sent. You're supposed to
+ * know this on a case-by-case basis.
+ *
+ * "Name servers", "domain name", and the like from the relay
+ * agent's scope seems less than useful. Our options are to
+ * restart the option cache from the lease's best point of view
+ * (execute statements from the lease pool's group), or to
+ * simply restart the option cache from empty.
+ *
+ * I think restarting the option cache from empty best
+ * approaches RFC 4388's intent; specific options are included.
+ */
+ option_state_dereference(&options, MDL);
+
+ if (!option_state_allocate(&options, MDL)) {
+ log_error("%s: out of memory, no reply sent", msgbuf);
+ lease_dereference(&lease, MDL);
+ return;
+ }
+
+ /*
+ * Set the hardware address fields.
+ */
+
+ packet->raw->hlen = lease->hardware_addr.hlen - 1;
+ packet->raw->htype = lease->hardware_addr.hbuf[0];
+ memcpy(packet->raw->chaddr,
+ &lease->hardware_addr.hbuf[1],
+ sizeof(packet->raw->chaddr));
+
+ /*
+ * Set client identifier option.
+ */
+ if (lease->uid_len > 0) {
+ if (!add_option(options,
+ DHO_DHCP_CLIENT_IDENTIFIER,
+ lease->uid,
+ lease->uid_len)) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+
+ /*
+ * Calculate T1 and T2, the times when the client
+ * tries to extend its lease on its networking
+ * address.
+ * These seem to be hard-coded in ISC DHCP, to 0.5 and
+ * 0.875 of the lease time.
+ */
+
+ lease_duration = lease->ends - lease->starts;
+ time_renewal = lease->starts +
+ (lease_duration / 2);
+ time_rebinding = lease->starts +
+ (lease_duration / 2) +
+ (lease_duration / 4) +
+ (lease_duration / 8);
+
+ if (time_renewal > cur_time) {
+ if (time_renewal < cur_time)
+ time_renewal = 0;
+ else
+ time_renewal = htonl(time_renewal - cur_time);
+
+ if (!add_option(options,
+ DHO_DHCP_RENEWAL_TIME,
+ &time_renewal,
+ sizeof(time_renewal))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ if (time_rebinding > cur_time) {
+ time_rebinding = htonl(time_rebinding - cur_time);
+
+ if (!add_option(options,
+ DHO_DHCP_REBINDING_TIME,
+ &time_rebinding,
+ sizeof(time_rebinding))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ if (lease->ends > cur_time) {
+ if (time_expiry < cur_time) {
+ log_error("Impossible condition at %s:%d.",
+ MDL);
+
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ return;
+ }
+ time_expiry = htonl(lease->ends - cur_time);
+ if (!add_option(options,
+ DHO_DHCP_LEASE_TIME,
+ &time_expiry,
+ sizeof(time_expiry))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ /* Supply the Vendor-Class-Identifier. */
+ if (lease->scope != NULL) {
+ struct data_string vendor_class;
+
+ memset(&vendor_class, 0, sizeof(vendor_class));
+
+ if (find_bound_string(&vendor_class, lease->scope,
+ "vendor-class-identifier")) {
+ if (!add_option(options,
+ DHO_VENDOR_CLASS_IDENTIFIER,
+ (void *)vendor_class.data,
+ vendor_class.len)) {
+ option_state_dereference(&options,
+ MDL);
+ lease_dereference(&lease, MDL);
+ log_error("%s: error adding vendor "
+ "class identifier, no reply "
+ "sent", msgbuf);
+ data_string_forget(&vendor_class, MDL);
+ return;
+ }
+ data_string_forget(&vendor_class, MDL);
+ }
+ }
+
+ /*
+ * Set the relay agent info.
+ *
+ * Note that because agent info is appended without regard
+ * to the PRL in cons_options(), this will be sent as the
+ * last option in the packet whether it is listed on PRL or
+ * not.
+ */
+
+ if (lease->agent_options != NULL) {
+ int idx = agent_universe.index;
+ struct option_chain_head **tmp1 =
+ (struct option_chain_head **)
+ &(options->universes[idx]);
+ struct option_chain_head *tmp2 =
+ (struct option_chain_head *)
+ lease->agent_options;
+
+ option_chain_head_reference(tmp1, tmp2, MDL);
+ }
+
+ /*
+ * Set the client last transaction time.
+ * We check to make sure we have a timestamp. For
+ * lease files that were saved before running a
+ * timestamp-aware version of the server, this may
+ * not be set.
+ */
+
+ if (lease->cltt != MIN_TIME) {
+ if (cur_time > lease->cltt) {
+ client_last_transaction_time =
+ htonl(cur_time - lease->cltt);
+ } else {
+ client_last_transaction_time = htonl(0);
+ }
+ if (!add_option(options,
+ DHO_CLIENT_LAST_TRANSACTION_TIME,
+ &client_last_transaction_time,
+ sizeof(client_last_transaction_time))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+
+ /*
+ * Set associated IPs, if requested and there are some.
+ */
+ if (want_associated_ip && (assoc_ip_cnt > 0)) {
+ if (!add_option(options,
+ DHO_ASSOCIATED_IP,
+ assoc_ips,
+ assoc_ip_cnt * sizeof(assoc_ips[0]))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: out of memory, no reply sent",
+ msgbuf);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Set the message type.
+ */
+
+ packet->raw->op = BOOTREPLY;
+
+ /*
+ * Set DHCP message type.
+ */
+ if (!add_option(options,
+ DHO_DHCP_MESSAGE_TYPE,
+ &dhcpMsgType,
+ sizeof(dhcpMsgType))) {
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+ log_info("%s: error adding option, no reply sent", msgbuf);
+ return;
+ }
+
+ /*
+ * Log the message we've received.
+ */
+ log_info("%s", msgbuf);
+
+ /*
+ * Set up the option buffer.
+ */
+
+ memset(&prl, 0, sizeof(prl));
+ oc = lookup_option(&dhcp_universe, options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+ if (oc != NULL) {
+ evaluate_option_cache(&prl,
+ packet,
+ NULL,
+ NULL,
+ packet->options,
+ options,
+ &global_scope,
+ oc,
+ MDL);
+ }
+ if (prl.len > 0) {
+ prl_ptr = &prl;
+ } else {
+ prl_ptr = NULL;
+ }
+
+ packet->packet_length = cons_options(packet,
+ packet->raw,
+ lease,
+ NULL,
+ 0,
+ packet->options,
+ options,
+ &global_scope,
+ 0,
+ 0,
+ 0,
+ prl_ptr,
+ NULL);
+
+ data_string_forget(&prl, MDL); /* SK: safe, even if empty */
+ option_state_dereference(&options, MDL);
+ lease_dereference(&lease, MDL);
+
+ memset(&to, 0, sizeof(to));
+ to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ to.sin6_len = sizeof(to);
+#endif
+
+ /*
+ * Leasequery packets are be sent to the IPv6 CRA address.
+ */
+ memcpy(&to.sin6_addr, packet->client_addr.iabuf, 16);
+ to.sin6_port = local_port;
+
+ /*
+ * Report what we're sending.
+ */
+ log_info("%s to %s for %s (%d associated IPs)",
+ dhcp_msg_type_name,
+ piaddr(packet->client_addr), dbg_info, assoc_ip_cnt);
+
+ send_packet6(packet->interface,
+ (unsigned char *)packet->raw,
+ packet->packet_length,
+ &to);
+}
/*
* TODO: RFC5007 query-by-clientid.
diff --git a/server/mdb.c b/server/mdb.c
index 3867ba58..d4c2b8b4 100644
--- a/server/mdb.c
+++ b/server/mdb.c
@@ -849,6 +849,8 @@ int find_subnet (struct subnet **sp,
struct subnet *rv;
for (rv = subnets; rv; rv = rv -> next_subnet) {
+ if (addr . len != rv -> netmask . len)
+ continue;
if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) {
if (subnet_reference (sp, rv,
file, line) != ISC_R_SUCCESS)
@@ -866,6 +868,8 @@ int find_grouped_subnet (struct subnet **sp,
struct subnet *rv;
for (rv = share -> subnets; rv; rv = rv -> next_sibling) {
+ if (addr . len != rv -> netmask . len)
+ continue;
if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) {
if (subnet_reference (sp, rv,
file, line) != ISC_R_SUCCESS)
@@ -881,6 +885,8 @@ int
subnet_inner_than(const struct subnet *subnet,
const struct subnet *scan,
int warnp) {
+ if (subnet->net.len != scan->net.len)
+ return 0;
if (addr_eq(subnet_number(subnet->net, scan->netmask), scan->net) ||
addr_eq(subnet_number(scan->net, subnet->netmask), subnet->net)) {
char n1buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255")];
@@ -2967,6 +2973,10 @@ void free_everything(void)
cancel_all_timeouts ();
relinquish_timeouts ();
+#ifdef DHCVPv6
+ if (run_as_tsv)
+ relinquish_ackqueue_tsv();
+#endif
relinquish_ackqueue();
trace_free_all ();
group_dereference (&root_group, MDL);
diff --git a/server/stables.c b/server/stables.c
index 914694d0..5f205c43 100644
--- a/server/stables.c
+++ b/server/stables.c
@@ -3,7 +3,7 @@
Tables of information only used by server... */
/*
- * Copyright (c) 2004-2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2012 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 1995-2003 by Internet Software Consortium
*
* Permission to use, copy, modify, and distribute this software for any
@@ -175,6 +175,7 @@ static struct option agent_options[] = {
{ "agent-id", "I", &agent_universe, 3, 1 },
{ "DOCSIS-device-class", "L", &agent_universe, 4, 1 },
{ "link-selection", "I", &agent_universe, 5, 1 },
+ { "cra6addr", "6", &agent_universe, 46, 1 },
{ NULL, NULL, NULL, 0, 0 }
};
@@ -267,6 +268,7 @@ static struct option server_options[] = {
#endif /* LDAP_USE_SSL */
#endif /* LDAP_CONFIGURATION */
{ "dhcp-cache-threshold", "B", &server_universe, 78, 1 },
+ { "local-address6", "6", &server_universe, 79, 1 },
{ NULL, NULL, NULL, 0, 0 }
};
diff --git a/server/tsv.c b/server/tsv.c
new file mode 100644
index 00000000..4e3a733b
--- /dev/null
+++ b/server/tsv.c
@@ -0,0 +1,3226 @@
+/* tsv.c
+
+ DHCP Protocol engine (IPv6-transport server variant). */
+
+/*
+ * Copyright (c) 2004-2012 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This software has been written for Internet Systems Consortium
+ * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc.
+ * To learn more about Internet Systems Consortium, see
+ * ``https://www.isc.org/''. To learn more about Vixie Enterprises,
+ * see ``http://www.vix.com''. To learn more about Nominum, Inc., see
+ * ``http://www.nominum.com''.
+ */
+
+#include "dhcpd.h"
+
+#ifdef DHCPv6
+
+#include <errno.h>
+#include <limits.h>
+#include <sys/time.h>
+
+static void commit_leases_ackout(void *foo);
+static void maybe_return_agent_options(struct packet *packet,
+ struct option_state *options);
+
+static struct leasequeue *ackqueue_head, *ackqueue_tail;
+static struct leasequeue *free_ackqueue;
+static struct timeval max_fsync;
+
+static int outstanding_acks;
+static int min_ack_delay_usecs = DEFAULT_MIN_ACK_DELAY_USECS;
+
+static char dhcp_message [256];
+static int site_code_min;
+
+static int find_min_site_code(struct universe *);
+static isc_result_t lowest_site_code(const void *, unsigned, void *);
+
+static const char *dhcp_type_names [] = {
+ "DHCPDISCOVER",
+ "DHCPOFFER",
+ "DHCPREQUEST",
+ "DHCPDECLINE",
+ "DHCPACK",
+ "DHCPNAK",
+ "DHCPRELEASE",
+ "DHCPINFORM",
+ "type 9",
+ "DHCPLEASEQUERY",
+ "DHCPLEASEUNASSIGNED",
+ "DHCPLEASEUNKNOWN",
+ "DHCPLEASEACTIVE"
+};
+static const int dhcp_type_name_max =
+ ((sizeof dhcp_type_names) / sizeof (char *));
+
+void
+dhcp_tsv (struct packet *packet) {
+ int ms_nulltp = 0;
+ struct option_cache *oc;
+ struct lease *lease = NULL;
+ const char *errmsg;
+
+ if (!locate_network_tsv(packet) &&
+ packet->packet_type != DHCPREQUEST &&
+ packet->packet_type != DHCPINFORM &&
+ packet->packet_type != DHCPLEASEQUERY) {
+ const char *s;
+ char typebuf[32];
+ errmsg = "unknown network segment";
+ bad_packet:
+
+ if (packet->packet_type > 0 &&
+ packet->packet_type <= dhcp_type_name_max) {
+ s = dhcp_type_names[packet->packet_type - 1];
+ } else {
+ /* %Audit% Cannot exceed 28 bytes. %2004.06.17,Safe% */
+ sprintf(typebuf, "type %d", packet->packet_type);
+ s = typebuf;
+ }
+
+ log_info("%s from %s via %s: %s", s,
+ (packet->raw->htype
+ ? print_hw_addr(packet->raw->htype,
+ packet->raw->hlen,
+ packet->raw->chaddr)
+ : "<no identifier>"),
+ piaddr(packet->client_addr), errmsg);
+ goto out;
+ }
+
+ /* If a client null terminates options it sends, it probably
+ * expects the server to reciprocate.
+ */
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_HOST_NAME))) {
+ if (!oc -> expression)
+ ms_nulltp = oc->flags & OPTION_HAD_NULLS;
+ }
+
+ /* Classify the client. */
+ classify_client (packet);
+
+ switch (packet -> packet_type) {
+ case DHCPDISCOVER:
+ dhcpdiscover_tsv (packet, ms_nulltp);
+ break;
+
+ case DHCPREQUEST:
+ dhcprequest_tsv (packet, ms_nulltp, lease);
+ break;
+
+ case DHCPRELEASE:
+ dhcprelease_tsv (packet, ms_nulltp);
+ break;
+
+ case DHCPDECLINE:
+ dhcpdecline_tsv (packet, ms_nulltp);
+ break;
+
+ case DHCPINFORM:
+ dhcpinform_tsv (packet, ms_nulltp);
+ break;
+
+ case DHCPLEASEQUERY:
+ dhcpleasequery_tsv(packet, ms_nulltp);
+ break;
+
+ case DHCPACK:
+ case DHCPOFFER:
+ case DHCPNAK:
+ case DHCPLEASEUNASSIGNED:
+ case DHCPLEASEUNKNOWN:
+ case DHCPLEASEACTIVE:
+ break;
+
+ default:
+ errmsg = "unknown packet type";
+ goto bad_packet;
+ }
+ out:
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcpdiscover_tsv (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ struct lease *lease = (struct lease *)0;
+ char msgbuf [1024]; /* XXX */
+ TIME when;
+ const char *s;
+ int peer_has_leases = 0;
+#if defined (FAILOVER_PROTOCOL)
+ dhcp_failover_state_t *peer;
+#endif
+
+ find_lease (&lease, packet, packet -> shared_network,
+ 0, &peer_has_leases, (struct lease *)0, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf, "DHCPDISCOVER from %s %s%s%svia %s",
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ piaddr (packet -> client_addr));
+
+ /* Sourceless packets don't make sense here. */
+ if (!packet -> shared_network) {
+ log_info ("Packet from unknown subnet: %s",
+ piaddr (packet -> client_addr));
+ goto out;
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ peer = lease -> pool -> failover_peer;
+
+ /*
+ * If the lease is ours to (re)allocate, then allocate it.
+ *
+ * If the lease is active, it belongs to the client. This
+ * is the right lease, if we are to offer one. We decide
+ * whether or not to offer later on.
+ *
+ * If the lease was last active, and we've reached this
+ * point, then it was last active with the same client. We
+ * can safely re-activate the lease with this client.
+ */
+ if (lease->binding_state == FTS_ACTIVE ||
+ lease->rewind_binding_state == FTS_ACTIVE ||
+ lease_mine_to_reallocate(lease)) {
+ ; /* This space intentionally left blank. */
+
+ /* Otherwise, we can't let the client have this lease. */
+ } else {
+#if defined (DEBUG_FIND_LEASE)
+ log_debug ("discarding %s - %s",
+ piaddr (lease -> ip_addr),
+ binding_state_print (lease -> binding_state));
+#endif
+ lease_dereference (&lease, MDL);
+ }
+ }
+#endif
+
+ /* If we didn't find a lease, try to allocate one... */
+ if (!lease) {
+ if (!allocate_lease (&lease, packet,
+ packet -> shared_network -> pools,
+ &peer_has_leases)) {
+ if (peer_has_leases)
+ log_error ("%s: peer holds all free leases",
+ msgbuf);
+ else
+ log_error ("%s: network %s: no free leases",
+ msgbuf,
+ packet -> shared_network -> name);
+ return;
+ }
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ peer = lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ log_info ("%s: not responding%s",
+ msgbuf, peer -> nrr);
+ goto out;
+ }
+ } else
+ peer = (dhcp_failover_state_t *)0;
+
+ /* Do load balancing if configured. */
+ if (peer && (peer -> service_state == cooperating) &&
+ !load_balance_mine (packet, peer)) {
+ if (peer_has_leases) {
+ log_debug ("%s: load balance to peer %s",
+ msgbuf, peer -> name);
+ goto out;
+ } else {
+ log_debug ("%s: cancel load balance to peer %s - %s",
+ msgbuf, peer -> name, "no free leases");
+ }
+ }
+#endif
+
+ /* If it's an expired lease, get rid of any bindings. */
+ if (lease -> ends < cur_time && lease -> scope)
+ binding_scope_dereference (&lease -> scope, MDL);
+
+ /* Set the lease to really expire in 2 minutes, unless it has
+ not yet expired, in which case leave its expiry time alone. */
+ when = cur_time + 120;
+ if (when < lease -> ends)
+ when = lease -> ends;
+
+ ack_lease_tsv (packet, lease, DHCPOFFER, when, msgbuf, ms_nulltp,
+ (struct host_decl *)0);
+ out:
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcprequest_tsv (packet, ms_nulltp, ip_lease)
+ struct packet *packet;
+ int ms_nulltp;
+ struct lease *ip_lease;
+{
+ struct lease *lease;
+ struct iaddr cip;
+ struct iaddr sip;
+ struct subnet *subnet;
+ int ours = 0;
+ struct option_cache *oc;
+ struct data_string data;
+ char msgbuf [1024]; /* XXX */
+ char addrbuf [MAX_ADDRESS_STRING_LEN];
+ const char *s;
+ char smbuf [19];
+#if defined (FAILOVER_PROTOCOL)
+ dhcp_failover_state_t *peer;
+#endif
+ int have_requested_addr = 0;
+
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_REQUESTED_ADDRESS);
+ memset (&data, 0, sizeof data);
+ if (oc &&
+ evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ cip.len = 4;
+ memcpy (cip.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ have_requested_addr = 1;
+ } else {
+ oc = (struct option_cache *)0;
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr.s_addr, 4);
+ }
+
+ /* Find the lease that matches the address requested by the
+ client. */
+
+ subnet = (struct subnet *)0;
+ lease = (struct lease *)0;
+ if (find_subnet (&subnet, cip, MDL))
+ find_lease (&lease, packet,
+ subnet -> shared_network, &ours, 0,
+ ip_lease, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ memset (&data, 0, sizeof data);
+ if (oc &&
+ evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ sip.len = 4;
+ memcpy (sip.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ /* piaddr() should not return more than a 15 byte string.
+ * safe.
+ */
+ sprintf (smbuf, " (%s)", piaddr (sip));
+ } else
+ smbuf [0] = 0;
+
+ strncpy(addrbuf, piaddr (packet -> client_addr), sizeof(addrbuf));
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf,
+ "DHCPREQUEST for %s%s from %s %s%s%svia %s",
+ piaddr (cip), smbuf,
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ addrbuf);
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ peer = lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ log_info ("%s: not responding%s",
+ msgbuf, peer -> nrr);
+ goto out;
+ }
+
+ /* "load balance to peer" - is not done at all for request.
+ *
+ * If it's RENEWING, we are the only server to hear it, so
+ * we have to serve it. If it's REBINDING, it's out of
+ * communication with the other server, so there's no point
+ * in waiting to serve it. However, if the lease we're
+ * offering is not a free lease, then we may be the only
+ * server that can offer it, so we can't load balance if
+ * the lease isn't in the free or backup state. If it is
+ * in the free or backup state, then that state is what
+ * mandates one server or the other should perform the
+ * allocation, not the LBA...we know the peer cannot
+ * allocate a request for an address in our free state.
+ *
+ * So our only compass is lease_mine_to_reallocate(). This
+ * effects both load balancing, and a sanity-check that we
+ * are not going to try to allocate a lease that isn't ours.
+ */
+ if ((lease -> binding_state == FTS_FREE ||
+ lease -> binding_state == FTS_BACKUP) &&
+ !lease_mine_to_reallocate (lease)) {
+ log_debug ("%s: lease owned by peer", msgbuf);
+ goto out;
+ }
+
+ /*
+ * If the lease is in a transitional state, we can't
+ * renew it unless we can rewind it to a non-transitional
+ * state (active, free, or backup). lease_mine_to_reallocate()
+ * checks for free/backup, so we only need to check for active.
+ */
+ if ((lease->binding_state == FTS_RELEASED ||
+ lease->binding_state == FTS_EXPIRED) &&
+ lease->rewind_binding_state != FTS_ACTIVE &&
+ !lease_mine_to_reallocate(lease)) {
+ log_debug("%s: lease in transition state %s", msgbuf,
+ (lease->binding_state == FTS_RELEASED)
+ ? "released" : "expired");
+ goto out;
+ }
+
+ /* It's actually very unlikely that we'll ever get here,
+ but if we do, tell the client to stop using the lease,
+ because the administrator reset it. */
+ if (lease -> binding_state == FTS_RESET &&
+ !lease_mine_to_reallocate (lease)) {
+ log_debug ("%s: lease reset by administrator", msgbuf);
+ nak_lease_tsv (packet, &cip);
+ goto out;
+ }
+
+ /* At this point it's possible that we will get a broadcast
+ DHCPREQUEST for a lease that we didn't offer, because
+ both we and the peer are in a position to offer it.
+ In that case, we probably shouldn't answer. In order
+ to not answer, we would have to compare the server
+ identifier sent by the client with the list of possible
+ server identifiers we can send, and if the client's
+ identifier isn't on the list, drop the DHCPREQUEST.
+ We aren't currently doing that for two reasons - first,
+ it's not clear that all clients do the right thing
+ with respect to sending the client identifier, which
+ could mean that we might simply not respond to a client
+ that is depending on us to respond. Secondly, we allow
+ the user to specify the server identifier to send, and
+ we don't enforce that the server identifier should be
+ one of our IP addresses. This is probably not a big
+ deal, but it's theoretically an issue.
+
+ The reason we care about this is that if both servers
+ send a DHCPACK to the DHCPREQUEST, they are then going
+ to send dueling BNDUPD messages, which could cause
+ trouble. I think it causes no harm, but it seems
+ wrong. */
+ } else
+ peer = (dhcp_failover_state_t *)0;
+#endif
+
+ /* If a client on a given network REQUESTs a lease on an
+ address on a different network, NAK it. If the Requested
+ Address option was used, the protocol says that it must
+ have been broadcast, so we can trust the source network
+ information.
+
+ If ciaddr was specified and Requested Address was not, then
+ we really only know for sure what network a packet came from
+ if it came through a BOOTP gateway - if it came through an
+ IP router, we'll just have to assume that it's cool.
+
+ If we don't think we know where the packet came from, it
+ came through a gateway from an unknown network, so it's not
+ from a RENEWING client. If we recognize the network it
+ *thinks* it's on, we can NAK it even though we don't
+ recognize the network it's *actually* on; otherwise we just
+ have to ignore it.
+
+ We don't currently try to take advantage of access to the
+ raw packet, because it's not available on all platforms.
+ So a packet that was unicast to us through a router from a
+ RENEWING client is going to look exactly like a packet that
+ was broadcast to us from an INIT-REBOOT client.
+
+ Since we can't tell the difference between these two kinds
+ of packets, if the packet appears to have come in off the
+ local wire, we have to treat it as if it's a RENEWING
+ client. This means that we can't NAK a RENEWING client on
+ the local wire that has a bogus address. The good news is
+ that we won't ACK it either, so it should revert to INIT
+ state and send us a DHCPDISCOVER, which we *can* work with.
+
+ Because we can't detect that a RENEWING client is on the
+ wrong wire, it's going to sit there trying to renew until
+ it gets to the REBIND state, when we *can* NAK it because
+ the packet will get to us through a BOOTP gateway. We
+ shouldn't actually see DHCPREQUEST packets from RENEWING
+ clients on the wrong wire anyway, since their idea of their
+ local router will be wrong. In any case, the protocol
+ doesn't really allow us to NAK a DHCPREQUEST from a
+ RENEWING client, so we can punt on this issue. */
+
+ if (!packet -> shared_network ||
+ packet -> raw -> ciaddr.s_addr ||
+ (have_requested_addr && !packet -> raw -> ciaddr.s_addr)) {
+
+ /* If we don't know where it came from but we do know
+ where it claims to have come from, it didn't come
+ from there. */
+ if (!packet -> shared_network) {
+ if (subnet && subnet -> group -> authoritative) {
+ log_info ("%s: wrong network.", msgbuf);
+ nak_lease_tsv (packet, &cip);
+ goto out;
+ }
+ /* Otherwise, ignore it. */
+ log_info ("%s: ignored (%s).", msgbuf,
+ (subnet
+ ? "not authoritative" : "unknown subnet"));
+ goto out;
+ }
+
+ /* If we do know where it came from and it asked for an
+ address that is not on that shared network, nak it. */
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+ if (!find_grouped_subnet (&subnet, packet -> shared_network,
+ cip, MDL)) {
+ if (packet -> shared_network -> group -> authoritative)
+ {
+ log_info ("%s: wrong network.", msgbuf);
+ nak_lease_tsv (packet, &cip);
+ goto out;
+ }
+ log_info ("%s: ignored (not authoritative).", msgbuf);
+ return;
+ }
+ }
+
+ /* If the address the client asked for is ours, but it wasn't
+ available for the client, NAK it. */
+ if (!lease && ours) {
+ log_info ("%s: lease %s unavailable.", msgbuf, piaddr (cip));
+ nak_lease_tsv (packet, &cip);
+ goto out;
+ }
+
+ /* Otherwise, send the lease to the client if we found one. */
+ if (lease) {
+ ack_lease_tsv (packet, lease, DHCPACK, 0, msgbuf, ms_nulltp,
+ (struct host_decl *)0);
+ } else
+ log_info ("%s: unknown lease %s.", msgbuf, piaddr (cip));
+
+ out:
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+ if (lease)
+ lease_dereference (&lease, MDL);
+ return;
+}
+
+void dhcprelease_tsv (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ struct lease *lease = (struct lease *)0, *next = (struct lease *)0;
+ struct iaddr cip;
+ struct option_cache *oc;
+ struct data_string data;
+ const char *s;
+ char msgbuf [1024], cstr[16]; /* XXX */
+
+
+ /* DHCPRELEASE must not specify address in requested-address
+ option, but old protocol specs weren't explicit about this,
+ so let it go. */
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_REQUESTED_ADDRESS))) {
+ log_info ("DHCPRELEASE from %s specified requested-address.",
+ print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr));
+ }
+
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ memset (&data, 0, sizeof data);
+ if (oc &&
+ evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ find_lease_by_uid (&lease, data.data, data.len, MDL);
+ data_string_forget (&data, MDL);
+
+ /* See if we can find a lease that matches the IP address
+ the client is claiming. */
+ while (lease) {
+ if (lease -> n_uid)
+ lease_reference (&next, lease -> n_uid, MDL);
+ if (!memcmp (&packet -> raw -> ciaddr,
+ lease -> ip_addr.iabuf, 4)) {
+ break;
+ }
+ lease_dereference (&lease, MDL);
+ if (next) {
+ lease_reference (&lease, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ }
+ if (next)
+ lease_dereference (&next, MDL);
+ }
+
+ /* The client is supposed to pass a valid client-identifier,
+ but the spec on this has changed historically, so try the
+ IP address in ciaddr if the client-identifier fails. */
+ if (!lease) {
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4);
+ find_lease_by_ip_addr (&lease, cip, MDL);
+ }
+
+
+ /* If the hardware address doesn't match, don't do the release. */
+ if (lease &&
+ (lease -> hardware_addr.hlen != packet -> raw -> hlen + 1 ||
+ lease -> hardware_addr.hbuf [0] != packet -> raw -> htype ||
+ memcmp (&lease -> hardware_addr.hbuf [1],
+ packet -> raw -> chaddr, packet -> raw -> hlen)))
+ lease_dereference (&lease, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ /* %Audit% Cannot exceed 16 bytes. %2004.06.17,Safe%
+ * We copy this out to stack because we actually want to log two
+ * inet_ntoa()'s in this message.
+ */
+ strncpy(cstr, inet_ntoa (packet -> raw -> ciaddr), 15);
+ cstr[15] = '\0';
+
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf,
+ "DHCPRELEASE of %s from %s %s%s%svia %s (%sfound)",
+ cstr,
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ piaddr (packet -> client_addr),
+ lease ? "" : "not ");
+
+#if defined (FAILOVER_PROTOCOL)
+ if (lease && lease -> pool && lease -> pool -> failover_peer) {
+ dhcp_failover_state_t *peer = lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ log_info ("%s: ignored%s",
+ peer -> name, peer -> nrr);
+ goto out;
+ }
+
+ /* DHCPRELEASE messages are unicast, so if the client
+ sent the DHCPRELEASE to us, it's not going to send it
+ to the peer. Not sure why this would happen, and
+ if it does happen I think we still have to change the
+ lease state, so that's what we're doing.
+ XXX See what it says in the draft about this. */
+ }
+#endif
+
+ /* If we found a lease, release it. */
+ if (lease && lease -> ends > cur_time) {
+ release_lease (lease, packet);
+ }
+ log_info ("%s", msgbuf);
+#if defined(FAILOVER_PROTOCOL)
+ out:
+#endif
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcpdecline_tsv (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ struct lease *lease = (struct lease *)0;
+ struct option_state *options = (struct option_state *)0;
+ int ignorep = 0;
+ int i;
+ const char *status;
+ const char *s;
+ char msgbuf [1024]; /* XXX */
+ char addrbuf [MAX_ADDRESS_STRING_LEN];
+ struct iaddr cip;
+ struct option_cache *oc;
+ struct data_string data;
+
+ /* DHCPDECLINE must specify address. */
+ if (!(oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_REQUESTED_ADDRESS)))
+ return;
+ memset (&data, 0, sizeof data);
+ if (!evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL))
+ return;
+
+ cip.len = 4;
+ memcpy (cip.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ find_lease_by_ip_addr (&lease, cip, MDL);
+
+ if (lease && lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ strncpy(addrbuf, piaddr (packet -> client_addr), sizeof(addrbuf));
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf,
+ "DHCPDECLINE of %s from %s %s%s%svia %s",
+ piaddr (cip),
+ (packet -> raw -> htype
+ ? print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr)
+ : (lease
+ ? print_hex_1(lease->uid_len, lease->uid, 60)
+ : "<no identifier>")),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ addrbuf);
+
+ option_state_allocate (&options, MDL);
+
+ /* Execute statements in scope starting with the subnet scope. */
+ if (lease)
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope,
+ lease -> subnet -> group,
+ (struct group *)0);
+
+ /* Execute statements in the class scopes. */
+ for (i = packet -> class_count; i > 0; i--) {
+ execute_statements_in_scope
+ ((struct binding_value **)0, packet, (struct lease *)0,
+ (struct client_state *)0, packet -> options, options,
+ &global_scope, packet -> classes [i - 1] -> group,
+ lease ? lease -> subnet -> group : (struct group *)0);
+ }
+
+ /* Drop the request if dhcpdeclines are being ignored. */
+ oc = lookup_option (&server_universe, options, SV_DECLINES);
+ if (!oc ||
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options, options,
+ &lease -> scope, oc, MDL)) {
+ /* If we found a lease, mark it as unusable and complain. */
+ if (lease) {
+#if defined (FAILOVER_PROTOCOL)
+ if (lease -> pool && lease -> pool -> failover_peer) {
+ dhcp_failover_state_t *peer =
+ lease -> pool -> failover_peer;
+ if (peer -> service_state == not_responding ||
+ peer -> service_state == service_startup) {
+ if (!ignorep)
+ log_info ("%s: ignored%s",
+ peer -> name, peer -> nrr);
+ goto out;
+ }
+
+ /* DHCPDECLINE messages are broadcast, so we can safely
+ ignore the DHCPDECLINE if the peer has the lease.
+ XXX Of course, at this point that information has been
+ lost. */
+ }
+#endif
+
+ abandon_lease (lease, "declined.");
+ status = "abandoned";
+ } else {
+ status = "not found";
+ }
+ } else
+ status = "ignored";
+
+ if (!ignorep)
+ log_info ("%s: %s", msgbuf, status);
+
+#if defined(FAILOVER_PROTOCOL)
+ out:
+#endif
+ if (options)
+ option_state_dereference (&options, MDL);
+ if (lease)
+ lease_dereference (&lease, MDL);
+}
+
+void dhcpinform_tsv (packet, ms_nulltp)
+ struct packet *packet;
+ int ms_nulltp;
+{
+ char msgbuf [1024];
+ char addrbuf [MAX_ADDRESS_STRING_LEN];
+ struct data_string d1, prl;
+ struct option_cache *oc;
+ struct option_state *options = (struct option_state *)0;
+ struct dhcp_packet raw;
+ struct packet outgoing;
+ unsigned char dhcpack = DHCPACK;
+ struct subnet *subnet = NULL;
+ struct iaddr cip;
+ unsigned i;
+ int nulltp;
+ struct sockaddr_in6 to;
+ ssize_t result;
+
+ if (!packet -> raw -> ciaddr.s_addr) {
+ cip.len = 4;
+ memset (cip.iabuf, 0, 4);
+ } else {
+ cip.len = 4;
+ memcpy (cip.iabuf, &packet -> raw -> ciaddr, 4);
+ }
+
+ strncpy(addrbuf, piaddr (packet -> client_addr), sizeof(addrbuf));
+ /* %Audit% This is log output. %2004.06.17,Safe%
+ * If we truncate we hope the user can get a hint from the log.
+ */
+ snprintf (msgbuf, sizeof msgbuf, "DHCPINFORM from %s via %s",
+ piaddr (cip), addrbuf);
+
+ /* If the IP source address is zero, don't respond. */
+ if (!memcmp (cip.iabuf, "\0\0\0", 4)) {
+ log_info ("%s: ignored (null source address).", msgbuf);
+ return;
+ }
+
+ /* Find the subnet that the client is on. */
+ /* XXX - do subnet selection (not relay agent) option here */
+ find_subnet(&subnet, cip, MDL);
+
+ if (subnet == NULL) {
+ log_info("%s: unknown subnet for client address %s",
+ msgbuf, piaddr(cip));
+ return;
+ }
+
+ /* We don't respond to DHCPINFORM packets if we're not authoritative.
+ It would be nice if a per-host value could override this, but
+ there's overhead involved in checking this, so let's see how people
+ react first. */
+ if (subnet && !subnet -> group -> authoritative) {
+ static int eso = 0;
+ log_info ("%s: not authoritative for subnet %s",
+ msgbuf, piaddr (subnet -> net));
+ if (!eso) {
+ log_info ("If this DHCP server is authoritative for%s",
+ " that subnet,");
+ log_info ("please write an `authoritative;' directi%s",
+ "ve either in the");
+ log_info ("subnet declaration or in some scope that%s",
+ " encloses the");
+ log_info ("subnet declaration - for example, write %s",
+ "it at the top");
+ log_info ("of the dhcpd.conf file.");
+ }
+ if (eso++ == 100)
+ eso = 0;
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+
+ option_state_allocate (&options, MDL);
+ memset (&outgoing, 0, sizeof outgoing);
+ memset (&raw, 0, sizeof raw);
+ outgoing.raw = &raw;
+
+ maybe_return_agent_options(packet, options);
+
+ /* Execute statements in scope starting with the subnet scope. */
+ if (subnet)
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, subnet -> group,
+ (struct group *)0);
+
+ /* Execute statements in the class scopes. */
+ for (i = packet -> class_count; i > 0; i--) {
+ execute_statements_in_scope
+ ((struct binding_value **)0, packet, (struct lease *)0,
+ (struct client_state *)0, packet -> options, options,
+ &global_scope, packet -> classes [i - 1] -> group,
+ subnet ? subnet -> group : (struct group *)0);
+ }
+
+ /* Figure out the filename. */
+ memset (&d1, 0, sizeof d1);
+ oc = lookup_option (&server_universe, options, SV_FILENAME);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ i = d1.len;
+ if (i >= sizeof(raw.file)) {
+ log_info("file name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.file), i,
+ (int)i, d1.data);
+ i = sizeof(raw.file);
+ } else
+ raw.file[i] = 0;
+ memcpy (raw.file, d1.data, i);
+ data_string_forget (&d1, MDL);
+ }
+
+ /* Choose a server name as above. */
+ oc = lookup_option (&server_universe, options, SV_SERVER_NAME);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ i = d1.len;
+ if (i >= sizeof(raw.sname)) {
+ log_info("server name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.sname), i,
+ (int)i, d1.data);
+ i = sizeof(raw.sname);
+ } else
+ raw.sname[i] = 0;
+ memcpy (raw.sname, d1.data, i);
+ data_string_forget (&d1, MDL);
+ }
+
+ /* Set a flag if this client is a lame Microsoft client that NUL
+ terminates string options and expects us to do likewise. */
+ nulltp = 0;
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_HOST_NAME))) {
+ if (!oc->expression)
+ nulltp = oc->flags & OPTION_HAD_NULLS;
+ }
+
+ /* Put in DHCP-specific options. */
+ i = DHO_DHCP_MESSAGE_TYPE;
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ &dhcpack, 1, 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+
+ /* Use the subnet mask from the subnet declaration if no other
+ mask has been provided. */
+ i = DHO_SUBNET_MASK;
+ if (subnet && !lookup_option (&dhcp_universe, options, i)) {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ subnet -> netmask.iabuf,
+ subnet -> netmask.len,
+ 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+
+ /* If a site option space has been specified, use that for
+ site option codes. */
+ i = SV_SITE_OPTION_SPACE;
+ if ((oc = lookup_option (&server_universe, options, i)) &&
+ evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, oc, MDL)) {
+ struct universe *u = (struct universe *)0;
+
+ if (!universe_hash_lookup (&u, universe_hash,
+ (const char *)d1.data, d1.len,
+ MDL)) {
+ log_error ("unknown option space %s.", d1.data);
+ option_state_dereference (&options, MDL);
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+
+ options -> site_universe = u -> index;
+ options->site_code_min = find_min_site_code(u);
+ data_string_forget (&d1, MDL);
+ } else {
+ options -> site_universe = dhcp_universe.index;
+ options -> site_code_min = 0; /* Trust me, it works. */
+ }
+
+ memset (&prl, 0, sizeof prl);
+
+ /* Use the parameter list from the scope if there is one. */
+ oc = lookup_option (&dhcp_universe, options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ /* Otherwise, if the client has provided a list of options
+ that it wishes returned, use it to prioritize. Otherwise,
+ prioritize based on the default priority list. */
+
+ if (!oc)
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ if (oc)
+ evaluate_option_cache (&prl, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, oc, MDL);
+
+#ifdef DEBUG_PACKET
+ dump_packet (packet);
+ dump_raw ((unsigned char *)packet -> raw, packet -> packet_length);
+#endif
+
+ log_info ("%s", msgbuf);
+
+ /* Figure out the address of the boot file server. */
+ if ((oc =
+ lookup_option (&server_universe, options, SV_NEXT_SERVER))) {
+ if (evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, options,
+ &global_scope, oc, MDL)) {
+ /* If there was more than one answer,
+ take the first. */
+ if (d1.len >= 4 && d1.data)
+ memcpy (&raw.siaddr, d1.data, 4);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /*
+ * Remove any time options, per section 3.4 RFC 2131
+ */
+ delete_option(&dhcp_universe, options, DHO_DHCP_LEASE_TIME);
+ delete_option(&dhcp_universe, options, DHO_DHCP_RENEWAL_TIME);
+ delete_option(&dhcp_universe, options, DHO_DHCP_REBINDING_TIME);
+
+ /* Set up the option buffer... */
+ outgoing.packet_length =
+ cons_options (packet, outgoing.raw, (struct lease *)0,
+ (struct client_state *)0,
+ 0, packet -> options, options, &global_scope,
+ 0, nulltp, 0,
+ prl.len ? &prl : (struct data_string *)0,
+ (char *)0);
+ option_state_dereference (&options, MDL);
+ data_string_forget (&prl, MDL);
+
+ /* Make sure that the packet is at least as big as a BOOTP packet. */
+ if (outgoing.packet_length < BOOTP_MIN_LEN)
+ outgoing.packet_length = BOOTP_MIN_LEN;
+
+ raw.ciaddr = packet -> raw -> ciaddr;
+ memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr);
+ raw.hlen = packet -> raw -> hlen;
+ raw.htype = packet -> raw -> htype;
+
+ raw.xid = packet -> raw -> xid;
+ raw.secs = packet -> raw -> secs;
+ raw.flags = packet -> raw -> flags;
+ raw.hops = packet -> raw -> hops;
+ raw.op = BOOTREPLY;
+
+#ifdef DEBUG_PACKET
+ dump_packet (&outgoing);
+ dump_raw ((unsigned char *)&raw, outgoing.packet_length);
+#endif
+
+ /* Set up the common stuff... */
+ memset (&to, 0, sizeof to);
+ to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ to.sin6_len = sizeof to;
+#endif
+
+ memcpy(&to.sin6_addr, packet->client_addr.iabuf, 16);
+ to.sin6_port = local_port;
+
+ /* Report what we're sending. */
+ snprintf(msgbuf, sizeof msgbuf, "DHCPACK to %s (%s) via", piaddr(cip),
+ (packet->raw->htype && packet->raw->hlen) ?
+ print_hw_addr(packet->raw->htype, packet->raw->hlen,
+ packet->raw->chaddr) :
+ "<no client hardware address>");
+ log_info("%s %s", msgbuf, piaddr(packet->client_addr));
+
+ errno = 0;
+ result = send_packet6(packet->interface,
+ (const unsigned char *)&raw,
+ outgoing.packet_length, &to);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long packet over %s "
+ "interface.", MDL, outgoing.packet_length,
+ packet->interface->name);
+ }
+
+
+ if (subnet)
+ subnet_dereference (&subnet, MDL);
+}
+
+void nak_lease_tsv (packet, cip)
+ struct packet *packet;
+ struct iaddr *cip;
+{
+ char addrbuf [MAX_ADDRESS_STRING_LEN];
+ struct sockaddr_in6 to;
+ int result;
+ struct dhcp_packet raw;
+ unsigned char nak = DHCPNAK;
+ struct packet outgoing;
+ unsigned i;
+ struct option_state *options = (struct option_state *)0;
+ struct option_cache *oc = (struct option_cache *)0;
+
+ option_state_allocate (&options, MDL);
+ memset (&outgoing, 0, sizeof outgoing);
+ memset (&raw, 0, sizeof raw);
+ outgoing.raw = &raw;
+
+ /* Set DHCP_MESSAGE_TYPE to DHCPNAK */
+ if (!option_cache_allocate (&oc, MDL)) {
+ log_error ("No memory for DHCPNAK message type.");
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ if (!make_const_data (&oc -> expression, &nak, sizeof nak,
+ 0, 0, MDL)) {
+ log_error ("No memory for expr_const expression.");
+ option_cache_dereference (&oc, MDL);
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ i = DHO_DHCP_MESSAGE_TYPE;
+ option_code_hash_lookup(&oc->option, dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ option_cache_dereference (&oc, MDL);
+
+ /* Set DHCP_MESSAGE to whatever the message is */
+ if (!option_cache_allocate (&oc, MDL)) {
+ log_error ("No memory for DHCPNAK message type.");
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ if (!make_const_data (&oc -> expression,
+ (unsigned char *)dhcp_message,
+ strlen (dhcp_message), 1, 0, MDL)) {
+ log_error ("No memory for expr_const expression.");
+ option_cache_dereference (&oc, MDL);
+ option_state_dereference (&options, MDL);
+ return;
+ }
+ i = DHO_DHCP_MESSAGE;
+ option_code_hash_lookup(&oc->option, dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe, options, oc);
+ option_cache_dereference (&oc, MDL);
+
+ /* If there were agent options in the incoming packet, return
+ * them. We do not check giaddr to detect the presence of a
+ * relay, as this excludes "l2" relay agents which have no
+ * giaddr to set.
+ */
+ if (packet->options->universe_count > agent_universe.index &&
+ packet->options->universes [agent_universe.index]) {
+ option_chain_head_reference
+ ((struct option_chain_head **)
+ &(options -> universes [agent_universe.index]),
+ (struct option_chain_head *)
+ packet -> options -> universes [agent_universe.index],
+ MDL);
+ }
+
+ /* Do not use the client's requested parameter list. */
+ delete_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ /* Set up the option buffer... */
+ outgoing.packet_length =
+ cons_options (packet, outgoing.raw, (struct lease *)0,
+ (struct client_state *)0,
+ 0, packet -> options, options, &global_scope,
+ 0, 0, 0, (struct data_string *)0, (char *)0);
+ option_state_dereference (&options, MDL);
+
+/* memset (&raw.ciaddr, 0, sizeof raw.ciaddr);*/
+ memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr);
+ raw.hlen = packet -> raw -> hlen;
+ raw.htype = packet -> raw -> htype;
+
+ raw.xid = packet -> raw -> xid;
+ raw.secs = packet -> raw -> secs;
+ raw.flags = packet -> raw -> flags | htons (BOOTP_BROADCAST);
+ raw.hops = packet -> raw -> hops;
+ raw.op = BOOTREPLY;
+
+ /* Report what we're sending... */
+ strncpy(addrbuf, piaddr (packet -> client_addr), sizeof(addrbuf));
+ log_info ("DHCPNAK on %s to %s via %s",
+ piaddr (*cip),
+ print_hw_addr (packet -> raw -> htype,
+ packet -> raw -> hlen,
+ packet -> raw -> chaddr),
+ addrbuf);
+
+#ifdef DEBUG_PACKET
+ dump_packet (packet);
+ dump_raw ((unsigned char *)packet -> raw, packet -> packet_length);
+ dump_packet (&outgoing);
+ dump_raw ((unsigned char *)&raw, outgoing.packet_length);
+#endif
+
+ /* Set up the common stuff... */
+ memset (&to, 0, sizeof to);
+ to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ to.sin6_len = sizeof to;
+#endif
+
+ /* Make sure that the packet is at least as big as a BOOTP packet. */
+ if (outgoing.packet_length < BOOTP_MIN_LEN)
+ outgoing.packet_length = BOOTP_MIN_LEN;
+
+ /* this was gatewayed, send it back to the gateway. */
+ memcpy(&to.sin6_addr, packet->client_addr.iabuf, 16);
+ to.sin6_port = local_port;
+
+ errno = 0;
+ result = send_packet6(packet->interface,
+ (const unsigned char *)&raw,
+ outgoing.packet_length, &to);
+ if (result < 0) {
+ log_error ("%s:%d: Failed to send %d byte long "
+ "packet over %s interface.", MDL,
+ outgoing.packet_length,
+ packet->interface->name);
+ }
+}
+
+void ack_lease_tsv (packet, lease, offer, when, msg, ms_nulltp, hp)
+ struct packet *packet;
+ struct lease *lease;
+ unsigned int offer;
+ TIME when;
+ char *msg;
+ int ms_nulltp;
+ struct host_decl *hp;
+{
+ struct lease *lt;
+ struct lease_state *state;
+ struct lease *next;
+ struct host_decl *host = (struct host_decl *)0;
+ TIME lease_time;
+ TIME offered_lease_time;
+ struct data_string d1;
+ TIME min_lease_time;
+ TIME max_lease_time;
+ TIME default_lease_time;
+ struct option_cache *oc;
+ isc_result_t result;
+ TIME ping_timeout;
+ TIME lease_cltt;
+ TIME remaining_time;
+ struct iaddr cip;
+#if defined(DELAYED_ACK)
+ isc_boolean_t enqueue = ISC_TRUE;
+#endif
+ int use_old_lease = 0;
+
+ unsigned i, j;
+ int s1;
+ int ignorep;
+ struct timeval tv;
+
+ /* If we're already acking this lease, don't do it again. */
+ if (lease -> state)
+ return;
+
+ /* Save original cltt for comparison later. */
+ lease_cltt = lease->cltt;
+
+ /* If the lease carries a host record, remember it. */
+ if (hp)
+ host_reference (&host, hp, MDL);
+ else if (lease -> host)
+ host_reference (&host, lease -> host, MDL);
+
+ /* Allocate a lease state structure... */
+ state = new_lease_state (MDL);
+ if (!state)
+ log_fatal ("unable to allocate lease state!");
+ state -> got_requested_address = packet -> got_requested_address;
+ shared_network_reference (&state -> shared_network,
+ packet -> interface -> shared_network, MDL);
+
+ /* See if we got a server identifier option. */
+ if (lookup_option (&dhcp_universe,
+ packet -> options, DHO_DHCP_SERVER_IDENTIFIER))
+ state -> got_server_identifier = 1;
+
+ maybe_return_agent_options(packet, state->options);
+
+ /* If we are offering a lease that is still currently valid, preserve
+ the events. We need to do this because if the client does not
+ REQUEST our offer, it will expire in 2 minutes, overriding the
+ expire time in the currently in force lease. We want the expire
+ events to be executed at that point. */
+ if (lease -> ends <= cur_time && offer != DHCPOFFER) {
+ /* Get rid of any old expiry or release statements - by
+ executing the statements below, we will be inserting new
+ ones if there are any to insert. */
+ if (lease -> on_expiry)
+ executable_statement_dereference (&lease -> on_expiry,
+ MDL);
+ if (lease -> on_commit)
+ executable_statement_dereference (&lease -> on_commit,
+ MDL);
+ if (lease -> on_release)
+ executable_statement_dereference (&lease -> on_release,
+ MDL);
+ }
+
+ /* Execute statements in scope starting with the subnet scope. */
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options,
+ state -> options, &lease -> scope,
+ lease -> subnet -> group,
+ (struct group *)0);
+
+ /* If the lease is from a pool, run the pool scope. */
+ if (lease -> pool)
+ (execute_statements_in_scope
+ ((struct binding_value **)0, packet, lease,
+ (struct client_state *)0, packet -> options,
+ state -> options, &lease -> scope, lease -> pool -> group,
+ lease -> pool -> shared_network -> group));
+
+ /* Execute statements from class scopes. */
+ for (i = packet -> class_count; i > 0; i--) {
+ execute_statements_in_scope
+ ((struct binding_value **)0,
+ packet, lease, (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, packet -> classes [i - 1] -> group,
+ (lease -> pool
+ ? lease -> pool -> group
+ : lease -> subnet -> group));
+ }
+
+ /* See if the client is only supposed to have one lease at a time,
+ and if so, find its other leases and release them. We can only
+ do this on DHCPREQUEST. It's a little weird to do this before
+ looking at permissions, because the client might not actually
+ _get_ a lease after we've done the permission check, but the
+ assumption for this option is that the client has exactly one
+ network interface, and will only ever remember one lease. So
+ if it sends a DHCPREQUEST, and doesn't get the lease, it's already
+ forgotten about its old lease, so we can too. */
+ if (packet -> packet_type == DHCPREQUEST &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_ONE_LEASE_PER_CLIENT)) &&
+ evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options, &lease -> scope,
+ oc, MDL)) {
+ struct lease *seek;
+ if (lease -> uid_len) {
+ do {
+ seek = (struct lease *)0;
+ find_lease_by_uid (&seek, lease -> uid,
+ lease -> uid_len, MDL);
+ if (!seek)
+ break;
+ if (seek == lease && !seek -> n_uid) {
+ lease_dereference (&seek, MDL);
+ break;
+ }
+ next = (struct lease *)0;
+
+ /* Don't release expired leases, and don't
+ release the lease we're going to assign. */
+ next = (struct lease *)0;
+ while (seek) {
+ if (seek -> n_uid)
+ lease_reference (&next, seek -> n_uid, MDL);
+ if (seek != lease &&
+ seek -> binding_state != FTS_RELEASED &&
+ seek -> binding_state != FTS_EXPIRED &&
+ seek -> binding_state != FTS_RESET &&
+ seek -> binding_state != FTS_FREE &&
+ seek -> binding_state != FTS_BACKUP)
+ break;
+ lease_dereference (&seek, MDL);
+ if (next) {
+ lease_reference (&seek, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ }
+ if (next)
+ lease_dereference (&next, MDL);
+ if (seek) {
+ release_lease (seek, packet);
+ lease_dereference (&seek, MDL);
+ } else
+ break;
+ } while (1);
+ }
+ if (!lease -> uid_len ||
+ (host &&
+ !host -> client_identifier.len &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_DUPLICATES)) &&
+ !evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope,
+ oc, MDL))) {
+ do {
+ seek = (struct lease *)0;
+ find_lease_by_hw_addr
+ (&seek, lease -> hardware_addr.hbuf,
+ lease -> hardware_addr.hlen, MDL);
+ if (!seek)
+ break;
+ if (seek == lease && !seek -> n_hw) {
+ lease_dereference (&seek, MDL);
+ break;
+ }
+ next = (struct lease *)0;
+ while (seek) {
+ if (seek -> n_hw)
+ lease_reference (&next, seek -> n_hw, MDL);
+ if (seek != lease &&
+ seek -> binding_state != FTS_RELEASED &&
+ seek -> binding_state != FTS_EXPIRED &&
+ seek -> binding_state != FTS_RESET &&
+ seek -> binding_state != FTS_FREE &&
+ seek -> binding_state != FTS_BACKUP)
+ break;
+ lease_dereference (&seek, MDL);
+ if (next) {
+ lease_reference (&seek, next, MDL);
+ lease_dereference (&next, MDL);
+ }
+ }
+ if (next)
+ lease_dereference (&next, MDL);
+ if (seek) {
+ release_lease (seek, packet);
+ lease_dereference (&seek, MDL);
+ } else
+ break;
+ } while (1);
+ }
+ }
+
+
+ /* Make sure this packet satisfies the configured minimum
+ number of seconds. */
+ memset (&d1, 0, sizeof d1);
+ if (offer == DHCPOFFER &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_MIN_SECS))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len &&
+ ntohs (packet -> raw -> secs) < d1.data [0]) {
+ log_info("%s: configured min-secs value (%d) "
+ "is greater than secs field (%d). "
+ "message dropped.", msg, d1.data[0],
+ ntohs(packet->raw->secs));
+ data_string_forget (&d1, MDL);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Try to find a matching host declaration for this lease.
+ */
+ if (!host) {
+ struct host_decl *hp = (struct host_decl *)0;
+ struct host_decl *h;
+
+ /* Try to find a host_decl that matches the client
+ identifier or hardware address on the packet, and
+ has no fixed IP address. If there is one, hang
+ it off the lease so that its option definitions
+ can be used. */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ find_hosts_by_uid (&hp, d1.data, d1.len, MDL);
+ data_string_forget (&d1, MDL);
+ for (h = hp; h; h = h -> n_ipaddr) {
+ if (!h -> fixed_addr)
+ break;
+ }
+ if (h)
+ host_reference (&host, h, MDL);
+ if (hp != NULL)
+ host_dereference(&hp, MDL);
+ }
+ if (!host) {
+ find_hosts_by_haddr (&hp,
+ packet -> raw -> htype,
+ packet -> raw -> chaddr,
+ packet -> raw -> hlen,
+ MDL);
+ for (h = hp; h; h = h -> n_ipaddr) {
+ if (!h -> fixed_addr)
+ break;
+ }
+ if (h)
+ host_reference (&host, h, MDL);
+ if (hp != NULL)
+ host_dereference(&hp, MDL);
+ }
+ if (!host) {
+ find_hosts_by_option(&hp, packet, packet->options, MDL);
+ for (h = hp; h; h = h -> n_ipaddr) {
+ if (!h -> fixed_addr)
+ break;
+ }
+ if (h)
+ host_reference (&host, h, MDL);
+ if (hp != NULL)
+ host_dereference(&hp, MDL);
+ }
+ }
+
+ /* If we have a host_decl structure, run the options associated
+ with its group. Whether the host decl struct is old or not. */
+ if (host)
+ execute_statements_in_scope ((struct binding_value **)0,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options, &lease -> scope,
+ host -> group,
+ (lease -> pool
+ ? lease -> pool -> group
+ : lease -> subnet -> group));
+
+ /* Drop the request if it's not allowed for this client. By
+ default, unknown clients are allowed. */
+ if (!host &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_BOOT_UNKNOWN_CLIENTS)) &&
+ !evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: unknown client", msg);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* Drop the request if it's not allowed for this client. */
+ if (!offer &&
+ (oc = lookup_option (&server_universe, state -> options,
+ SV_ALLOW_BOOTP)) &&
+ !evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: bootp disallowed", msg);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* Drop the request if booting is specifically denied. */
+ oc = lookup_option (&server_universe, state -> options,
+ SV_ALLOW_BOOTING);
+ if (oc &&
+ !evaluate_boolean_option_cache (&ignorep,
+ packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (!ignorep)
+ log_info ("%s: booting disallowed", msg);
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* If we are configured to do per-class billing, do it. */
+ if (have_billing_classes && !(lease -> flags & STATIC_LEASE)) {
+ /* See if the lease is currently being billed to a
+ class, and if so, whether or not it can continue to
+ be billed to that class. */
+ if (lease -> billing_class) {
+ for (i = 0; i < packet -> class_count; i++)
+ if (packet -> classes [i] ==
+ lease -> billing_class)
+ break;
+ if (i == packet -> class_count)
+ unbill_class (lease, lease -> billing_class);
+ }
+
+ /* If we don't have an active billing, see if we need
+ one, and if we do, try to do so. */
+ if (lease->billing_class == NULL) {
+ int bill = 0;
+ for (i = 0; i < packet->class_count; i++) {
+ if (packet->classes[i]->lease_limit) {
+ bill++;
+ if (bill_class(lease,
+ packet->classes[i]))
+ break;
+ }
+ }
+ if (bill != 0 && i == packet->class_count) {
+ log_info("%s: no available billing: lease "
+ "limit reached in all matching "
+ "classes", msg);
+ free_lease_state(state, MDL);
+ if (host)
+ host_dereference(&host, MDL);
+ return;
+ }
+
+ /* If this is an offer, undo the billing. We go
+ * through all the steps above to bill a class so
+ * we can hit the 'no available billing' mark and
+ * abort without offering. But it just doesn't make
+ * sense to permanently bill a class for a non-active
+ * lease. This means on REQUEST, we will bill this
+ * lease again (if there is a REQUEST).
+ */
+ if (offer == DHCPOFFER &&
+ lease->billing_class != NULL &&
+ lease->binding_state != FTS_ACTIVE)
+ unbill_class(lease, lease->billing_class);
+ }
+ }
+
+ /* Figure out the filename. */
+ oc = lookup_option (&server_universe, state -> options, SV_FILENAME);
+ if (oc)
+ evaluate_option_cache (&state -> filename, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL);
+
+ /* Choose a server name as above. */
+ oc = lookup_option (&server_universe, state -> options,
+ SV_SERVER_NAME);
+ if (oc)
+ evaluate_option_cache (&state -> server_name, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL);
+
+ /* At this point, we have a lease that we can offer the client.
+ Now we construct a lease structure that contains what we want,
+ and call supersede_lease to do the right thing with it. */
+ lt = (struct lease *)0;
+ result = lease_allocate (&lt, MDL);
+ if (result != ISC_R_SUCCESS) {
+ log_info ("%s: can't allocate temporary lease structure: %s",
+ msg, isc_result_totext (result));
+ free_lease_state (state, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ /* Use the ip address of the lease that we finally found in
+ the database. */
+ lt -> ip_addr = lease -> ip_addr;
+
+ /* Start now. */
+ lt -> starts = cur_time;
+
+ /* Figure out how long a lease to assign. If this is a
+ dynamic BOOTP lease, its duration must be infinite. */
+ if (offer) {
+ lt->flags &= ~BOOTP_LEASE;
+
+ default_lease_time = DEFAULT_DEFAULT_LEASE_TIME;
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_DEFAULT_LEASE_TIME))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ default_lease_time =
+ getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_LEASE_TIME)))
+ s1 = evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL);
+ else
+ s1 = 0;
+
+ if (s1 && (d1.len == 4)) {
+ u_int32_t ones = 0xffffffff;
+
+ /* One potential use of reserved leases is to allow
+ * clients to signal reservation of their lease. They
+ * can kinda sorta do this, if you squint hard enough,
+ * by supplying an 'infinite' requested-lease-time
+ * option. This is generally bad practice...you want
+ * clients to return to the server on at least some
+ * period (days, months, years) to get up-to-date
+ * config state. So;
+ *
+ * 1) A client requests 0xffffffff lease-time.
+ * 2) The server reserves the lease, and assigns a
+ * <= max_lease_time lease-time to the client, which
+ * we presume is much smaller than 0xffffffff.
+ * 3) The client ultimately fails to renew its lease
+ * (all clients go offline at some point).
+ * 4) The server retains the reservation, although
+ * the lease expires and passes through those states
+ * as normal, it's placed in the 'reserved' queue,
+ * and is under no circumstances allocated to any
+ * clients.
+ *
+ * Whether the client knows its reserving its lease or
+ * not, this can be a handy tool for a sysadmin.
+ */
+ if ((memcmp(d1.data, &ones, 4) == 0) &&
+ (oc = lookup_option(&server_universe,
+ state->options,
+ SV_RESERVE_INFINITE)) &&
+ evaluate_boolean_option_cache(&ignorep, packet,
+ lease, NULL, packet->options,
+ state->options, &lease->scope,
+ oc, MDL)) {
+ lt->flags |= RESERVED_LEASE;
+ if (!ignorep)
+ log_info("Infinite-leasetime "
+ "reservation made on %s.",
+ piaddr(lt->ip_addr));
+ }
+
+ lease_time = getULong (d1.data);
+ } else
+ lease_time = default_lease_time;
+
+ if (s1)
+ data_string_forget(&d1, MDL);
+
+ /* See if there's a maximum lease time. */
+ max_lease_time = DEFAULT_MAX_LEASE_TIME;
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_MAX_LEASE_TIME))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ max_lease_time =
+ getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Enforce the maximum lease length. */
+ if (lease_time < 0 /* XXX */
+ || lease_time > max_lease_time)
+ lease_time = max_lease_time;
+
+ min_lease_time = DEFAULT_MIN_LEASE_TIME;
+ if (min_lease_time > max_lease_time)
+ min_lease_time = max_lease_time;
+
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_MIN_LEASE_TIME))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ min_lease_time = getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* CC: If there are less than
+ adaptive-lease-time-threshold % free leases,
+ hand out only short term leases */
+
+ memset(&d1, 0, sizeof(d1));
+ if (lease->pool &&
+ (oc = lookup_option(&server_universe, state->options,
+ SV_ADAPTIVE_LEASE_TIME_THRESHOLD)) &&
+ evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, state->options,
+ &lease->scope, oc, MDL)) {
+ if (d1.len == 1 && d1.data[0] > 0 &&
+ d1.data[0] < 100) {
+ TIME adaptive_time;
+ int poolfilled, total, count;
+
+ if (min_lease_time)
+ adaptive_time = min_lease_time;
+ else
+ adaptive_time = DEFAULT_MIN_LEASE_TIME;
+
+ /* Allow the client to keep its lease. */
+ if (lease->ends - cur_time > adaptive_time)
+ adaptive_time = lease->ends - cur_time;
+
+ count = lease->pool->lease_count;
+ total = count - (lease->pool->free_leases +
+ lease->pool->backup_leases);
+
+ poolfilled = (total > (INT_MAX / 100)) ?
+ total / (count / 100) :
+ (total * 100) / count;
+
+ log_debug("Adap-lease: Total: %d, Free: %d, "
+ "Ends: %d, Adaptive: %d, Fill: %d, "
+ "Threshold: %d",
+ lease->pool->lease_count,
+ lease->pool->free_leases,
+ (int)(lease->ends - cur_time),
+ (int)adaptive_time, poolfilled,
+ d1.data[0]);
+
+ if (poolfilled >= d1.data[0] &&
+ lease_time > adaptive_time) {
+ log_info("Pool over threshold, time "
+ "for %s reduced from %d to "
+ "%d.", piaddr(lease->ip_addr),
+ (int)lease_time,
+ (int)adaptive_time);
+
+ lease_time = adaptive_time;
+ }
+ }
+ data_string_forget(&d1, MDL);
+ }
+
+ /* a client requests an address which is not yet active*/
+ if (lease->pool && lease->pool->valid_from &&
+ cur_time < lease->pool->valid_from) {
+ /* NAK leases before pool activation date */
+ cip.len = 4;
+ memcpy (cip.iabuf, &lt->ip_addr.iabuf, 4);
+ nak_lease_tsv(packet, &cip);
+ free_lease_state (state, MDL);
+ lease_dereference (&lt, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+
+ }
+
+ /* CC:
+ a) NAK current lease if past the expiration date
+ b) extend lease only up to the expiration date, but not
+ below min-lease-time
+ Setting min-lease-time is essential for this to work!
+ The value of min-lease-time determines the lenght
+ of the transition window:
+ A client renewing a second before the deadline will
+ get a min-lease-time lease. Since the current ip might not
+ be routable after the deadline, the client will
+ be offline until it DISCOVERS again. Otherwise it will
+ receive a NAK at T/2.
+ A min-lease-time of 6 seconds effectively switches over
+ all clients in this pool very quickly.
+ */
+
+ if (lease->pool && lease->pool->valid_until) {
+ if (cur_time >= lease->pool->valid_until) {
+ /* NAK leases after pool expiration date */
+ cip.len = 4;
+ memcpy (cip.iabuf, &lt->ip_addr.iabuf, 4);
+ nak_lease_tsv(packet, &cip);
+ free_lease_state (state, MDL);
+ lease_dereference (&lt, MDL);
+ if (host)
+ host_dereference (&host, MDL);
+ return;
+ }
+ remaining_time = lease->pool->valid_until - cur_time;
+ if (lease_time > remaining_time)
+ lease_time = remaining_time;
+ }
+
+ if (lease_time < min_lease_time) {
+ if (min_lease_time)
+ lease_time = min_lease_time;
+ else
+ lease_time = default_lease_time;
+ }
+
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Okay, we know the lease duration. Now check the
+ failover state, if any. */
+ if (lease -> pool && lease -> pool -> failover_peer) {
+ TIME new_lease_time = lease_time;
+ dhcp_failover_state_t *peer =
+ lease -> pool -> failover_peer;
+
+ /* Copy previous lease failover ack-state. */
+ lt->tsfp = lease->tsfp;
+ lt->atsfp = lease->atsfp;
+
+ /* cltt set below */
+
+ /* Lease times less than MCLT are not a concern. */
+ if (lease_time > peer->mclt) {
+ /* Each server can only offer a lease time
+ * that is either equal to MCLT (at least),
+ * or up to TSFP+MCLT. Only if the desired
+ * lease time falls within TSFP+MCLT, can
+ * the server allow it.
+ */
+ if (lt->tsfp <= cur_time)
+ new_lease_time = peer->mclt;
+ else if ((cur_time + lease_time) >
+ (lt->tsfp + peer->mclt))
+ new_lease_time = (lt->tsfp - cur_time)
+ + peer->mclt;
+ }
+
+ /* Update potential expiry. Allow for the desired
+ * lease time plus one half the actual (whether
+ * modified downward or not) lease time, which is
+ * actually an estimate of when the client will
+ * renew. This way, the client will be able to get
+ * the desired lease time upon renewal.
+ */
+ if (offer == DHCPACK) {
+ lt->tstp = cur_time + lease_time +
+ (new_lease_time / 2);
+
+ /* If we reduced the potential expiry time,
+ * make sure we don't offer an old-expiry-time
+ * lease for this lease before the change is
+ * ack'd.
+ */
+ if (lt->tstp < lt->tsfp)
+ lt->tsfp = lt->tstp;
+ } else
+ lt->tstp = lease->tstp;
+
+ /* Use failover-modified lease time. */
+ lease_time = new_lease_time;
+ }
+#endif /* FAILOVER_PROTOCOL */
+
+ /* If the lease duration causes the time value to wrap,
+ use the maximum expiry time. */
+ if (cur_time + lease_time < cur_time)
+ state -> offered_expiry = MAX_TIME - 1;
+ else
+ state -> offered_expiry = cur_time + lease_time;
+ if (when)
+ lt -> ends = when;
+ else
+ lt -> ends = state -> offered_expiry;
+
+ /* Don't make lease active until we actually get a
+ DHCPREQUEST. */
+ if (offer == DHCPACK)
+ lt -> next_binding_state = FTS_ACTIVE;
+ else
+ lt -> next_binding_state = lease -> binding_state;
+ } else {
+ lt->flags |= BOOTP_LEASE;
+
+ lease_time = MAX_TIME - cur_time;
+
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_BOOTP_LEASE_LENGTH))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ lease_time = getULong (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_BOOTP_LEASE_CUTOFF))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ lease_time = (getULong (d1.data) -
+ cur_time);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ lt -> ends = state -> offered_expiry = cur_time + lease_time;
+ lt -> next_binding_state = FTS_ACTIVE;
+ }
+
+ /* Update Client Last Transaction Time. */
+ lt->cltt = cur_time;
+
+ /* Record the uid, if given... */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_CLIENT_IDENTIFIER);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len <= sizeof lt -> uid_buf) {
+ memcpy (lt -> uid_buf, d1.data, d1.len);
+ lt -> uid = lt -> uid_buf;
+ lt -> uid_max = sizeof lt -> uid_buf;
+ lt -> uid_len = d1.len;
+ } else {
+ unsigned char *tuid;
+ lt -> uid_max = d1.len;
+ lt -> uid_len = d1.len;
+ tuid = (unsigned char *)dmalloc (lt -> uid_max, MDL);
+ /* XXX inelegant */
+ if (!tuid)
+ log_fatal ("no memory for large uid.");
+ memcpy (tuid, d1.data, lt -> uid_len);
+ lt -> uid = tuid;
+ }
+ data_string_forget (&d1, MDL);
+ }
+
+ if (host) {
+ host_reference (&lt -> host, host, MDL);
+ host_dereference (&host, MDL);
+ }
+ if (lease -> subnet)
+ subnet_reference (&lt -> subnet, lease -> subnet, MDL);
+ if (lease -> billing_class)
+ class_reference (&lt -> billing_class,
+ lease -> billing_class, MDL);
+
+ /* Set a flag if this client is a broken client that NUL
+ terminates string options and expects us to do likewise. */
+ if (ms_nulltp)
+ lease -> flags |= MS_NULL_TERMINATION;
+ else
+ lease -> flags &= ~MS_NULL_TERMINATION;
+
+ /* Save any bindings. */
+ if (lease -> scope) {
+ binding_scope_reference (&lt -> scope, lease -> scope, MDL);
+ binding_scope_dereference (&lease -> scope, MDL);
+ }
+ if (lease -> agent_options)
+ option_chain_head_reference (&lt -> agent_options,
+ lease -> agent_options, MDL);
+
+ /* Save the vendor-class-identifier for DHCPLEASEQUERY. */
+ oc = lookup_option(&dhcp_universe, packet->options,
+ DHO_VENDOR_CLASS_IDENTIFIER);
+ if (oc != NULL &&
+ evaluate_option_cache(&d1, packet, NULL, NULL, packet->options,
+ NULL, &lease->scope, oc, MDL)) {
+ if (d1.len != 0) {
+ bind_ds_value(&lease->scope, "vendor-class-identifier",
+ &d1);
+ }
+
+ data_string_forget(&d1, MDL);
+ }
+
+ /* If we got relay agent information options from the packet, then
+ * cache them for renewal in case the relay agent can't supply them
+ * when the client unicasts. The options may be from an addressed
+ * "l3" relay, or from an unaddressed "l2" relay which does not set
+ * giaddr.
+ */
+ if (!packet->agent_options_stashed &&
+ (packet->options != NULL) &&
+ packet->options->universe_count > agent_universe.index &&
+ packet->options->universes[agent_universe.index] != NULL) {
+ oc = lookup_option (&server_universe, state -> options,
+ SV_STASH_AGENT_OPTIONS);
+ if (!oc ||
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (lt -> agent_options)
+ option_chain_head_dereference (&lt -> agent_options, MDL);
+ option_chain_head_reference
+ (&lt -> agent_options,
+ (struct option_chain_head *)
+ packet -> options -> universes [agent_universe.index],
+ MDL);
+ }
+ }
+
+ /* Replace the old lease hostname with the new one, if it's changed. */
+ oc = lookup_option (&dhcp_universe, packet -> options, DHO_HOST_NAME);
+ if (oc)
+ s1 = evaluate_option_cache (&d1, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL);
+ else
+ s1 = 0;
+
+ if (oc && s1 &&
+ lease -> client_hostname &&
+ strlen (lease -> client_hostname) == d1.len &&
+ !memcmp (lease -> client_hostname, d1.data, d1.len)) {
+ /* Hasn't changed. */
+ data_string_forget (&d1, MDL);
+ lt -> client_hostname = lease -> client_hostname;
+ lease -> client_hostname = (char *)0;
+ } else if (oc && s1) {
+ lt -> client_hostname = dmalloc (d1.len + 1, MDL);
+ if (!lt -> client_hostname)
+ log_error ("no memory for client hostname.");
+ else {
+ memcpy (lt -> client_hostname, d1.data, d1.len);
+ lt -> client_hostname [d1.len] = 0;
+ }
+ data_string_forget (&d1, MDL);
+ }
+
+ /* Record the hardware address, if given... */
+ lt -> hardware_addr.hlen = packet -> raw -> hlen + 1;
+ lt -> hardware_addr.hbuf [0] = packet -> raw -> htype;
+ memcpy (&lt -> hardware_addr.hbuf [1], packet -> raw -> chaddr,
+ sizeof packet -> raw -> chaddr);
+
+ lt -> flags = lease -> flags & ~PERSISTENT_FLAGS;
+
+ /* If there are statements to execute when the lease is
+ committed, execute them. */
+ if (lease -> on_commit && (!offer || offer == DHCPACK)) {
+ execute_statements ((struct binding_value **)0,
+ packet, lt, (struct client_state *)0,
+ packet -> options,
+ state -> options, &lt -> scope,
+ lease -> on_commit);
+ if (lease -> on_commit)
+ executable_statement_dereference (&lease -> on_commit,
+ MDL);
+ }
+
+#ifdef NSUPDATE
+ /* Perform DDNS updates, if configured to. */
+ if ((!offer || offer == DHCPACK) &&
+ (!(oc = lookup_option (&server_universe, state -> options,
+ SV_DDNS_UPDATES)) ||
+ evaluate_boolean_option_cache (&ignorep, packet, lt,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lt -> scope, oc, MDL))) {
+ ddns_updates(packet, lt, lease, NULL, NULL, state->options);
+ }
+#endif /* NSUPDATE */
+
+ /* Don't call supersede_lease on a mocked-up lease. */
+ if (lease -> flags & STATIC_LEASE) {
+ /* Copy the hardware address into the static lease
+ structure. */
+ lease -> hardware_addr.hlen = packet -> raw -> hlen + 1;
+ lease -> hardware_addr.hbuf [0] = packet -> raw -> htype;
+ memcpy (&lease -> hardware_addr.hbuf [1],
+ packet -> raw -> chaddr,
+ sizeof packet -> raw -> chaddr); /* XXX */
+ } else {
+ int commit = (!offer || (offer == DHCPACK));
+ int thresh = DEFAULT_CACHE_THRESHOLD;
+
+ /*
+ * Check if the lease was issued recently, if so replay the
+ * current lease and do not require a database sync event.
+ * Recently is defined as being issued less than a given
+ * percentage of the lease previously. The percentage can be
+ * chosen either from a default value or via configuration.
+ *
+ */
+ if ((oc = lookup_option(&server_universe, state->options,
+ SV_CACHE_THRESHOLD)) &&
+ evaluate_option_cache(&d1, packet, lt, NULL,
+ packet->options, state->options,
+ &lt->scope, oc, MDL)) {
+ if (d1.len == 1 && (d1.data[0] < 100))
+ thresh = d1.data[0];
+
+ data_string_forget(&d1, MDL);
+ }
+
+ if ((thresh > 0) && (offer == DHCPACK) &&
+ (lease->binding_state == FTS_ACTIVE)) {
+ int limit;
+ int prev_lease = lease->ends - lease->starts;
+
+ /* it is better to avoid division by 0 */
+ if (prev_lease <= (INT_MAX / thresh))
+ limit = prev_lease * thresh / 100;
+ else
+ limit = prev_lease / 100 * thresh;
+
+ if ((lt->starts - lease->starts) <= limit) {
+ lt->starts = lease->starts;
+ state->offered_expiry = lt->ends = lease->ends;
+ commit = 0;
+ use_old_lease = 1;
+ }
+ }
+
+#if !defined(DELAYED_ACK)
+ /* Install the new information on 'lt' onto the lease at
+ * 'lease'. If this is a DHCPOFFER, it is a 'soft' promise,
+ * if it is a DHCPACK, it is a 'hard' binding, so it needs
+ * to be recorded and propogated immediately. If the update
+ * fails, don't ACK it (or BOOTREPLY) either; we may give
+ * the same lease to another client later, and that would be
+ * a conflict.
+ */
+ if (!use_old_lease && !supersede_lease(lease, lt, commit,
+ offer == DHCPACK, offer == DHCPACK)) {
+#else /* defined(DELAYED_ACK) */
+ /*
+ * If there already isn't a need for a lease commit, and we
+ * can just answer right away, set a flag to indicate this.
+ */
+ if (commit && !(lease->flags & STATIC_LEASE) &&
+ (!offer || (offer == DHCPACK)))
+ enqueue = ISC_TRUE;
+ else
+ enqueue = ISC_FALSE;
+
+ /* Install the new information on 'lt' onto the lease at
+ * 'lease'.  We will not 'commit' this information to disk
+ * yet (fsync()), we will 'propogate' the information if
+ * this is BOOTP or a DHCPACK, but we will not 'pimmediate'ly
+ * transmit failover binding updates (this is delayed until
+ * after the fsync()). If the update fails, don't ACK it (or
+ * BOOTREPLY either); we may give the same lease out to a
+ * different client, and that would be a conflict.
+ */
+ if (!supersede_lease(lease, lt, 0, !offer || offer == DHCPACK,
+ 0)) {
+#endif
+ log_info ("%s: database update failed", msg);
+ free_lease_state (state, MDL);
+ lease_dereference (&lt, MDL);
+ return;
+ }
+ }
+ lease_dereference (&lt, MDL);
+
+ /* Remember the interface on which the packet arrived. */
+ state -> ip = packet -> interface;
+
+ /* Remember the xid, secs, flags and hops. */
+ state -> ciaddr = packet -> raw -> ciaddr;
+ state -> xid = packet -> raw -> xid;
+ state -> secs = packet -> raw -> secs;
+ state -> bootp_flags = packet -> raw -> flags;
+ state -> hops = packet -> raw -> hops;
+ state -> offer = offer;
+
+ /* Remember the IPv6 address. */
+ memcpy(&state -> from,
+ &packet -> client_addr,
+ sizeof(state -> from));
+
+ /* If we're always supposed to broadcast to this client, set
+ the broadcast bit in the bootp flags field. */
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_ALWAYS_BROADCAST)) &&
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL))
+ state -> bootp_flags |= htons (BOOTP_BROADCAST);
+
+ /* Get the Maximum Message Size option from the packet, if one
+ was sent. */
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_MAX_MESSAGE_SIZE);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int16_t))
+ state -> max_message_size = getUShort (d1.data);
+ data_string_forget (&d1, MDL);
+ } else {
+ oc = lookup_option (&dhcp_universe, state -> options,
+ DHO_DHCP_MAX_MESSAGE_SIZE);
+ if (oc &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int16_t))
+ state -> max_message_size =
+ getUShort (d1.data);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Get the Subnet Selection option from the packet, if one
+ was sent. */
+ if ((oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_SUBNET_SELECTION))) {
+
+ /* Make a copy of the data. */
+ struct option_cache *noc = (struct option_cache *)0;
+ if (option_cache_allocate (&noc, MDL)) {
+ if (oc -> data.len)
+ data_string_copy (&noc -> data,
+ &oc -> data, MDL);
+ if (oc -> expression)
+ expression_reference (&noc -> expression,
+ oc -> expression, MDL);
+ if (oc -> option)
+ option_reference(&(noc->option), oc->option,
+ MDL);
+ }
+
+ save_option (&dhcp_universe, state -> options, noc);
+ option_cache_dereference (&noc, MDL);
+ }
+
+ /* Now, if appropriate, put in DHCP-specific options that
+ override those. */
+ if (state -> offer) {
+ i = DHO_DHCP_MESSAGE_TYPE;
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ &state -> offer, 1, 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+
+ offered_lease_time =
+ state -> offered_expiry - cur_time;
+
+ putULong(state->expiry, (u_int32_t)offered_lease_time);
+ i = DHO_DHCP_LEASE_TIME;
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data(&oc->expression, state->expiry,
+ 4, 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+
+ /*
+ * Validate any configured renew or rebinding times against
+ * the determined lease time. Do rebinding first so that
+ * the renew time can be validated against the rebind time.
+ */
+ if ((oc = lookup_option(&dhcp_universe, state->options,
+ DHO_DHCP_REBINDING_TIME)) != NULL &&
+ evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, state->options,
+ &lease->scope, oc, MDL)) {
+ TIME rebind_time = getULong(d1.data);
+
+ /* Drop the configured (invalid) rebinding time. */
+ if (rebind_time >= offered_lease_time)
+ delete_option(&dhcp_universe, state->options,
+ DHO_DHCP_REBINDING_TIME);
+ else /* XXX: variable is reused. */
+ offered_lease_time = rebind_time;
+
+ data_string_forget(&d1, MDL);
+ }
+
+ if ((oc = lookup_option(&dhcp_universe, state->options,
+ DHO_DHCP_RENEWAL_TIME)) != NULL &&
+ evaluate_option_cache(&d1, packet, lease, NULL,
+ packet->options, state->options,
+ &lease->scope, oc, MDL)) {
+ if (getULong(d1.data) >= offered_lease_time)
+ delete_option(&dhcp_universe, state->options,
+ DHO_DHCP_RENEWAL_TIME);
+
+ data_string_forget(&d1, MDL);
+ }
+ }
+
+ /* Figure out the address of the boot file server. */
+ memset (&state -> siaddr, 0, sizeof state -> siaddr);
+ if ((oc =
+ lookup_option (&server_universe,
+ state -> options, SV_NEXT_SERVER))) {
+ if (evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ /* If there was more than one answer,
+ take the first. */
+ if (d1.len >= 4 && d1.data)
+ memcpy (&state -> siaddr, d1.data, 4);
+ data_string_forget (&d1, MDL);
+ }
+ }
+
+ /* Use the subnet mask from the subnet declaration if no other
+ mask has been provided. */
+ i = DHO_SUBNET_MASK;
+ if (!lookup_option (&dhcp_universe, state -> options, i)) {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ lease -> subnet -> netmask.iabuf,
+ lease -> subnet -> netmask.len,
+ 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+
+ /* Use the hostname from the host declaration if there is one
+ and no hostname has otherwise been provided, and if the
+ use-host-decl-name flag is set. */
+ i = DHO_HOST_NAME;
+ j = SV_USE_HOST_DECL_NAMES;
+ if (!lookup_option (&dhcp_universe, state -> options, i) &&
+ lease -> host && lease -> host -> name &&
+ (evaluate_boolean_option_cache
+ (&ignorep, packet, lease, (struct client_state *)0,
+ packet -> options, state -> options, &lease -> scope,
+ lookup_option (&server_universe, state -> options, j), MDL))) {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ ((unsigned char *)
+ lease -> host -> name),
+ strlen (lease -> host -> name),
+ 1, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+
+ /* If we don't have a hostname yet, and we've been asked to do
+ a reverse lookup to find the hostname, do it. */
+ i = DHO_HOST_NAME;
+ j = SV_GET_LEASE_HOSTNAMES;
+ if (!lookup_option(&dhcp_universe, state->options, i) &&
+ evaluate_boolean_option_cache
+ (&ignorep, packet, lease, NULL,
+ packet->options, state->options, &lease->scope,
+ lookup_option (&server_universe, state->options, j), MDL)) {
+ struct in_addr ia;
+ struct hostent *h;
+
+ memcpy (&ia, lease -> ip_addr.iabuf, 4);
+
+ h = gethostbyaddr ((char *)&ia, sizeof ia, AF_INET);
+ if (!h)
+ log_error ("No hostname for %s", inet_ntoa (ia));
+ else {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ ((unsigned char *)
+ h -> h_name),
+ strlen (h -> h_name) + 1,
+ 1, 1, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+ }
+
+ /* If so directed, use the leased IP address as the router address.
+ This supposedly makes Win95 machines ARP for all IP addresses,
+ so if the local router does proxy arp, you win. */
+
+ if (evaluate_boolean_option_cache
+ (&ignorep, packet, lease, (struct client_state *)0,
+ packet -> options, state -> options, &lease -> scope,
+ lookup_option (&server_universe, state -> options,
+ SV_USE_LEASE_ADDR_FOR_DEFAULT_ROUTE), MDL)) {
+ i = DHO_ROUTERS;
+ oc = lookup_option (&dhcp_universe, state -> options, i);
+ if (!oc) {
+ oc = (struct option_cache *)0;
+ if (option_cache_allocate (&oc, MDL)) {
+ if (make_const_data (&oc -> expression,
+ lease -> ip_addr.iabuf,
+ lease -> ip_addr.len,
+ 0, 0, MDL)) {
+ option_code_hash_lookup(&oc->option,
+ dhcp_universe.code_hash,
+ &i, 0, MDL);
+ save_option (&dhcp_universe,
+ state -> options, oc);
+ }
+ option_cache_dereference (&oc, MDL);
+ }
+ }
+ }
+
+ /* If a site option space has been specified, use that for
+ site option codes. */
+ i = SV_SITE_OPTION_SPACE;
+ if ((oc = lookup_option (&server_universe, state -> options, i)) &&
+ evaluate_option_cache (&d1, packet, lease,
+ (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL)) {
+ struct universe *u = (struct universe *)0;
+
+ if (!universe_hash_lookup (&u, universe_hash,
+ (const char *)d1.data, d1.len,
+ MDL)) {
+ log_error ("unknown option space %s.", d1.data);
+ return;
+ }
+
+ state -> options -> site_universe = u -> index;
+ state->options->site_code_min = find_min_site_code(u);
+ data_string_forget (&d1, MDL);
+ } else {
+ state -> options -> site_code_min = 0;
+ state -> options -> site_universe = dhcp_universe.index;
+ }
+
+ /* If the client has provided a list of options that it wishes
+ returned, use it to prioritize. If there's a parameter
+ request list in scope, use that in preference. Otherwise
+ use the default priority list. */
+
+ oc = lookup_option (&dhcp_universe, state -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+
+ if (!oc)
+ oc = lookup_option (&dhcp_universe, packet -> options,
+ DHO_DHCP_PARAMETER_REQUEST_LIST);
+ if (oc)
+ evaluate_option_cache (&state -> parameter_request_list,
+ packet, lease, (struct client_state *)0,
+ packet -> options, state -> options,
+ &lease -> scope, oc, MDL);
+
+#ifdef DEBUG_PACKET
+ dump_packet (packet);
+ dump_raw ((unsigned char *)packet -> raw, packet -> packet_length);
+#endif
+
+ lease -> state = state;
+
+ log_info ("%s", msg);
+
+ /* Hang the packet off the lease state. */
+ packet_reference (&lease -> state -> packet, packet, MDL);
+
+ /* If this is a DHCPOFFER, ping the lease address before actually
+ sending the offer. */
+ if (offer == DHCPOFFER && !(lease -> flags & STATIC_LEASE) &&
+ ((cur_time - lease_cltt) > 60) &&
+ (!(oc = lookup_option (&server_universe, state -> options,
+ SV_PING_CHECKS)) ||
+ evaluate_boolean_option_cache (&ignorep, packet, lease,
+ (struct client_state *)0,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL))) {
+ icmp_echorequest (&lease -> ip_addr);
+
+ /* Determine whether to use configured or default ping timeout.
+ */
+ if ((oc = lookup_option (&server_universe, state -> options,
+ SV_PING_TIMEOUT)) &&
+ evaluate_option_cache (&d1, packet, lease, NULL,
+ packet -> options,
+ state -> options,
+ &lease -> scope, oc, MDL)) {
+ if (d1.len == sizeof (u_int32_t))
+ ping_timeout = getULong (d1.data);
+ else
+ ping_timeout = DEFAULT_PING_TIMEOUT;
+
+ data_string_forget (&d1, MDL);
+ } else
+ ping_timeout = DEFAULT_PING_TIMEOUT;
+
+#ifdef DEBUG
+ log_debug ("Ping timeout: %ld", (long)ping_timeout);
+#endif
+
+ /*
+ * Set a timeout for 'ping-timeout' seconds from NOW, including
+ * current microseconds. As ping-timeout defaults to 1, the
+ * exclusion of current microseconds causes a value somewhere
+ * /between/ zero and one.
+ */
+ tv.tv_sec = cur_tv.tv_sec + ping_timeout;
+ tv.tv_usec = cur_tv.tv_usec;
+ add_timeout (&tv, lease_ping_timeout, lease,
+ (tvref_t)lease_reference,
+ (tvunref_t)lease_dereference);
+ ++outstanding_pings;
+ } else {
+ lease->cltt = cur_time;
+#if defined(DELAYED_ACK)
+ if (enqueue)
+ delayed_ack_enqueue_tsv(lease);
+ else
+#endif
+ dhcp_reply_tsv(lease);
+ }
+}
+
+/*
+ * CC: queue single ACK:
+ * - write the lease (but do not fsync it yet)
+ * - add to double linked list
+ * - commit if more than xx ACKs pending
+ * - if necessary set the max timer and bump the next timer
+ * but only up to the max timer value.
+ */
+
+void
+delayed_ack_enqueue_tsv(struct lease *lease)
+{
+ struct leasequeue *q;
+
+ if (!write_lease(lease))
+ return;
+ if (free_ackqueue) {
+ q = free_ackqueue;
+ free_ackqueue = q->next;
+ } else {
+ q = ((struct leasequeue *)
+ dmalloc(sizeof(struct leasequeue), MDL));
+ if (!q)
+ log_fatal("delayed_ack_enqueue: no memory!");
+ }
+ memset(q, 0, sizeof *q);
+ /* prepend to ackqueue*/
+ lease_reference(&q->lease, lease, MDL);
+ q->next = ackqueue_head;
+ ackqueue_head = q;
+ if (!ackqueue_tail)
+ ackqueue_tail = q;
+ else
+ q->next->prev = q;
+
+ outstanding_acks++;
+ if (outstanding_acks > max_outstanding_acks) {
+ commit_leases();
+
+ /* Reset max_fsync and cancel any pending timeout. */
+ memset(&max_fsync, 0, sizeof(max_fsync));
+ cancel_timeout(commit_leases_ackout, NULL);
+ } else {
+ struct timeval next_fsync;
+
+ if (max_fsync.tv_sec == 0 && max_fsync.tv_usec == 0) {
+ /* set the maximum time we'll wait */
+ max_fsync.tv_sec = cur_tv.tv_sec + max_ack_delay_secs;
+ max_fsync.tv_usec = cur_tv.tv_usec +
+ max_ack_delay_usecs;
+
+ if (max_fsync.tv_usec >= 1000000) {
+ max_fsync.tv_sec++;
+ max_fsync.tv_usec -= 1000000;
+ }
+ }
+
+ /* Set the timeout */
+ next_fsync.tv_sec = cur_tv.tv_sec;
+ next_fsync.tv_usec = cur_tv.tv_usec + min_ack_delay_usecs;
+ if (next_fsync.tv_usec >= 1000000) {
+ next_fsync.tv_sec++;
+ next_fsync.tv_usec -= 1000000;
+ }
+ /* but not more than the max */
+ if ((next_fsync.tv_sec > max_fsync.tv_sec) ||
+ ((next_fsync.tv_sec == max_fsync.tv_sec) &&
+ (next_fsync.tv_usec > max_fsync.tv_usec))) {
+ next_fsync.tv_sec = max_fsync.tv_sec;
+ next_fsync.tv_usec = max_fsync.tv_usec;
+ }
+
+ add_timeout(&next_fsync, commit_leases_ackout, NULL,
+ (tvref_t) NULL, (tvunref_t) NULL);
+ }
+}
+
+static void
+commit_leases_ackout(void *foo)
+{
+ if (outstanding_acks) {
+ commit_leases();
+
+ memset(&max_fsync, 0, sizeof(max_fsync));
+ }
+}
+
+/* CC: process the delayed ACK responses:
+ - send out the ACK packets
+ - move the queue slots to the free list
+ */
+void
+flush_ackqueue_tsv(void *foo)
+{
+ struct leasequeue *ack, *p;
+ /* process from bottom to retain packet order */
+ for (ack = ackqueue_tail ; ack ; ack = p) {
+ p = ack->prev;
+
+ /* dhcp_reply() requires that the reply state still be valid */
+ if (ack->lease->state == NULL)
+ log_error("delayed ack for %s has gone stale",
+ piaddr(ack->lease->ip_addr));
+ else
+ dhcp_reply_tsv(ack->lease);
+
+ lease_dereference(&ack->lease, MDL);
+ ack->next = free_ackqueue;
+ free_ackqueue = ack;
+ }
+ ackqueue_head = NULL;
+ ackqueue_tail = NULL;
+ outstanding_acks = 0;
+}
+
+#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
+void
+relinquish_ackqueue_tsv(void)
+{
+ struct leasequeue *q, *n;
+
+ for (q = ackqueue_head ; q ; q = n) {
+ n = q->next;
+ dfree(q, MDL);
+ }
+ for (q = free_ackqueue ; q ; q = n) {
+ n = q->next;
+ dfree(q, MDL);
+ }
+}
+#endif
+
+void dhcp_reply_tsv (lease)
+ struct lease *lease;
+{
+ int bufs = 0;
+ unsigned packet_length;
+ struct dhcp_packet raw;
+ struct sockaddr_in6 to;
+ int result;
+ struct lease_state *state = lease -> state;
+ int nulltp, bootpp;
+ struct data_string d1;
+ const char *s;
+ char addrbuf [MAX_ADDRESS_STRING_LEN];
+
+ if (!state)
+ log_fatal ("dhcp_reply was supplied lease with no state!");
+
+ /* Compose a response for the client... */
+ memset (&raw, 0, sizeof raw);
+ memset (&d1, 0, sizeof d1);
+
+ /* Copy in the filename if given; otherwise, flag the filename
+ buffer as available for options. */
+ if (state -> filename.len && state -> filename.data) {
+ memcpy (raw.file,
+ state -> filename.data,
+ state -> filename.len > sizeof raw.file
+ ? sizeof raw.file : state -> filename.len);
+ if (sizeof raw.file > state -> filename.len)
+ memset (&raw.file [state -> filename.len], 0,
+ (sizeof raw.file) - state -> filename.len);
+ else
+ log_info("file name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.file),
+ state->filename.len, (int)state->filename.len,
+ state->filename.data);
+ } else
+ bufs |= 1;
+
+ /* Copy in the server name if given; otherwise, flag the
+ server_name buffer as available for options. */
+ if (state -> server_name.len && state -> server_name.data) {
+ memcpy (raw.sname,
+ state -> server_name.data,
+ state -> server_name.len > sizeof raw.sname
+ ? sizeof raw.sname : state -> server_name.len);
+ if (sizeof raw.sname > state -> server_name.len)
+ memset (&raw.sname [state -> server_name.len], 0,
+ (sizeof raw.sname) - state -> server_name.len);
+ else
+ log_info("server name longer than packet field "
+ "truncated - field: %lu name: %d %.*s",
+ (unsigned long)sizeof(raw.sname),
+ state->server_name.len,
+ (int)state->server_name.len,
+ state->server_name.data);
+ } else
+ bufs |= 2; /* XXX */
+
+ memcpy (raw.chaddr,
+ &lease -> hardware_addr.hbuf [1], sizeof raw.chaddr);
+ raw.hlen = lease -> hardware_addr.hlen - 1;
+ raw.htype = lease -> hardware_addr.hbuf [0];
+
+ /* See if this is a Microsoft client that NUL-terminates its
+ strings and expects us to do likewise... */
+ if (lease -> flags & MS_NULL_TERMINATION)
+ nulltp = 1;
+ else
+ nulltp = 0;
+
+ /* See if this is a bootp client... */
+ if (state -> offer)
+ bootpp = 0;
+ else
+ bootpp = 1;
+
+ /* Insert such options as will fit into the buffer. */
+ packet_length = cons_options (state -> packet, &raw, lease,
+ (struct client_state *)0,
+ state -> max_message_size,
+ state -> packet -> options,
+ state -> options, &global_scope,
+ bufs, nulltp, bootpp,
+ &state -> parameter_request_list,
+ (char *)0);
+
+ memcpy (&raw.ciaddr, &state -> ciaddr, sizeof raw.ciaddr);
+ memcpy (&raw.yiaddr, lease -> ip_addr.iabuf, 4);
+ raw.siaddr = state -> siaddr;
+
+ raw.xid = state -> xid;
+ raw.secs = state -> secs;
+ raw.flags = state -> bootp_flags;
+ raw.hops = state -> hops;
+ raw.op = BOOTREPLY;
+
+ if (lease -> client_hostname) {
+ if ((strlen (lease -> client_hostname) <= 64) &&
+ db_printable((unsigned char *)lease->client_hostname))
+ s = lease -> client_hostname;
+ else
+ s = "Hostname Unsuitable for Printing";
+ } else
+ s = (char *)0;
+
+ /* Say what we're doing... */
+ strncpy(addrbuf, piaddr (state -> from), sizeof(addrbuf));
+ log_info ("%s on %s to %s %s%s%svia %s",
+ (state -> offer
+ ? (state -> offer == DHCPACK ? "DHCPACK" : "DHCPOFFER")
+ : "BOOTREPLY"),
+ piaddr (lease -> ip_addr),
+ (lease -> hardware_addr.hlen
+ ? print_hw_addr (lease -> hardware_addr.hbuf [0],
+ lease -> hardware_addr.hlen - 1,
+ &lease -> hardware_addr.hbuf [1])
+ : print_hex_1(lease->uid_len, lease->uid, 60)),
+ s ? "(" : "", s ? s : "", s ? ") " : "",
+ addrbuf);
+
+ memset (&to, 0, sizeof to);
+ to.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ to.sin6_len = sizeof to;
+#endif
+
+#ifdef DEBUG_PACKET
+ dump_raw ((unsigned char *)&raw, packet_length);
+#endif
+
+ /* Make sure outgoing packets are at least as big
+ as a BOOTP packet. */
+ if (packet_length < BOOTP_MIN_LEN)
+ packet_length = BOOTP_MIN_LEN;
+
+ /* this was gatewayed, send it back to the gateway... */
+ memcpy(&to.sin6_addr, state->from.iabuf, 16);
+ to.sin6_port = local_port;
+
+ result = send_packet6(state->ip,
+ (unsigned char *)&raw,
+ packet_length, &to);
+ if (result < 0) {
+ log_error("%s:%d: Failed to send %d byte long "
+ "packet over %s interface.", MDL,
+ packet_length, state->ip->name);
+ }
+
+ /* Free all of the entries in the option_state structure
+ now that we're done with them. */
+
+ free_lease_state (state, MDL);
+ lease -> state = (struct lease_state *)0;
+}
+
+int locate_network_tsv (packet)
+ struct packet *packet;
+{
+ struct iaddr ia;
+ struct data_string data;
+ struct subnet *subnet = (struct subnet *)0;
+ struct option_cache *oc;
+ int sso = 0;
+
+ /* See if there's a Relay Agent Link Selection Option, or a
+ * Subnet Selection Option. The Link-Select and Subnet-Select
+ * are formatted and used precisely the same, but we must prefer
+ * the link-select over the subnet-select.
+ */
+ if ((oc = lookup_option(&agent_universe, packet->options,
+ RAI_LINK_SELECT)) == NULL)
+ oc = lookup_option(&dhcp_universe, packet->options,
+ DHO_SUBNET_SELECTION);
+ if (oc)
+ sso = 1;
+
+ /* See if there is a Relay Agent CRA6ADDR Option. */
+ if (!sso)
+ oc = lookup_option(&agent_universe, packet->options,
+ RAI_CRA6ADDR);
+ if (!sso && oc) {
+ memset (&data, 0, sizeof data);
+ if (!evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ return 0;
+ }
+ if (data.len != 16) {
+ return 0;
+ }
+ ia.len = 16;
+ memcpy (ia.iabuf, data.data, 16);
+ data_string_forget (&data, MDL);
+
+ /* Get the subnet of this IPv6 address. */
+ if (find_subnet (&subnet, ia, MDL)) {
+ shared_network_reference (&packet -> shared_network,
+ subnet -> shared_network,
+ MDL);
+ subnet_dereference (&subnet, MDL);
+ return 1;
+ }
+ }
+
+ /* If there's an option indicating link connection, and it's valid,
+ * use it to figure out the subnet. If it's not valid, fail.
+ */
+ if (sso) {
+ memset (&data, 0, sizeof data);
+ if (!evaluate_option_cache (&data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options,
+ (struct option_state *)0,
+ &global_scope, oc, MDL)) {
+ return 0;
+ }
+ if (data.len != 4) {
+ return 0;
+ }
+ ia.len = 4;
+ memcpy (ia.iabuf, data.data, 4);
+ data_string_forget (&data, MDL);
+ } else {
+ /* Always available. */
+ ia.len = 16;
+ memcpy (ia.iabuf, packet->client_addr.iabuf, 16);
+ }
+
+ /* If we know the subnet on which the IP address lives, use it. */
+ if (find_subnet (&subnet, ia, MDL)) {
+ shared_network_reference (&packet -> shared_network,
+ subnet -> shared_network, MDL);
+ subnet_dereference (&subnet, MDL);
+ return 1;
+ }
+
+ /* Otherwise, fail. */
+ return 0;
+}
+
+/*
+ * Look for the lowest numbered site code number and
+ * apply a log warning if it is less than 224. Do not
+ * permit site codes less than 128 (old code never did).
+ *
+ * Note that we could search option codes 224 down to 128
+ * on the hash table, but the table is (probably) smaller
+ * than that if it was declared as a standalone table with
+ * defaults. So we traverse the option code hash.
+ */
+static int
+find_min_site_code(struct universe *u)
+{
+ if (u->site_code_min)
+ return u->site_code_min;
+
+ /*
+ * Note that site_code_min has to be global as we can't pass an
+ * argument through hash_foreach(). The value 224 is taken from
+ * RFC 3942.
+ */
+ site_code_min = 224;
+ option_code_hash_foreach(u->code_hash, lowest_site_code);
+
+ if (site_code_min < 224) {
+ log_error("WARNING: site-local option codes less than 224 have "
+ "been deprecated by RFC3942. You have options "
+ "listed in site local space %s that number as low as "
+ "%d. Please investigate if these should be declared "
+ "as regular options rather than site-local options, "
+ "or migrated up past 224.",
+ u->name, site_code_min);
+ }
+
+ /*
+ * don't even bother logging, this is just silly, and never worked
+ * on any old version of software.
+ */
+ if (site_code_min < 128)
+ site_code_min = 128;
+
+ /*
+ * Cache the determined minimum site code on the universe structure.
+ * Note that due to the < 128 check above, a value of zero is
+ * impossible.
+ */
+ u->site_code_min = site_code_min;
+
+ return site_code_min;
+}
+
+static isc_result_t
+lowest_site_code(const void *key, unsigned len, void *object)
+{
+ struct option *option = object;
+
+ if (option->code < site_code_min)
+ site_code_min = option->code;
+
+ return ISC_R_SUCCESS;
+}
+
+static void
+maybe_return_agent_options(struct packet *packet, struct option_state *options)
+{
+ /* If there were agent options in the incoming packet, return
+ * them. Do not return the agent options if they were stashed
+ * on the lease. We do not check giaddr to detect the presence of
+ * a relay, as this excludes "l2" relay agents which have no giaddr
+ * to set.
+ *
+ * XXX: If the user configures options for the relay agent information
+ * (state->options->universes[agent_universe.index] is not NULL),
+ * we're still required to duplicate other values provided by the
+ * relay agent. So we need to merge the old values not configured
+ * by the user into the new state, not just give up.
+ */
+ if (!packet->agent_options_stashed &&
+ (packet->options != NULL) &&
+ packet->options->universe_count > agent_universe.index &&
+ packet->options->universes[agent_universe.index] != NULL &&
+ (options->universe_count <= agent_universe.index ||
+ options->universes[agent_universe.index] == NULL)) {
+ option_chain_head_reference
+ ((struct option_chain_head **)
+ &(options->universes[agent_universe.index]),
+ (struct option_chain_head *)
+ packet->options->universes[agent_universe.index], MDL);
+
+ if (options->universe_count <= agent_universe.index)
+ options->universe_count = agent_universe.index + 1;
+ }
+}
+
+#endif /* DHCPv6 */
diff --git a/tests/t_api_dhcp.c b/tests/t_api_dhcp.c
index 9ef221a1..3524187a 100644
--- a/tests/t_api_dhcp.c
+++ b/tests/t_api_dhcp.c
@@ -14,6 +14,12 @@ void
dhcp(struct packet *packet) {
}
+#ifdef DHCPv6
+void
+dhcp_tsv(struct packet *packet) {
+}
+#endif
+
void
dhcpv6(struct packet *packet) {
}