diff options
author | Francis Dupont <fdupont@isc.org> | 2012-07-18 16:34:09 +0200 |
---|---|---|
committer | Francis Dupont <fdupont@isc.org> | 2012-07-18 16:34:09 +0200 |
commit | 6fe35fdf34d9f052d70f8e55b86367a856a213f2 (patch) | |
tree | 6db4027d2949ec692d61177efe5453fe2cd0d71f | |
parent | 355db4b687b0f66f224f40b0b754a9c500c8e41e (diff) | |
download | isc-dhcp-rt28195b.tar.gz |
rebase 28195 aka DHCPv4 over IPv6 to git in branch rt28195brt28195b
-rw-r--r-- | client/dhclient.c | 6 | ||||
-rw-r--r-- | common/discover.c | 49 | ||||
-rw-r--r-- | common/options.c | 89 | ||||
-rw-r--r-- | common/socket.c | 16 | ||||
-rw-r--r-- | dhcpctl/omshell.c | 1 | ||||
-rw-r--r-- | includes/dhcp.h | 3 | ||||
-rw-r--r-- | includes/dhcpd.h | 27 | ||||
-rw-r--r-- | relay/.cvsignore | 4 | ||||
-rw-r--r-- | relay/Makefile.am | 10 | ||||
-rw-r--r-- | relay/Makefile.in | 32 | ||||
-rw-r--r-- | relay/dhccra.8 | 136 | ||||
-rw-r--r-- | relay/dhccra.c | 695 | ||||
-rw-r--r-- | relay/dhcrelay.8 | 2 | ||||
-rw-r--r-- | relay/dhcrelay.c | 7 | ||||
-rw-r--r-- | relay/dhctra.8 | 133 | ||||
-rw-r--r-- | relay/dhctra.c | 1011 | ||||
-rw-r--r-- | server/Makefile.am | 2 | ||||
-rw-r--r-- | server/Makefile.dist | 4 | ||||
-rw-r--r-- | server/Makefile.in | 19 | ||||
-rw-r--r-- | server/confpars.c | 18 | ||||
-rw-r--r-- | server/db.c | 4 | ||||
-rw-r--r-- | server/dhcp.c | 39 | ||||
-rw-r--r-- | server/dhcpd.8 | 11 | ||||
-rw-r--r-- | server/dhcpd.c | 129 | ||||
-rw-r--r-- | server/dhcpd.conf.5 | 17 | ||||
-rw-r--r-- | server/dhcpleasequery.c | 541 | ||||
-rw-r--r-- | server/mdb.c | 10 | ||||
-rw-r--r-- | server/stables.c | 4 | ||||
-rw-r--r-- | server/tsv.c | 3226 | ||||
-rw-r--r-- | tests/t_api_dhcp.c | 6 |
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 (<, 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, <->ip_addr.iabuf, 4); + nak_lease_tsv(packet, &cip); + free_lease_state (state, MDL); + lease_dereference (<, 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, <->ip_addr.iabuf, 4); + nak_lease_tsv(packet, &cip); + free_lease_state (state, MDL); + lease_dereference (<, 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 (< -> host, host, MDL); + host_dereference (&host, MDL); + } + if (lease -> subnet) + subnet_reference (< -> subnet, lease -> subnet, MDL); + if (lease -> billing_class) + class_reference (< -> 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 (< -> scope, lease -> scope, MDL); + binding_scope_dereference (&lease -> scope, MDL); + } + if (lease -> agent_options) + option_chain_head_reference (< -> 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 (< -> agent_options, MDL); + option_chain_head_reference + (< -> 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 (< -> 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, < -> 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, + < -> 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, + <->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 (<, MDL); + return; + } + } + lease_dereference (<, 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) { } |