summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/DHCPv6/000-badmsgtype.pl164
-rw-r--r--tests/DHCPv6/010-solicit-noclientid.pl212
-rw-r--r--tests/DHCPv6/011-solicit-serverid.pl215
-rw-r--r--tests/DHCPv6/020-advertise-mcast.pl164
-rw-r--r--tests/DHCPv6/030-request-noclientid.pl212
-rw-r--r--tests/DHCPv6/031-request-noserverid.pl212
-rw-r--r--tests/DHCPv6/032-request-badduid.pl216
-rw-r--r--tests/DHCPv6/110-information-request-ia_na.pl212
-rw-r--r--tests/DHCPv6/111-information-request-ia_ta.pl212
-rw-r--r--tests/DHCPv6/112-badduid.pl208
-rw-r--r--tests/DHCPv6/210-solicit-nohost.pl212
-rw-r--r--tests/DHCPv6/211-solicit-opt-in-na.pl218
-rw-r--r--tests/DHCPv6/212-solicit-opt-in-na-norapidcommit.pl218
-rw-r--r--tests/DHCPv6/280-release-nohost.pl175
-rw-r--r--tests/DHCPv6/281-release-bad-address.pl187
-rw-r--r--tests/DHCPv6/282-release-no-address.pl183
-rw-r--r--tests/DHCPv6/283-release.pl189
-rw-r--r--tests/DHCPv6/290-decline-nohost.pl175
-rw-r--r--tests/DHCPv6/291-decline-bad-address.pl187
-rw-r--r--tests/DHCPv6/292-decline-no-address.pl183
-rw-r--r--tests/DHCPv6/293-decline.pl189
-rw-r--r--tests/DHCPv6/README62
-rw-r--r--tests/DHCPv6/dhcp_client.pm454
-rw-r--r--tests/DHCPv6/stubcli-opt-in-na.pl217
-rw-r--r--tests/DHCPv6/stubcli.pl212
-rw-r--r--tests/DHCPv6/test-a.conf67
-rw-r--r--tests/DHCPv6/test-b.conf60
-rw-r--r--tests/HOWTO-unit-test89
-rw-r--r--tests/Makefile.am33
-rw-r--r--tests/Makefile.in562
-rw-r--r--tests/failover/dhcp-1.cf154
-rw-r--r--tests/failover/dhcp-2.cf152
-rwxr-xr-xtests/failover/new-failover28
-rw-r--r--tests/t_api.c828
-rw-r--r--tests/t_api_dhcp.c44
-rw-r--r--tests/unit_test_sample.c25
-rwxr-xr-xtests/unittest.sh.in79
37 files changed, 7209 insertions, 0 deletions
diff --git a/tests/DHCPv6/000-badmsgtype.pl b/tests/DHCPv6/000-badmsgtype.pl
new file mode 100644
index 0000000..d0c35ee
--- /dev/null
+++ b/tests/DHCPv6/000-badmsgtype.pl
@@ -0,0 +1,164 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new(255);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+# timeout parameters
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our bogus packet
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/010-solicit-noclientid.pl b/tests/DHCPv6/010-solicit-noclientid.pl
new file mode 100644
index 0000000..6bb5d6e
--- /dev/null
+++ b/tests/DHCPv6/010-solicit-noclientid.pl
@@ -0,0 +1,212 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_SOLICIT);
+
+# do NOT add the Client Identifier (required by DOCSIS and RFC 3315)
+#$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/011-solicit-serverid.pl b/tests/DHCPv6/011-solicit-serverid.pl
new file mode 100644
index 0000000..cee4ddb
--- /dev/null
+++ b/tests/DHCPv6/011-solicit-serverid.pl
@@ -0,0 +1,215 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_SOLICIT);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server Identifier (NOT ALLOWED by DOCSIS and RFC 3315)
+$msg->add_option($OPT_SERVERID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/020-advertise-mcast.pl b/tests/DHCPv6/020-advertise-mcast.pl
new file mode 100644
index 0000000..c5c4ade
--- /dev/null
+++ b/tests/DHCPv6/020-advertise-mcast.pl
@@ -0,0 +1,164 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_ADVERTISE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+# timeout parameters
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our bogus packet
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/030-request-noclientid.pl b/tests/DHCPv6/030-request-noclientid.pl
new file mode 100644
index 0000000..d5af077
--- /dev/null
+++ b/tests/DHCPv6/030-request-noclientid.pl
@@ -0,0 +1,212 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_REQUEST);
+
+# NOT add the Client Identifier (required by DOCSIS and RFC 3315)
+#$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/031-request-noserverid.pl b/tests/DHCPv6/031-request-noserverid.pl
new file mode 100644
index 0000000..1f73038
--- /dev/null
+++ b/tests/DHCPv6/031-request-noserverid.pl
@@ -0,0 +1,212 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_REQUEST);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/032-request-badduid.pl b/tests/DHCPv6/032-request-badduid.pl
new file mode 100644
index 0000000..30e5040
--- /dev/null
+++ b/tests/DHCPv6/032-request-badduid.pl
@@ -0,0 +1,216 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_REQUEST);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server Identifier (required by DOCSIS and RFC 3315)
+# but use *our* DUID
+$msg->add_option($OPT_SERVERID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/110-information-request-ia_na.pl b/tests/DHCPv6/110-information-request-ia_na.pl
new file mode 100644
index 0000000..5dc6889
--- /dev/null
+++ b/tests/DHCPv6/110-information-request-ia_na.pl
@@ -0,0 +1,212 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_INFORMATION_REQUEST);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/111-information-request-ia_ta.pl b/tests/DHCPv6/111-information-request-ia_ta.pl
new file mode 100644
index 0000000..dc3cf1c
--- /dev/null
+++ b/tests/DHCPv6/111-information-request-ia_ta.pl
@@ -0,0 +1,212 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_INFORMATION_REQUEST);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_TA for each interface (should be IA_NA, by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_TA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/112-badduid.pl b/tests/DHCPv6/112-badduid.pl
new file mode 100644
index 0000000..3be9261
--- /dev/null
+++ b/tests/DHCPv6/112-badduid.pl
@@ -0,0 +1,208 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_INFORMATION_REQUEST);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server Identifier (required by DOCSIS and RFC 3315)
+# but use *our* DUID
+$msg->add_option($OPT_SERVERID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/210-solicit-nohost.pl b/tests/DHCPv6/210-solicit-nohost.pl
new file mode 100644
index 0000000..be9538c
--- /dev/null
+++ b/tests/DHCPv6/210-solicit-nohost.pl
@@ -0,0 +1,212 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_SOLICIT);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/211-solicit-opt-in-na.pl b/tests/DHCPv6/211-solicit-opt-in-na.pl
new file mode 100644
index 0000000..33f6951
--- /dev/null
+++ b/tests/DHCPv6/211-solicit-opt-in-na.pl
@@ -0,0 +1,218 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+$Data::Dumper::Useqq = 1;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_SOLICIT);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+my $client_id = "\x00\x01\x00\x01\x0c\x00\xa1\x41\x00\x06\x5b\x50\x99\xf6";
+#my $client_id = dhcp_client::duid(3);
+$msg->add_option($OPT_CLIENTID, $client_id);
+#$msg->add_option($OPT_CLIENTID, dhcp_client::duid(3));
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ my $enc_opt = dhcp_client::msg->new(0);
+ $enc_opt->add_option($OPT_CLIENTID, $client_id);
+ $option_data .= $enc_opt->packed_options();
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+#print Dumper($reply_msg), "\n";
+ exit(0);
+}
+
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/212-solicit-opt-in-na-norapidcommit.pl b/tests/DHCPv6/212-solicit-opt-in-na-norapidcommit.pl
new file mode 100644
index 0000000..a8d8149
--- /dev/null
+++ b/tests/DHCPv6/212-solicit-opt-in-na-norapidcommit.pl
@@ -0,0 +1,218 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+$Data::Dumper::Useqq = 1;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_SOLICIT);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+my $client_id = "\x00\x01\x00\x01\x0c\x00\xa1\x41\x00\x06\x5b\x50\x99\xf6";
+#my $client_id = dhcp_client::duid(3);
+$msg->add_option($OPT_CLIENTID, $client_id);
+#$msg->add_option($OPT_CLIENTID, dhcp_client::duid(3));
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ my $enc_opt = dhcp_client::msg->new(0);
+ $enc_opt->add_option($OPT_CLIENTID, $client_id);
+ $option_data .= $enc_opt->packed_options();
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+#$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+#print Dumper($reply_msg), "\n";
+ exit(0);
+}
+
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/280-release-nohost.pl b/tests/DHCPv6/280-release-nohost.pl
new file mode 100644
index 0000000..9758cd8
--- /dev/null
+++ b/tests/DHCPv6/280-release-nohost.pl
@@ -0,0 +1,175 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new message
+my $msg = dhcp_client::msg->new($MSG_RELEASE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our message
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/281-release-bad-address.pl b/tests/DHCPv6/281-release-bad-address.pl
new file mode 100644
index 0000000..d130812
--- /dev/null
+++ b/tests/DHCPv6/281-release-bad-address.pl
@@ -0,0 +1,187 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_RELEASE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $iaaddr_option = inet_pton(AF_INET6, "1:2:3:4::");
+ $iaaddr_option .= pack("NN", 0, 0);
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $option_data .= pack("nn", $OPT_IAADDR, length($iaaddr_option)-8);
+ $option_data .= $iaaddr_option;
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/282-release-no-address.pl b/tests/DHCPv6/282-release-no-address.pl
new file mode 100644
index 0000000..b3d0af5
--- /dev/null
+++ b/tests/DHCPv6/282-release-no-address.pl
@@ -0,0 +1,183 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_RELEASE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/283-release.pl b/tests/DHCPv6/283-release.pl
new file mode 100644
index 0000000..a1baae4
--- /dev/null
+++ b/tests/DHCPv6/283-release.pl
@@ -0,0 +1,189 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_RELEASE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+my $client_id = "\x00\x01\x00\x01\x0c\x00\xa1\x41\x00\x06\x5b\x50\x99\xf6";
+$msg->add_option($OPT_CLIENTID, $client_id);
+#$msg->add_option($OPT_CLIENTID, dhcp_client::duid(3));
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $iaaddr_option = inet_pton(AF_INET6, "3ffe:aaaa:aaaa:aaaa::ffff");
+ $iaaddr_option .= pack("NN", 0, 0);
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $option_data .= pack("nn", $OPT_IAADDR, length($iaaddr_option)-8);
+ $option_data .= $iaaddr_option;
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/290-decline-nohost.pl b/tests/DHCPv6/290-decline-nohost.pl
new file mode 100644
index 0000000..02a25e3
--- /dev/null
+++ b/tests/DHCPv6/290-decline-nohost.pl
@@ -0,0 +1,175 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_DECLINE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/291-decline-bad-address.pl b/tests/DHCPv6/291-decline-bad-address.pl
new file mode 100644
index 0000000..d9f0e1e
--- /dev/null
+++ b/tests/DHCPv6/291-decline-bad-address.pl
@@ -0,0 +1,187 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_DECLINE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $iaaddr_option = inet_pton(AF_INET6, "1:2:3:4::");
+ $iaaddr_option .= pack("NN", 0, 0);
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $option_data .= pack("nn", $OPT_IAADDR, length($iaaddr_option)-8);
+ $option_data .= $iaaddr_option;
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/292-decline-no-address.pl b/tests/DHCPv6/292-decline-no-address.pl
new file mode 100644
index 0000000..84c970f
--- /dev/null
+++ b/tests/DHCPv6/292-decline-no-address.pl
@@ -0,0 +1,183 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_DECLINE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/293-decline.pl b/tests/DHCPv6/293-decline.pl
new file mode 100644
index 0000000..960195a
--- /dev/null
+++ b/tests/DHCPv6/293-decline.pl
@@ -0,0 +1,189 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_DECLINE);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+my $client_id = "\x00\x01\x00\x01\x0c\x00\xa1\x41\x00\x06\x5b\x50\x99\xf6";
+$msg->add_option($OPT_CLIENTID, $client_id);
+#$msg->add_option($OPT_CLIENTID, dhcp_client::duid(3));
+
+# add the Server identifier (required by RFC 3315)
+$msg->add_option($OPT_SERVERID, "InfiniteEntropy");
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $iaaddr_option = inet_pton(AF_INET6, "3ffe:aaaa:aaaa:aaaa::ffff");
+ $iaaddr_option .= pack("NN", 0, 0);
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $option_data .= pack("nn", $OPT_IAADDR, length($iaaddr_option)-8);
+ $option_data .= $iaaddr_option;
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/README b/tests/DHCPv6/README
new file mode 100644
index 0000000..c559de2
--- /dev/null
+++ b/tests/DHCPv6/README
@@ -0,0 +1,62 @@
+In order to test the DHCPv6 server, we have a configuration file with
+known values, and some Perl scripts designed to send and receive
+DHCPv6 packets to check various code paths.
+
+It is not complete test converage by any means, but it should be
+fairly easy to add additional tests as needed.
+
+The scripts themselves are not very well written. There is a lot of
+copied code, poor error handling, and so on. These should be rewritten
+at some point.
+
+To use, the DHCPv6 server must be running in test mode to send back to
+the originating port. (The scripts can be changed to bind to the
+appropriate client port, but they don't now, and have to run as root
+to do this). In server/dhcpv6.c, look for this comment:
+
+/* For testing, we reply to the sending port, so we don't need a root */
+/* client */
+ to_addr.sin6_port = remote_port;
+/* to_addr.sin6_port = packet->client_port;*/
+
+And change the code to use the client_port value.
+
+You will need to modify one of the test configuration files to use one
+of the physical subnets that your machine uses, in the subnet6
+statement.
+
+Then run the server as root, in debug mode:
+
+# touch /tmp/test.leases
+# dhcpd -cf test-a.conf -lf /tmp/test.leases -d
+
+You can invoke the scripts then:
+
+$ perl 000-badmsgtype.pl
+
+The expected results vary per script, depending on the behavior that
+is being tested.
+
+
+Notes about scripts:
+
+In order to manipulate IPv6 addresses, we need the Socket6 library,
+available from CPAN:
+
+http://search.cpan.org/~umemoto/Socket6-0.19/Socket6.pm
+
+The Perl that Sun issues for Solaris 10 is compiled with the Sun
+compiler. If you have the Sun compiler, then this will work fine.
+Otherwise you may need to install Perl from source.
+
+We need to get the hardware address in order to build DUID properly.
+The IO::Interface module reports hardware address, but not on Solaris
+10 it seems. Rather than do this the "right way", we do it the "Perl
+way", and hack it. "ifconfig" does return the Ethernet address, but
+only to the root user. However, we can look for files of the name
+/etc/hostname.*, get the IP address from "ifconfig", and then check
+for those addresses in the ARP table.
+
+Client DUID is supposed to be an opaque value to the server, but we go
+ahead and make a "real" type 1 or type 3 DUID.
+
diff --git a/tests/DHCPv6/dhcp_client.pm b/tests/DHCPv6/dhcp_client.pm
new file mode 100644
index 0000000..5caa377
--- /dev/null
+++ b/tests/DHCPv6/dhcp_client.pm
@@ -0,0 +1,454 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+package dhcp_client;
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+# message types
+$MSG_SOLICIT = 1;
+$MSG_ADVERTISE = 2;
+$MSG_REQUEST = 3;
+$MSG_CONFIRM = 4;
+$MSG_RENEW = 5;
+$MSG_REBIND = 6;
+$MSG_REPLY = 7;
+$MSG_RELEASE = 8;
+$MSG_DECLINE = 9;
+$MSG_RECONFIGURE = 10;
+$MSG_INFORMATION_REQUEST = 11;
+$MSG_RELAY_FORW = 12;
+$MSG_RELAY_REPL = 13;
+
+# option numbers
+$OPT_CLIENTID = 1;
+$OPT_SERVERID = 2;
+$OPT_IA_NA = 3;
+$OPT_IA_TA = 4;
+$OPT_IAADDR = 5;
+$OPT_ORO = 6;
+$OPT_PREFERENCE = 7;
+$OPT_ELAPSED_TIME = 8;
+$OPT_RELAY_MSG = 9;
+$OPT_AUTH = 11;
+$OPT_UNICAST = 12;
+$OPT_STATUS_CODE = 13;
+$OPT_RAPID_COMMIT = 14;
+$OPT_USER_CLASS = 15;
+$OPT_VENDOR_CLASS = 16;
+$OPT_VENDOR_OPTS = 17;
+$OPT_INTERFACE_ID = 18;
+$OPT_RECONF_MSG = 19;
+$OPT_RECONF_ACCEPT = 20;
+
+# timeouts
+$SOL_MAX_DELAY = 1;
+$SOL_TIMEOUT = 1;
+$SOL_MAX_RT = 120;
+$REQ_TIMEOUT = 1;
+$REQ_MAX_RT = 30;
+$REQ_MAX_RC = 10;
+$CNF_MAX_DELAY = 1;
+$CNF_MAX_RT = 4;
+$CNF_MAX_RD = 10;
+$REN_TIMEOUT = 10;
+$REN_MAX_RT = 600;
+$REB_TIMEOUT = 10;
+$REB_MAX_RT = 600;
+$INF_MAX_DELAY = 1;
+$INF_TIMEOUT = 1;
+$INF_MAX_RT = 120;
+$REL_TIMEOUT = 1;
+$REL_MAX_RC = 5;
+$DEC_TIMEOUT = 1;
+$DEC_MAX_RC = 5;
+$REC_TIMEOUT = 2;
+$REC_MAX_RC = 8;
+$HOP_COUNT_LIMIT = 32;
+
+@EXPORT = qw( $MSG_SOLICIT $MSG_ADVERTISE $MSG_REQUEST $MSG_CONFIRM
+ $MSG_RENEW $MSG_REBIND $MSG_REPLY $MSG_RELEASE $MSG_DECLINE
+ $MSG_RECONFIGURE $MSG_INFORMATION_REQUEST $MSG_RELAY_FORW
+ $MSG_RELAY_REPL
+ $OPT_CLIENTID $OPT_SERVERID $OPT_IA_NA $OPT_IA_TA $OPT_IAADDR
+ $OPT_ORO $OPT_PREFERENCE $OPT_ELAPSED_TIME $OPT_RELAY_MSG
+ $OPT_AUTH $OPT_UNICAST $OPT_STATUS_CODE $OPT_RAPID_COMMIT
+ $OPT_USER_CLASS $OPT_VENDOR_CLASS $OPT_VENDOR_OPTS
+ $OPT_INTERFACE_ID $OPT_RECONF_MSG $OPT_RECONF_ACCEPT
+ $SOL_MAX_DELAY $SOL_TIMEOUT $SOL_MAX_RT $REQ_TIMEOUT
+ $REQ_MAX_RT $REQ_MAX_RC $CNF_MAX_DELAY $CNF_MAX_RT
+ $CNF_MAX_RD $REN_TIMEOUT $REN_MAX_RT $REB_TIMEOUT $REB_MAX_RT
+ $INF_MAX_DELAY $INF_TIMEOUT $INF_MAX_RT $REL_TIMEOUT
+ $REL_MAX_RC $DEC_TIMEOUT $DEC_MAX_RC $REC_TIMEOUT $REC_MAX_RC
+ $HOP_COUNT_LIMIT );
+
+my %msg_type_num = (
+ MSG_SOLICIT => 1,
+ MSG_ADVERTISE => 2,
+ MSG_REQUEST => 3,
+ MSG_CONFIRM => 4,
+ MSG_RENEW => 5,
+ MSG_REBIND => 6,
+ MSG_REPLY => 7,
+ MSG_RELEASE => 8,
+ MSG_DECLINE => 9,
+ MSG_RECONFIGURE => 10,
+ MSG_INFORMATION_REQUEST => 11,
+ MSG_RELAY_FORW => 12,
+ MSG_RELAY_REPL => 13,
+);
+my %msg_num_type = reverse(%msg_type_num);
+
+my %opt_type_num = (
+ OPT_CLIENTID => 1,
+ OPT_SERVERID => 2,
+ OPT_IA_NA => 3,
+ OPT_IA_TA => 4,
+ OPT_IAADDR => 5,
+ OPT_ORO => 6,
+ OPT_PREFERENCE => 7,
+ OPT_ELAPSED_TIME => 8,
+ OPT_RELAY_MSG => 9,
+ OPT_AUTH => 11,
+ OPT_UNICAST => 12,
+ OPT_STATUS_CODE => 13,
+ OPT_RAPID_COMMIT => 14,
+ OPT_USER_CLASS => 15,
+ OPT_VENDOR_CLASS => 16,
+ OPT_VENDOR_OPTS => 17,
+ OPT_INTERFACE_ID => 18,
+ OPT_RECONF_MSG => 19,
+ OPT_RECONF_ACCEPT => 20,
+);
+my %opt_num_type = reverse(%opt_type_num);
+
+my %status_code_num = (
+ Success => 0,
+ UnspecFail => 1,
+ NoAddrsAvail => 2,
+ NoBinding => 3,
+ NotOnLink => 4,
+ UseMulticast => 5,
+);
+my %status_num_code = reverse(%status_code_num);
+
+my %docsis_type_num = (
+ CL_OPTION_ORO => 1,
+ CL_OPTION_TFTP_SERVERS => 32,
+ CL_OPTION_CONFIG_FILE_NAME => 33,
+ CL_OPTION_SYSLOG_SERVERS => 34,
+ CL_OPTION_TLV5 => 35,
+ CL_OPTION_DEVICE_ID => 36,
+ CL_OPTION_CCC => 37,
+ CL_OPTION_DOCSIS_VERS => 38,
+);
+my %docsis_num_type = reverse(%docsis_type_num);
+
+use strict;
+use English;
+use POSIX;
+
+# XXX: very Solaris-specific
+sub iface {
+ my @ifaces;
+ foreach my $fname (glob("/etc/hostname.*")) {
+ $fname =~ s[^/etc/hostname.][];
+ push(@ifaces, $fname);
+ }
+ return wantarray() ? @ifaces : $ifaces[0];
+}
+
+# XXX: very Solaris-specific
+sub mac_addr {
+ my @ip_addrs;
+ foreach my $iface (iface()) {
+ if (`ifconfig $iface 2>/dev/null` =~ /\sinet (\S+)\s/) {
+ push(@ip_addrs, $1);
+ }
+ }
+ my @mac_addrs;
+ foreach my $line (split(/\n/, `arp -an 2>/dev/null`)) {
+ my @parts = split(/\s+/, $line);
+ my $ip = $parts[1];
+ my $mac = $parts[-1];
+ if (grep { $ip eq $_ } @ip_addrs) {
+ $mac =~ s/://g;
+ push(@mac_addrs, $mac);
+ }
+ }
+ return wantarray() ? @mac_addrs : $mac_addrs[0];
+}
+
+sub mac_addr_binary {
+ my @mac_addr = split(//, mac_addr());
+ my $mac_addr = join("", map { chr(hex($_)) } @mac_addr);
+ return $mac_addr;
+}
+
+# DHCPv6 times start 2000-01-01 00:00:00
+my $dhcp_time_base = 946684800;
+#{
+# local $ENV{TZ} = "UTC";
+# POSIX::tzset();
+# $dhcp_time_base = POSIX::mktime(0, 0, 0, 1, 0, 100);
+#}
+
+sub dhcpv6_time {
+ return time() - $dhcp_time_base;
+}
+
+sub duid {
+ my ($type) = @_;
+
+ $type = 1 unless (defined $type);
+
+ if (($type == 1) || ($type == 3)) {
+ my $mac_addr = mac_addr_binary();
+ if ($type == 1) {
+ my $time = pack("N", dhcpv6_time());
+ return "\x00\x01\x00\x01${time}${mac_addr}";
+ } else {
+ return "\x00\x03\x00\x01${mac_addr}";
+ }
+ } else {
+ die "Unknown DUID type $type requested";
+ }
+}
+
+package dhcp_client::msg;
+
+use Socket;
+use Socket6;
+
+sub new {
+ my ($pkg, $msg_type, $trans_id) = @_;
+
+ my $this = {};
+ bless $this;
+
+ $this->{msg_type} = $msg_type+0;
+ if (defined $trans_id) {
+ $this->{trans_id} = $trans_id;
+ } else {
+ $this->{trans_id} = chr(rand(256)) .
+ chr(rand(256)) . chr(rand(256));
+ }
+ $this->{options} = [ ];
+
+ return $this;
+}
+
+
+sub add_option {
+ my ($this, $num, $data) = @_;
+
+ push(@{$this->{options}}, [ $num, $data ]);
+}
+
+sub get_option {
+ my ($this, $num) = @_;
+ my @options;
+ foreach my $option (@{$this->{options}}) {
+ if ($option->[0] == $num) {
+ push(@options, $option->[1]);
+ }
+ }
+ return wantarray() ? @options : $options[0];
+}
+
+sub packed_options {
+ my ($this) = @_;
+
+ my $options = "";
+ foreach my $option (@{$this->{options}}) {
+ $options .= pack("nn", $option->[0], length($option->[1]));
+ $options .= $option->[1];
+ }
+ return $options;
+}
+
+sub packet {
+ my ($this) = @_;
+
+ my $packet = "";
+ $packet .= chr($this->{msg_type});
+ $packet .= $this->{trans_id};
+ $packet .= $this->packed_options();
+ return $packet;
+}
+
+sub unpack_options {
+ my ($options) = @_;
+
+ my @parsed_options;
+ my $p = 0;
+ while ($p < length($options)) {
+ my ($id, $len) = unpack("nn", substr($options, $p, 4));
+ push(@parsed_options, [ $id, substr($options, $p + 4, $len) ]);
+ $p += 4 + $len;
+ }
+ return @parsed_options;
+}
+
+sub print_docsis_option {
+ my ($num, $data, $indent) = @_;
+
+ print "${indent}DOCSIS Option $num";
+ if ($docsis_num_type{$num}) {
+ print " ($docsis_num_type{$num})";
+ }
+ print ", length ", length($data), "\n";
+
+ return unless ($docsis_num_type{$num});
+
+ if ($docsis_num_type{$num} eq "CL_OPTION_ORO") {
+ my $num_oro = length($data) / 2;
+ for (my $i=0; $i<$num_oro; $i++) {
+ my $oro_num = unpack("n", substr($data, $i*2, 2));
+ print "${indent} $oro_num";
+ if ($docsis_num_type{$oro_num}) {
+ print " ($docsis_num_type{$oro_num})";
+ }
+ print "\n";
+ }
+ } elsif ($docsis_num_type{$num} eq "CL_OPTION_TFTP_SERVERS") {
+ my $num_servers = length($data) / 16;
+ for (my $i=0; $i<$num_servers; $i++) {
+ my $srv = inet_ntop(AF_INET6, substr($data, $i*16, 16));
+ print "$indent TFTP server ", ($i+1), ": ";
+ print uc($srv), "\n";
+ }
+ } elsif ($docsis_num_type{$num} eq "CL_OPTION_CONFIG_FILE_NAME") {
+ print "$indent Config file name: \"$data\"\n"
+ } elsif ($docsis_num_type{$num} eq "CL_OPTION_SYSLOG_SERVERS") {
+ my $num_servers = length($data) / 16;
+ for (my $i=0; $i<$num_servers; $i++) {
+ my $srv = inet_ntop(AF_INET6, substr($data, $i*16, 16));
+ print "$indent syslog server ", ($i+1), ": ";
+ print uc($srv), "\n";
+ }
+ }
+}
+
+sub print_option {
+ my ($num, $data, $indent) = @_;
+
+ print "${indent}Option $num";
+ if ($opt_num_type{$num}) {
+ print " ($opt_num_type{$num})";
+ }
+ print ", length ", length($data), "\n";
+ if ($num == $dhcp_client::OPT_ORO) {
+ my $num_oro = length($data) / 2;
+ for (my $i=0; $i<$num_oro; $i++) {
+ my $oro_num = unpack("n", substr($data, $i*2, 2));
+ print "${indent} $oro_num";
+ if ($opt_num_type{$oro_num}) {
+ print " ($opt_num_type{$oro_num})";
+ }
+ print "\n";
+ }
+ } elsif (($num == $dhcp_client::OPT_CLIENTID) ||
+ ($num == $dhcp_client::OPT_SERVERID)) {
+ print $indent, " ";
+ if (length($data) > 0) {
+ printf '%02X', ord(substr($data, 0, 1));
+ for (my $i=1; $i<length($data); $i++) {
+ printf ':%02X', ord(substr($data, $i, 1));
+ }
+ }
+ print "\n";
+ } elsif ($num == $dhcp_client::OPT_IA_NA) {
+ printf "${indent} IAID: 0x\%08X\n",
+ unpack("N", substr($data, 0, 4));
+ printf "${indent} T1: \%d\n", unpack("N", substr($data, 4, 4));
+ printf "${indent} T2: \%d\n", unpack("N", substr($data, 8, 4));
+ if (length($data) > 12) {
+ printf "${indent} IA_NA encapsulated options:\n";
+ foreach my $option (unpack_options(substr($data, 12))) {
+ print_option(@{$option}, $indent . " ");
+ }
+ }
+ } elsif ($num == $dhcp_client::OPT_IAADDR) {
+ printf "${indent} IPv6 address: \%s\n",
+ uc(inet_ntop(AF_INET6, substr($data, 0, 16)));
+ printf "${indent} Preferred lifetime: \%d\n",
+ unpack("N", substr($data, 16, 4));
+ printf "${indent} Valid lifetime: \%d\n",
+ unpack("N", substr($data, 20, 4));
+ if (length($data) > 24) {
+ printf "${indent} IAADDR encapsulated options:\n";
+ foreach my $option (unpack_options(substr($data, 24))) {
+ print_option(@{$option}, $indent . " ");
+ }
+ }
+ } elsif ($num == $dhcp_client::OPT_VENDOR_OPTS) {
+ my $enterprise_number = unpack("N", substr($data, 0, 4));
+ print "${indent} Enterprise number: $enterprise_number\n";
+
+ # DOCSIS
+ if ($enterprise_number == 4491) {
+ foreach my $option (unpack_options(substr($data, 4))) {
+ print_docsis_option(@{$option}, $indent . " ");
+ }
+ }
+ } elsif ($num == $dhcp_client::OPT_STATUS_CODE) {
+ my $code = ord(substr($data, 0, 1));
+ my $msg = substr($data, 1);
+ print "${indent} Code: $code";
+ if ($status_num_code{$code}) {
+ print " ($status_num_code{$code})";
+ }
+ print "\n";
+ print "${indent} Message: \"$msg\"\n";
+ }
+}
+
+# XXX: we aren't careful about packet boundaries and values...
+# DO NOT RUN ON PRODUCTION SYSTEMS!!!
+sub decode {
+ my ($packet, $print) = @_;
+
+ my $msg_type = ord(substr($packet, 0, 1));
+ my $trans_id = substr($packet, 1, 3);
+ my $msg = dhcp_client::msg->new($msg_type, $trans_id);
+
+ if ($print) {
+ print "DHCPv6 packet\n";
+ print " Message type: $msg_num_type{$msg_type}\n";
+ printf " Transaction id: 0x\%02X\%02X\%02X\n",
+ ord(substr($trans_id, 0, 1)),
+ ord(substr($trans_id, 1, 1)),
+ ord(substr($trans_id, 2, 1));
+ print " Options:\n";
+ }
+
+ foreach my $option (unpack_options(substr($packet, 4))) {
+ print_option(@{$option}, " ") if ($print);
+ $msg->add_option(@{$option});
+ }
+
+ return $msg;
+}
+
diff --git a/tests/DHCPv6/stubcli-opt-in-na.pl b/tests/DHCPv6/stubcli-opt-in-na.pl
new file mode 100644
index 0000000..d68ba09
--- /dev/null
+++ b/tests/DHCPv6/stubcli-opt-in-na.pl
@@ -0,0 +1,217 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+$Data::Dumper::Useqq = 1;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_SOLICIT);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+my $client_id = "\x00\x01\x00\x01\x0c\x00\xa1\x41\x00\x06\x5b\x50\x99\xf6";
+$msg->add_option($OPT_CLIENTID, $client_id);
+#$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ my $enc_opt = dhcp_client::msg->new(0);
+ $enc_opt->add_option($OPT_CLIENTID, $client_id);
+ $option_data .= $enc_opt->packed_options();
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+#print Dumper($reply_msg), "\n";
+ exit(0);
+}
+
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/stubcli.pl b/tests/DHCPv6/stubcli.pl
new file mode 100644
index 0000000..be9538c
--- /dev/null
+++ b/tests/DHCPv6/stubcli.pl
@@ -0,0 +1,212 @@
+#! /usr/bin/perl -w
+
+# Copyright (c) 2007,2009 by Internet Systems Consortium, Inc. ("ISC")
+#
+# 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/
+
+use strict;
+use English;
+use Time::HiRes qw( sleep );
+use Socket;
+use Socket6;
+use IO::Select;
+
+use dhcp_client;
+
+# XXX: for debugging
+use Data::Dumper;
+
+# not-yet-standard options
+my $OPT_TIME_SERVERS = 40;
+my $OPT_TIME_OFFSET = 41;
+
+# DOCSIS sub-options
+my $DOCSIS_OPT_ORO = 1;
+# 2 to 31 are reserved
+my $DOCSIS_OPT_TFTP_SERVERS = 32;
+my $DOCSIS_OPT_CONFIG_FILE_NAME = 33;
+my $DOCSIS_OPT_SYSLOG_SERVERS = 34;
+my $DOCSIS_OPT_TLV5 = 35;
+my $DOCSIS_OPT_DEVICE_ID = 36;
+my $DOCSIS_OPT_CCC = 37;
+my $DOCSIS_OPT_VERS = 38;
+
+# well-known addresses
+my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2";
+my $All_DHCP_Servers = "ff05::1:3";
+
+# ports
+my $client_port = 546;
+my $server_port = 547;
+
+# create a new Solicit message
+my $msg = dhcp_client::msg->new($MSG_SOLICIT);
+
+# add the Client Identifier (required by DOCSIS and RFC 3315)
+$msg->add_option($OPT_CLIENTID, dhcp_client::duid());
+
+# add Elapsed Time, set to 0 on first packet (required by RFC 3315)
+$msg->add_option($OPT_ELAPSED_TIME, "\x00\x00");
+
+# add IA_NA for each interface (required by DOCSIS and RFC 3315)
+# XXX: should this be a single interface only?
+my $iaid = 0;
+foreach my $iface (dhcp_client::iface()) {
+ my $option_data = pack("NNN", ++$iaid, 0, 0);
+ $msg->add_option($OPT_IA_NA, $option_data);
+}
+
+# add Reconfigure Accept (required by DOCSIS)
+$msg->add_option($OPT_RECONF_ACCEPT, "");
+
+# add Options Request (required by DOCSIS, recommended by RFC 3315)
+my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET );
+$msg->add_option($OPT_ORO, pack("n*", @oro));
+
+
+# add Vendor Class option (required by DOCSIS)
+$msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0");
+
+# add Vendor-specific Information Option option (required by DOCSIS)
+my $vsio = pack("N", 4491);
+
+# ORO (required by DOCSIS)
+my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS );
+$vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro);
+
+# TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS)
+my $tlv5_data = "\x01\x02\x03\x0";
+$vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data;
+
+# DOCSIS Device (required by DOCSIS)
+my $docsis_device_id = dhcp_client::mac_addr_binary();
+$vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id));
+$vsio .= $docsis_device_id;
+
+$msg->add_option($OPT_VENDOR_OPTS, $vsio);
+
+# add Rapid Commit option (required by DOCSIS)
+$msg->add_option($OPT_RAPID_COMMIT, "");
+
+# timeout parameters, from DOCSIS
+my $IRT = $SOL_TIMEOUT;
+my $MRT = $SOL_MAX_RT;
+my $MRC = 4; # DOCSIS says 4, RFC 3315 says it SHOULD be 0
+my $MRD = 0;
+
+# sleep a random amount of time between 0 and 1 second, required by RFC 3315
+# XXX: this seems pretty stupid
+sleep(rand($SOL_MAX_DELAY));
+
+my $RT;
+my $count = 0;
+my $mrd_end_time;
+if ($MRD != 0) {
+ $mrd_end_time = time() + $MRD;
+}
+my $reply_msg;
+do {
+ # create our socket, and send our Solicit
+ socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die;
+ my $addr = inet_pton(AF_INET6, $All_DHCP_Servers);
+ my $packet = $msg->packet();
+ my $send_ret = send(SOCK, $packet, 0,
+ pack_sockaddr_in6($server_port, $addr));
+ if (not defined($send_ret)) {
+ printf STDERR
+ "Error \%d sending DHCPv6 Solicit message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ } elsif ($send_ret != length($packet)) {
+ print STDERR "Unable to send entire DHCPv6 Solicit message.\n";
+ exit(1);
+ }
+ $count++;
+
+ my $RAND = rand(0.2) - 0.1;
+ if (defined $RT) {
+ $RT = 2*$RT + $RAND*$RT;
+ if (($RT > $MRT) && ($MRT != 0)) {
+ $RT = $MRT + $RAND*$RT;
+ }
+ } else {
+ $RT = $IRT + $RAND*$IRT;
+ }
+
+ my $rt_end_time = time() + $RT;
+ if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) {
+ $rt_end_time = $mrd_end_time;
+ }
+
+ for (;;) {
+ my $timeout = $rt_end_time - time();
+ if ($timeout < 0) {
+# print STDERR "Timeout waiting for DHCPv6 Advertise ",
+# "or Reply message.\n";
+ last;
+ }
+
+ my @ready = IO::Select->new(\*SOCK)->can_read($timeout);
+
+ if (@ready) {
+ my $reply;
+ my $recv_ret;
+
+ $recv_ret = recv(SOCK, $reply, 1500, 0);
+ if (not defined $recv_ret) {
+ printf STDERR
+ "Error \%d receiving DHCPv6 " .
+ "message;\n\%s\n",
+ 0+$ERRNO, $ERRNO;
+ exit(1);
+ }
+
+ $reply_msg = dhcp_client::msg::decode($reply, 1);
+ if (($reply_msg->{msg_type} == $MSG_ADVERTISE) ||
+ ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ last;
+ }
+ }
+ }
+
+} until ($reply_msg ||
+ (($MRC != 0) && ($count > $MRC)) ||
+ (defined($mrd_end_time) && ($mrd_end_time > time())));
+
+unless ($reply_msg) {
+ if (($MRC != 0) && ($count >= $MRC)) {
+ print STDERR
+ "No reply after maximum retransmission count.\n";
+ } else {
+ print STDERR
+ "No reply after maximum retransmission duration.\n";
+ }
+}
+
+if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) {
+ print "Got DHCPv6 Reply message.\n";
+ exit(0);
+}
+
+#$Data::Dumper::Useqq = 1;
+#print Dumper($msg), "\n";
+#print Dumper($msg->packet()), "\n";
+#
+#print "packet length: ", length($msg->packet()), "\n";
+
diff --git a/tests/DHCPv6/test-a.conf b/tests/DHCPv6/test-a.conf
new file mode 100644
index 0000000..9fe4236
--- /dev/null
+++ b/tests/DHCPv6/test-a.conf
@@ -0,0 +1,67 @@
+#
+# Define the DHCPv6 option space.
+#
+# Option numbers are assigned by IANA:
+# http://www.iana.org/assignments/dhcpv6-parameters
+#
+option dhcp6.time-servers code 40 = array of ip6-address;
+option dhcp6.time-offset code 41 = signed integer 32;
+
+#
+# Define the DOCSIS option space.
+# TODO: DOCSIS oro definition
+#
+option space docsis code width 2 length width 2;
+option vsio.docsis code 4491 = encapsulate docsis;
+option docsis.tftp-servers code 32 = array of ip6-address;
+option docsis.cablelabs-configuration-file code 33 = text;
+option docsis.cablelabs-syslog-servers code 34 = array of ip6-address;
+option docsis.device-id code 36 = string;
+
+#
+# Declare some options.
+#
+option dhcp6.time-servers 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2;
+option docsis.tftp-servers 3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2;
+
+#
+# DNS server IP address to update dynamically
+#
+ddns-update-style interim;
+ddns-domainname "foo.com";
+
+#
+# Per-host settings.
+#
+host cablemodem-1 {
+ host-identifier option
+ dhcp6.client-id 00:01:00:01:0c:00:a1:41:00:06:5b:50:99:f6;
+ fixed-address6 3ffe:aaaa:aaaa:aaaa::ffff;
+ ddns-domainname "bar.com";
+ option dhcp6.time-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option docsis.tftp-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option dhcp6.time-offset -14400; # -4 hours
+ option docsis.cablelabs-configuration-file "bootfile.cfg";
+ option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+}
+
+#host cablemodem-2 {
+# host-identifier option docsis.device-id 00:06:5B:50:99:F6;
+# option dhcp6.time-servers 3ffe:dddd:aaaa:aaaa::1,
+# 3ffe:dddd:aaaa:aaaa::2;
+# option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1,
+# 3ffe:dddd:aaaa:aaaa::2;
+# option dhcp6.time-offset -14400; # -4 hours
+# option docsis.cablelabs-configuration-file "bootfile.cfg";
+# option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+# 3ffe:aaaa:aaaa:aaaa::2;
+#}
+
+# XXX: for testing
+subnet6 fe80::20c:29ff:fe42:820/128 {
+}
+
+
diff --git a/tests/DHCPv6/test-b.conf b/tests/DHCPv6/test-b.conf
new file mode 100644
index 0000000..4b67380
--- /dev/null
+++ b/tests/DHCPv6/test-b.conf
@@ -0,0 +1,60 @@
+#
+# Define the DHCPv6 option space.
+#
+# Option numbers are assigned by IANA:
+# http://www.iana.org/assignments/dhcpv6-parameters
+#
+option dhcpv6.time-servers code 40 = array of ip6-address;
+option dhcpv6.time-offset code 41 = signed integer 32;
+
+#
+# Define the DOCSIS option space.
+#
+option space docsis code width 2 length width 2;
+option docsis.tftp-servers code 32 = array of ip6-address;
+option docsis.cablelabs-configuration-file code 33 = text;
+option docsis.cablelabs-syslog-servers code 34 = array of ip6-address;
+option docsis.device-id code 36 = string;
+
+#
+# Declare some options.
+#
+option dhcpv6.time-servers 3ffe:bbbb:aaaa:aaaa::1, 3ffe:bbbb:aaaa:aaaa::2;
+option docsis.tftp-servers 3ffe:cccc:aaaa:aaaa::1, 3ffe:cccc:aaaa:aaaa::2;
+
+#
+# DNS server IP address to update dynamically
+#
+ddns-update-style interim;
+ddns-domainname "foo.com";
+
+#
+# Per-host settings.
+#
+host cablemodem-1 {
+ option dhcpv6.client-id 00:01:00:01:0c:00:a1:41:00:06:5b:50:99:f6;
+ fixed-address6 3ffe:aaaa:aaaa:aaaa::ffff;
+ ddns-domainname "bar.com";
+ option dhcpv6.time-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option docsis.tftp-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+ option dhcpv6.time-offset -14400; # -4 hours
+ option docsis.cablelabs-configuration-file "bootfile.cfg";
+ option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+}
+
+host cablemodem-2 {
+ option docsis.device-id 00:06:5B:50:99:F6;
+ option dhcpv6.time-servers 3ffe:dddd:aaaa:aaaa::1,
+ 3ffe:dddd:aaaa:aaaa::2;
+ option docsis.tftp-servers 3ffe:dddd:aaaa:aaaa::1,
+ 3ffe:dddd:aaaa:aaaa::2;
+ option dhcpv6.time-offset -14400; # -4 hours
+ option docsis.cablelabs-configuration-file "bootfile.cfg";
+ option docsis.cablelabs-syslog-servers 3ffe:aaaa:aaaa:aaaa::1,
+ 3ffe:aaaa:aaaa:aaaa::2;
+}
+
+
diff --git a/tests/HOWTO-unit-test b/tests/HOWTO-unit-test
new file mode 100644
index 0000000..e00b7d5
--- /dev/null
+++ b/tests/HOWTO-unit-test
@@ -0,0 +1,89 @@
+Introduction
+------------
+
+That is only a brief overview of tests in ISC DHCP. For more thorough
+description, see ISC DHCP Developer's Guide. You can generate it, by
+having Doxygen installed and doing:
+
+ cd doc
+ make devel
+
+and then opening doc/html/index.html
+
+Tests Overview
+--------------
+
+In DHCP, a unit test exercises a particular piece of code in
+isolation. There is a separate unit test per module or API. Each unit
+test lives in a directory beneath the code it is designed to exercise.
+So, we (will eventually) have:
+
+ server/tests/
+ client/tests/
+ common/tests/
+ dhcpctl/tests/
+
+And so on.
+
+We are using ATF (Automated Test Framework) as a framework to run our
+unit tests. See ISC DHCP Developer's Guide for much more thorough
+description of unit-test and ATF framework in general.
+
+Installing ATF
+--------------
+ATF sources can be downloaded from https://github.com/jmmv/kyua. ATF
+must be configured, compiled and then installed to be available during
+the DHCP configure procedure. Please follow INSTALL file supplied with
+ATF sources (it's essentially the typical ./configure && make &&
+make install procedure).
+
+Beginning with ATF version 0.16, it is necessary to include the following
+options --enable-tools and --disable-shared when configuring ATF:
+
+ configure --prefix=<prefix> --enable-tools --disable-shared
+
+ISC DHCP unittests will run with ATF releases upto 0.19. Beginning with
+ATF 0.20, the tools, atf-run and atf-report required by ISC DHCP, were
+deprecated and are no longer included with ATF.
+
+Running Unit Tests
+------------------
+
+In order to run the unit tests for DHCP, enable ATF support during configure:
+
+$ ./configure --with-atf{=<atf-path>}
+
+ where <atf-path> is the path into which ATF was installed. This would
+ be same value used for --prefix when ATF was configured (default is
+ /usr/local).
+
+And then build ISC_DHCP with:
+
+$ make
+
+Finally build and run unit tests with:
+
+$ make check
+
+This will traverse the source tree running the unit tests in each unit test
+subdirectory. Note that if one or more tests in a unit test subdirectory fail
+the make process will stop. To run all of the tests regardless of outcome,
+use:
+
+$ make -k check
+
+You can run a single test by going to the appropriate test directory
+and invoking the test directly:
+
+$ cd server/tests
+$ make check
+
+Adding a New Unit Test
+----------------------
+
+See ISC DHCP Developer's Guide.
+
+Adding a New Unit Test Program
+------------------------------
+
+See ISC DHCP Developer's Guide.
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..9c6c650
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,33 @@
+EXTRA_DIST = failover/dhcp-1.cf failover/dhcp-2.cf failover/new-failover \
+ DHCPv6/000-badmsgtype.pl \
+ DHCPv6/010-solicit-noclientid.pl \
+ DHCPv6/011-solicit-serverid.pl \
+ DHCPv6/020-advertise-mcast.pl \
+ DHCPv6/030-request-noclientid.pl \
+ DHCPv6/031-request-noserverid.pl \
+ DHCPv6/032-request-badduid.pl \
+ DHCPv6/110-information-request-ia_na.pl \
+ DHCPv6/111-information-request-ia_ta.pl \
+ DHCPv6/112-badduid.pl \
+ DHCPv6/210-solicit-nohost.pl \
+ DHCPv6/211-solicit-opt-in-na.pl \
+ DHCPv6/212-solicit-opt-in-na-norapidcommit.pl \
+ DHCPv6/280-release-nohost.pl \
+ DHCPv6/281-release-bad-address.pl \
+ DHCPv6/282-release-no-address.pl \
+ DHCPv6/283-release.pl \
+ DHCPv6/290-decline-nohost.pl \
+ DHCPv6/291-decline-bad-address.pl \
+ DHCPv6/292-decline-no-address.pl \
+ DHCPv6/293-decline.pl \
+ DHCPv6/README DHCPv6/dhcp_client.pm \
+ DHCPv6/stubcli-opt-in-na.pl DHCPv6/stubcli.pl \
+ DHCPv6/test-a.conf DHCPv6/test-b.conf \
+ HOWTO-unit-test \
+ unit_test_sample.c
+
+AM_CPPFLAGS = -I..
+
+check_LIBRARIES = libt_api.a
+libt_api_a_SOURCES = t_api.c t_api_dhcp.c
+
diff --git a/tests/Makefile.in b/tests/Makefile.in
new file mode 100644
index 0000000..629339d
--- /dev/null
+++ b/tests/Makefile.in
@@ -0,0 +1,562 @@
+# Makefile.in generated by automake 1.14 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = tests
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+ $(srcdir)/unittest.sh.in $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/includes/config.h
+CONFIG_CLEAN_FILES = unittest.sh
+CONFIG_CLEAN_VPATH_FILES =
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libt_api_a_AR = $(AR) $(ARFLAGS)
+libt_api_a_LIBADD =
+am_libt_api_a_OBJECTS = t_api.$(OBJEXT) t_api_dhcp.$(OBJEXT)
+libt_api_a_OBJECTS = $(am_libt_api_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/includes
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libt_api_a_SOURCES)
+DIST_SOURCES = $(libt_api_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+ATF_BIN = @ATF_BIN@
+ATF_CFLAGS = @ATF_CFLAGS@
+ATF_LDFLAGS = @ATF_LDFLAGS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDAP_CFLAGS = @LDAP_CFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_prefix_program = @ac_prefix_program@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+byte_order = @byte_order@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = failover/dhcp-1.cf failover/dhcp-2.cf failover/new-failover \
+ DHCPv6/000-badmsgtype.pl \
+ DHCPv6/010-solicit-noclientid.pl \
+ DHCPv6/011-solicit-serverid.pl \
+ DHCPv6/020-advertise-mcast.pl \
+ DHCPv6/030-request-noclientid.pl \
+ DHCPv6/031-request-noserverid.pl \
+ DHCPv6/032-request-badduid.pl \
+ DHCPv6/110-information-request-ia_na.pl \
+ DHCPv6/111-information-request-ia_ta.pl \
+ DHCPv6/112-badduid.pl \
+ DHCPv6/210-solicit-nohost.pl \
+ DHCPv6/211-solicit-opt-in-na.pl \
+ DHCPv6/212-solicit-opt-in-na-norapidcommit.pl \
+ DHCPv6/280-release-nohost.pl \
+ DHCPv6/281-release-bad-address.pl \
+ DHCPv6/282-release-no-address.pl \
+ DHCPv6/283-release.pl \
+ DHCPv6/290-decline-nohost.pl \
+ DHCPv6/291-decline-bad-address.pl \
+ DHCPv6/292-decline-no-address.pl \
+ DHCPv6/293-decline.pl \
+ DHCPv6/README DHCPv6/dhcp_client.pm \
+ DHCPv6/stubcli-opt-in-na.pl DHCPv6/stubcli.pl \
+ DHCPv6/test-a.conf DHCPv6/test-b.conf \
+ HOWTO-unit-test \
+ unit_test_sample.c
+
+AM_CPPFLAGS = -I..
+check_LIBRARIES = libt_api.a
+libt_api_a_SOURCES = t_api.c t_api_dhcp.c
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign tests/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+unittest.sh: $(top_builddir)/config.status $(srcdir)/unittest.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+clean-checkLIBRARIES:
+ -test -z "$(check_LIBRARIES)" || rm -f $(check_LIBRARIES)
+
+libt_api.a: $(libt_api_a_OBJECTS) $(libt_api_a_DEPENDENCIES) $(EXTRA_libt_api_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libt_api.a
+ $(AM_V_AR)$(libt_api_a_AR) libt_api.a $(libt_api_a_OBJECTS) $(libt_api_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libt_api.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_api.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_api_dhcp.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_LIBRARIES)
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-checkLIBRARIES clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \
+ clean-checkLIBRARIES clean-generic cscopelist-am ctags \
+ ctags-am distclean distclean-compile distclean-generic \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/tests/failover/dhcp-1.cf b/tests/failover/dhcp-1.cf
new file mode 100644
index 0000000..07842ab
--- /dev/null
+++ b/tests/failover/dhcp-1.cf
@@ -0,0 +1,154 @@
+authoritative;
+
+class "even" {
+ match if ((extract-int (suffix
+ (pick-first-value (option dhcp-client-identifier,
+ hardware), 1), 8) % 2) = 0);
+}
+class "odd" {
+ match if ((extract-int (suffix
+ (pick-first-value (option dhcp-client-identifier,
+ hardware), 1), 8) % 2) = 1);
+}
+
+lease-file-name "dhcp-1.leases";
+pid-file-name "dhcp-1.pid";
+ddns-update-style none;
+local-port 50002;
+remote-port 50003;
+omapi-port 50004;
+omapi-key FOO;
+
+default-lease-time 600;
+max-lease-time 600;
+
+failover peer "foo" {
+ primary;
+ address 10.0.0.1;
+ port 51000;
+ peer address 10.0.0.1;
+ peer port 51001;
+ max-response-delay 60;
+ max-unacked-updates 10;
+ mclt 100;
+ hba ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
+ 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00;
+ load balance max seconds 2;
+}
+
+option space SUNW;
+option SUNW.root-mount-options code 1 = text;
+option SUNW.root-server-ip-address code 2 = ip-address;
+option SUNW.root-server-hostname code 3 = text;
+option SUNW.root-path-name code 4 = text;
+option SUNW.swap-server-ip-address code 5 = ip-address;
+option SUNW.swap-file-path code 6 = text;
+option SUNW.boot-file-path code 7 = text;
+option SUNW.posix-timezone-string code 8 = text;
+option SUNW.boot-read-size code 9 = unsigned integer 16;
+option SUNW.install-server-ip-address code 10 = ip-address;
+option SUNW.install-server-hostname code 11 = text;
+option SUNW.install-path code 12 = text;
+option SUNW.sysid-config-file-server code 13 = text;
+option SUNW.JumpStart-server code 14 = text;
+option SUNW.terminal-name code 15 = text;
+
+class "solaris-i86pc" {
+ match if option vendor-class-identifier = "SUNW.i86pc";
+ vendor-option-space SUNW;
+ option SUNW.boot-file-path "/platform/i86pc/kernel/unix";
+ option SUNW.root-path-name "/export/root/i86pc";
+}
+
+class "solaris-sun4u" {
+ match if option vendor-class-identifier = "SUNW.Ultra-5_10";
+ vendor-option-space SUNW;
+ option SUNW.install-path "/export/2/s581_sparc";
+ option SUNW.root-path-name "/export/2/s581_sparc/Solaris_8/Tools/Boot";
+}
+
+option domain-name "connectathon.org.";
+option SUNW.root-server-ip-address 172.16.113.1;
+option SUNW.root-server-hostname "sundhcp-server17-1";
+
+class "sniffer" {
+ match if option host-name = "sniffer";
+}
+
+key FOO {
+ algorithm HMAC-MD5.SIG-ALG.REG.INT;
+ secret ABCD;
+}
+
+zone BISBEE.FUGUE.COM. {
+ primary 127.0.0.1;
+ key FOO;
+}
+
+zone 17.127.10.in-addr.arpa. {
+ primary 127.0.0.1;
+ key FOO;
+}
+
+zone 0.0.10.in-addr.arpa. {
+ primary 127.0.0.1;
+ key FOO;
+}
+
+subnet 204.152.186.128 netmask 255.255.255.192 {
+ not authoritative;
+}
+
+shared-network LOCAL {
+ subnet 127.0.0.0 netmask 255.255.255.0 {
+ }
+ subnet 10.0.2.0 netmask 255.255.255.0 {
+ pool {
+ deny dynamic bootp clients;
+ failover peer "foo";
+ range 10.0.2.100 10.0.2.200;
+ }
+ }
+}
+
+shared-network NET-187 {
+ subnet 204.152.187.0 netmask 255.255.255.0 {
+ }
+ subnet 205.140.116.224 netmask 255.255.255.248 {
+ }
+ subnet 10.0.1.0 netmask 255.255.255.0 {
+ pool {
+ deny dynamic bootp clients;
+ failover peer "foo";
+ range 10.0.1.10 10.0.1.200;
+ }
+ }
+}
+
+subnet 10.0.0.0 netmask 255.255.255.0 {
+ pool {
+ deny dynamic bootp clients;
+ allow members of "even";
+ option impress-servers 10.0.0.0;
+ failover peer "foo";
+ range 10.0.0.10 10.0.0.54;
+ range 10.0.0.100 10.0.0.149;
+ }
+ pool {
+ deny dynamic bootp clients;
+ allow members of "odd";
+ failover peer "foo";
+ option impress-servers 10.0.0.1;
+ range 10.0.0.55 10.0.0.99;
+ range 10.0.0.150 10.0.0.200;
+ }
+ pool {
+ deny dynamic bootp clients;
+ allow members of "sniffer";
+ failover peer "foo";
+ range 10.0.0.9 10.0.0.9;
+ }
+ option routers 10.0.0.1;
+ option domain-name "bisbee.fugue.com";
+ option domain-name-servers 10.0.0.1;
+}
diff --git a/tests/failover/dhcp-2.cf b/tests/failover/dhcp-2.cf
new file mode 100644
index 0000000..c9dfabe
--- /dev/null
+++ b/tests/failover/dhcp-2.cf
@@ -0,0 +1,152 @@
+authoritative;
+
+class "even" {
+ match if ((extract-int (suffix
+ (pick-first-value (option dhcp-client-identifier,
+ hardware), 1), 8) % 2) = 0);
+}
+class "odd" {
+ match if ((extract-int (suffix
+ (pick-first-value (option dhcp-client-identifier,
+ hardware), 1), 8) % 2) = 1);
+}
+
+lease-file-name "dhcp-2.leases";
+pid-file-name "dhcp-2.pid";
+local-port 50000;
+remote-port 50001;
+omapi-port 50005;
+
+ddns-update-style none;
+
+default-lease-time 600;
+max-lease-time 600;
+
+failover peer "foo" {
+ secondary;
+ address 10.0.0.1;
+ port 51001;
+ peer address 10.0.0.1;
+ peer port 51000;
+ max-response-delay 60;
+ max-unacked-updates 10;
+ mclt 100;
+ load balance max seconds 2;
+}
+
+option space SUNW;
+option SUNW.root-mount-options code 1 = text;
+option SUNW.root-server-ip-address code 2 = ip-address;
+option SUNW.root-server-hostname code 3 = text;
+option SUNW.root-path-name code 4 = text;
+option SUNW.swap-server-ip-address code 5 = ip-address;
+option SUNW.swap-file-path code 6 = text;
+option SUNW.boot-file-path code 7 = text;
+option SUNW.posix-timezone-string code 8 = text;
+option SUNW.boot-read-size code 9 = unsigned integer 16;
+option SUNW.install-server-ip-address code 10 = ip-address;
+option SUNW.install-server-hostname code 11 = text;
+option SUNW.install-path code 12 = text;
+option SUNW.sysid-config-file-server code 13 = text;
+option SUNW.JumpStart-server code 14 = text;
+option SUNW.terminal-name code 15 = text;
+
+class "solaris-i86pc" {
+ match if option vendor-class-identifier = "SUNW.i86pc";
+ vendor-option-space SUNW;
+ option SUNW.boot-file-path "/platform/i86pc/kernel/unix";
+ option SUNW.root-path-name "/export/root/i86pc";
+}
+
+class "solaris-sun4u" {
+ match if option vendor-class-identifier = "SUNW.Ultra-5_10";
+ vendor-option-space SUNW;
+ option SUNW.install-path "/export/2/s581_sparc";
+ option SUNW.root-path-name "/export/2/s581_sparc/Solaris_8/Tools/Boot";
+}
+
+option domain-name "connectathon.org.";
+option SUNW.root-server-ip-address 172.16.113.1;
+option SUNW.root-server-hostname "sundhcp-server17-1";
+
+class "sniffer" {
+ match if option host-name = "sniffer";
+}
+
+key FOO {
+ algorithm HMAC-MD5.SIG-ALG.REG.INT;
+ secret ABCD;
+}
+
+zone BISBEE.FUGUE.COM. {
+ primary 127.0.0.1;
+ key FOO;
+}
+
+zone 17.127.10.in-addr.arpa. {
+ primary 127.0.0.1;
+ key FOO;
+}
+
+zone 0.0.10.in-addr.arpa. {
+ primary 127.0.0.1;
+ key FOO;
+}
+
+subnet 204.152.186.128 netmask 255.255.255.192 {
+ not authoritative;
+}
+
+shared-network LOCAL {
+ subnet 127.0.0.0 netmask 255.255.255.0 {
+ }
+ subnet 10.0.2.0 netmask 255.255.255.0 {
+ pool {
+ deny dynamic bootp clients;
+ failover peer "foo";
+ range 10.0.2.100 10.0.2.200;
+ }
+ }
+}
+
+shared-network 187-NET {
+ subnet 204.152.187.0 netmask 255.255.255.0 {
+ }
+ subnet 205.140.116.224 netmask 255.255.255.248 {
+ }
+ subnet 10.0.1.0 netmask 255.255.255.0 {
+ pool {
+ deny dynamic bootp clients;
+ failover peer "foo";
+ range 10.0.1.10 10.0.1.200;
+ }
+ }
+}
+
+subnet 10.0.0.0 netmask 255.255.255.0 {
+ pool {
+ deny dynamic bootp clients;
+ allow members of "even";
+ option impress-servers 10.0.0.0;
+ failover peer "foo";
+ range 10.0.0.10 10.0.0.54;
+ range 10.0.0.100 10.0.0.149;
+ }
+ pool {
+ deny dynamic bootp clients;
+ allow members of "odd";
+ failover peer "foo";
+ option impress-servers 10.0.0.1;
+ range 10.0.0.55 10.0.0.99;
+ range 10.0.0.150 10.0.0.200;
+ }
+ pool {
+ deny dynamic bootp clients;
+ allow members of "sniffer";
+ failover peer "foo";
+ range 10.0.0.9 10.0.0.9;
+ }
+ option routers 10.0.0.1;
+ option domain-name "bisbee.fugue.com";
+ option domain-name-servers 10.0.0.1;
+}
diff --git a/tests/failover/new-failover b/tests/failover/new-failover
new file mode 100755
index 0000000..65d82e0
--- /dev/null
+++ b/tests/failover/new-failover
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+foo=10
+while [ $foo -lt 100 ]; do
+ cat >>dhcp-1.leases <<~
+lease 10.0.0.$foo {
+ starts 4 2001/05/01 02:19:16;
+ ends 5 2021/05/03 02:29:16;
+ binding state active;
+ next binding state free;
+ hardware ethernet 08:00:46:06:6c:23;
+ uid "test-$foo";
+}
+~
+ foo=`expr $foo + 1`
+ cat >>dhcp-2.leases <<~
+lease 10.0.0.$foo {
+ starts 4 2001/04/19 02:19:16;
+ ends 5 2021/04/21 02:29:16;
+ binding state active;
+ next binding state free;
+ hardware ethernet 08:00:46:06:6c:23;
+ uid "test-$foo";
+}
+~
+ foo=`expr $foo + 1`
+done
+
diff --git a/tests/t_api.c b/tests/t_api.c
new file mode 100644
index 0000000..e616226
--- /dev/null
+++ b/tests/t_api.c
@@ -0,0 +1,828 @@
+/*
+ * Copyright (C) 2004, 2005, 2007, 2009 Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 1999-2003 Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and/or 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.
+ */
+
+/* $Id: t_api.c,v 1.4 2009/10/28 04:12:30 sar Exp $ */
+
+/*! \file */
+
+/*
+ * This test API framework is taken from the BIND 9 code. It has been
+ * modified to remove the DNS-specific parts, and the BIND-specific
+ * parts.
+ *
+ * The DNS-specific parts are now wrapped with the DNS_SUPPORT macro,
+ * and the BIND-specific parts are now wrapped with the BIND_SUPPORT
+ * macro.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+
+#include <isc/boolean.h>
+#include <isc/commandline.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/mem.h>
+
+#ifdef DNS_SUPPORT
+#include <dns/compress.h>
+#include <omapip/result.h>
+#endif /* DNS_SUPPORT */
+
+#ifndef BIND_SUPPORT
+#define isc_commandline_parse getopt
+#define isc_commandline_argument optarg
+#define isc_commandline_option optopt
+#endif /* BIND_SUPPORT */
+
+#include "t_api.h"
+#include "cdefs.h"
+
+static const char *Usage =
+ "\t-a : run all tests\n"
+ "\t-b <dir> : chdir to dir before running tests"
+ "\t-c <config_file> : use specified config file\n"
+ "\t-d <debug_level> : set debug level to debug_level\n"
+ "\t-h : print test info\n"
+ "\t-u : print usage info\n"
+ "\t-n <test_name> : run specified test name\n"
+ "\t-t <test_number> : run specified test number\n"
+ "\t-x : don't execute tests in a subproc\n"
+ "\t-q <timeout> : use 'timeout' as the timeout value\n";
+/*!<
+ * -a --> run all tests
+ * -b dir --> chdir to dir before running tests
+ * -c config --> use config file 'config'
+ * -d --> turn on api debugging
+ * -h --> print out available test names
+ * -u --> print usage info
+ * -n name --> run test named name
+ * -tn --> run test n
+ * -x --> don't execute testcases in a subproc
+ * -q timeout --> use 'timeout' as the timeout value
+ */
+
+#define T_MAXTESTS 256 /*% must be 0 mod 8 */
+#define T_MAXENV 256
+#define T_DEFAULT_CONFIG "t_config"
+#define T_BUFSIZ 256
+#define T_BIGBUF 4096
+
+#define T_TCTOUT 60
+
+int T_debug;
+int T_timeout;
+pid_t T_pid;
+static const char * T_config;
+static char T_tvec[T_MAXTESTS / 8];
+static char * T_env[T_MAXENV + 1];
+static char T_buf[T_BIGBUF];
+static char * T_dir;
+
+static int
+t_initconf(const char *path);
+
+static int
+t_dumpconf(const char *path);
+
+static int
+t_putinfo(const char *key, const char *info);
+
+static char *
+t_getdate(char *buf, size_t buflen);
+
+static void
+printhelp(void);
+
+static void
+printusage(void);
+
+static int T_int;
+
+static void
+t_sighandler(int sig) {
+ T_int = sig;
+}
+
+int
+main(int argc, char **argv) {
+ int c;
+ int tnum;
+ int subprocs;
+ pid_t deadpid;
+ int status;
+ int len;
+ isc_boolean_t first;
+ testspec_t *pts;
+ struct sigaction sa;
+
+#ifdef BIND_SUPPORT
+ isc_mem_debugging = ISC_MEM_DEBUGRECORD;
+#endif /* BIND_SUPPORT */
+ first = ISC_TRUE;
+ subprocs = 1;
+ T_timeout = T_TCTOUT;
+
+ /*
+ * -a option is now default.
+ */
+ memset(T_tvec, 0xffff, sizeof(T_tvec));
+
+ /*
+ * Parse args.
+ */
+ while ((c = isc_commandline_parse(argc, argv, ":at:c:d:n:huxq:b:"))
+ != -1) {
+ if (c == 'a') {
+ /*
+ * Flag all tests to be run.
+ */
+ memset(T_tvec, 0xffff, sizeof(T_tvec));
+ }
+ else if (c == 'b') {
+ T_dir = isc_commandline_argument;
+ }
+ else if (c == 't') {
+ tnum = atoi(isc_commandline_argument);
+ if ((tnum > 0) && (tnum < T_MAXTESTS)) {
+ if (first) {
+ /*
+ * Turn off effect of -a default
+ * and allow multiple -t and -n
+ * options.
+ */
+ memset(T_tvec, 0, sizeof(T_tvec));
+ first = ISC_FALSE;
+ }
+ /*
+ * Flag test tnum to be run.
+ */
+ tnum -= 1;
+ T_tvec[tnum / 8] |= (0x01 << (tnum % 8));
+ }
+ }
+ else if (c == 'c') {
+ T_config = isc_commandline_argument;
+ }
+ else if (c == 'd') {
+ T_debug = atoi(isc_commandline_argument);
+ }
+ else if (c == 'n') {
+ pts = &T_testlist[0];
+ tnum = 0;
+ while (pts->pfv != NULL) {
+ if (! strcmp(pts->func_name,
+ isc_commandline_argument)) {
+ if (first) {
+ memset(T_tvec, 0,
+ sizeof(T_tvec));
+ first = ISC_FALSE;
+ }
+ T_tvec[tnum/8] |= (0x01 << (tnum%8));
+ break;
+ }
+ ++pts;
+ ++tnum;
+ }
+ if (pts->pfv == NULL) {
+ fprintf(stderr, "no such test %s\n",
+ isc_commandline_argument);
+ exit(1);
+ }
+ }
+ else if (c == 'h') {
+ printhelp();
+ exit(0);
+ }
+ else if (c == 'u') {
+ printusage();
+ exit(0);
+ }
+ else if (c == 'x') {
+ subprocs = 0;
+ }
+ else if (c == 'q') {
+ T_timeout = atoi(isc_commandline_argument);
+ }
+ else if (c == ':') {
+ fprintf(stderr, "Option -%c requires an argument\n",
+ isc_commandline_option);
+ exit(1);
+ }
+ else if (c == '?') {
+ fprintf(stderr, "Unrecognized option -%c\n",
+ isc_commandline_option);
+ exit(1);
+ }
+ }
+
+ /*
+ * Set cwd.
+ */
+
+ if (T_dir != NULL)
+ IGNORE_RET (chdir(T_dir));
+
+ /*
+ * We don't want buffered output.
+ */
+
+ (void)setbuf(stdout, NULL);
+ (void)setbuf(stderr, NULL);
+
+ /*
+ * Setup signals.
+ */
+
+ sa.sa_flags = 0;
+ sigfillset(&sa.sa_mask);
+
+#ifdef SIGCHLD
+ /*
+ * This is mostly here for NetBSD's pthread implementation, until
+ * people catch up to the latest unproven-pthread package.
+ */
+ sa.sa_handler = SIG_DFL;
+ (void)sigaction(SIGCHLD, &sa, NULL);
+#endif
+
+ sa.sa_handler = t_sighandler;
+ (void)sigaction(SIGINT, &sa, NULL);
+ (void)sigaction(SIGALRM, &sa, NULL);
+
+ /*
+ * Output start stanza to journal.
+ */
+
+ snprintf(T_buf, sizeof(T_buf), "%s:", argv[0]);
+ len = strlen(T_buf);
+ (void) t_getdate(T_buf + len, T_BIGBUF - len);
+ t_putinfo("S", T_buf);
+
+ /*
+ * Setup the test environment using the config file.
+ */
+
+ if (T_config == NULL)
+ T_config = T_DEFAULT_CONFIG;
+
+ t_initconf(T_config);
+ if (T_debug)
+ t_dumpconf(T_config);
+
+ /*
+ * Now invoke all the test cases.
+ */
+
+ tnum = 0;
+ pts = &T_testlist[0];
+ while (*pts->pfv != NULL) {
+ if (T_tvec[tnum / 8] & (0x01 << (tnum % 8))) {
+ if (subprocs) {
+ T_pid = fork();
+ if (T_pid == 0) {
+ (*pts->pfv)();
+ exit(0);
+ } else if (T_pid > 0) {
+
+ T_int = 0;
+ sa.sa_handler = t_sighandler;
+ (void)sigaction(SIGALRM, &sa, NULL);
+ alarm(T_timeout);
+
+ deadpid = (pid_t) -1;
+ while (deadpid != T_pid) {
+ deadpid =
+ waitpid(T_pid, &status, 0);
+ if (deadpid == T_pid) {
+ if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) ==
+ SIGTERM)
+ t_info(
+ "the test case timed out\n");
+ else
+ t_info(
+ "the test case caused exception %d\n",
+ WTERMSIG(status));
+ t_result(T_UNRESOLVED);
+ }
+ } else if ((deadpid == -1) &&
+ (errno == EINTR) &&
+ T_int) {
+ kill(T_pid, SIGTERM);
+ T_int = 0;
+ }
+ else if ((deadpid == -1) &&
+ ((errno == ECHILD) ||
+ (errno == ESRCH)))
+ break;
+ }
+
+ alarm(0);
+ sa.sa_handler = SIG_IGN;
+ (void)sigaction(SIGALRM, &sa, NULL);
+ } else {
+ t_info("fork failed, errno == %d\n",
+ errno);
+ t_result(T_UNRESOLVED);
+ }
+ }
+ else {
+ (*pts->pfv)();
+ }
+ }
+ ++pts;
+ ++tnum;
+ }
+
+ snprintf(T_buf, sizeof(T_buf), "%s:", argv[0]);
+ len = strlen(T_buf);
+ (void) t_getdate(T_buf + len, T_BIGBUF - len);
+ t_putinfo("E", T_buf);
+
+ return(0);
+}
+
+void
+t_assert(const char *component, int anum, int class, const char *what, ...) {
+ va_list args;
+
+ (void)printf("T:%s:%d:%s\n", component, anum, class == T_REQUIRED ?
+ "A" : "C");
+
+ /*
+ * Format text to a buffer.
+ */
+ va_start(args, what);
+ (void)vsnprintf(T_buf, sizeof(T_buf), what, args);
+ va_end(args);
+
+ (void)t_putinfo("A", T_buf);
+ (void)printf("\n");
+}
+
+void
+t_info(const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ (void) vsnprintf(T_buf, sizeof(T_buf), format, args);
+ va_end(args);
+ (void) t_putinfo("I", T_buf);
+}
+
+void
+t_result(int result) {
+ const char *p;
+
+ switch (result) {
+ case T_PASS:
+ p = "PASS";
+ break;
+ case T_FAIL:
+ p = "FAIL";
+ break;
+ case T_UNRESOLVED:
+ p = "UNRESOLVED";
+ break;
+ case T_UNSUPPORTED:
+ p = "UNSUPPORTED";
+ break;
+ case T_UNTESTED:
+ p = "UNTESTED";
+ break;
+ case T_THREADONLY:
+ p = "THREADONLY";
+ break;
+ default:
+ p = "UNKNOWN";
+ break;
+ }
+ printf("R:%s\n", p);
+}
+
+char *
+t_getenv(const char *name) {
+ char *n;
+ char **p;
+ size_t len;
+
+ n = NULL;
+ if (name && *name) {
+
+ p = &T_env[0];
+ len = strlen(name);
+
+ while (*p != NULL) {
+ if (strncmp(*p, name, len) == 0) {
+ if ( *(*p + len) == '=') {
+ n = *p + len + 1;
+ break;
+ }
+ }
+ ++p;
+ }
+ }
+ return(n);
+}
+
+/*
+ *
+ * Read in the config file at path, initializing T_env.
+ *
+ * note: no format checking for now ...
+ *
+ */
+
+static int
+t_initconf(const char *path) {
+
+ int n;
+ int rval;
+ char **p;
+ FILE *fp;
+
+ rval = -1;
+
+ fp = fopen(path, "r");
+ if (fp != NULL) {
+ n = 0;
+ p = &T_env[0];
+ while (n < T_MAXENV) {
+ *p = t_fgetbs(fp);
+ if (*p == NULL)
+ break;
+ if ((**p == '#') || (strchr(*p, '=') == NULL)) {
+ /*
+ * Skip comments and other junk.
+ */
+ (void)free(*p);
+ continue;
+ }
+ ++p; ++n;
+ }
+ (void)fclose(fp);
+ rval = 0;
+ }
+
+ return (rval);
+}
+
+/*
+ *
+ * Dump T_env to stdout.
+ *
+ */
+
+static int
+t_dumpconf(const char *path) {
+ int rval;
+ char **p;
+ FILE *fp;
+
+ rval = -1;
+ fp = fopen(path, "r");
+ if (fp != NULL) {
+ p = &T_env[0];
+ while (*p != NULL) {
+ printf("C:%s\n", *p);
+ ++p;
+ }
+ (void) fclose(fp);
+ rval = 0;
+ }
+ return(rval);
+}
+
+/*
+ *
+ * Read a newline or EOF terminated string from fp.
+ * On success:
+ * return a malloc'd buf containing the string with
+ * the newline converted to a '\0'.
+ * On error:
+ * return NULL.
+ *
+ * Caller is responsible for freeing buf.
+ *
+ */
+
+char *
+t_fgetbs(FILE *fp) {
+ int c;
+ size_t n;
+ size_t size;
+ char *buf, *old;
+ char *p;
+
+ n = 0;
+ size = T_BUFSIZ;
+ old = buf = (char *) malloc(T_BUFSIZ * sizeof(char));
+
+ if (buf != NULL) {
+ p = buf;
+ while ((c = fgetc(fp)) != EOF) {
+
+ if (c == '\n')
+ break;
+
+ *p++ = c;
+ ++n;
+ if ( n >= size ) {
+ size += T_BUFSIZ;
+ buf = (char *)realloc(buf,
+ size * sizeof(char));
+ if (buf == NULL)
+ goto err;
+ old = buf;
+ p = buf + n;
+ }
+ }
+ *p = '\0';
+ if (c == EOF && n == 0U) {
+ free(buf);
+ return (NULL);
+ }
+ return (buf);
+ } else {
+ err:
+ if (old != NULL)
+ free(old);
+ fprintf(stderr, "malloc/realloc failed %d", errno);
+ return(NULL);
+ }
+}
+
+/*
+ *
+ * Put info to log, using key.
+ * For now, just dump it out.
+ * Later format into pretty lines.
+ *
+ */
+
+static int
+t_putinfo(const char *key, const char *info) {
+ int rval;
+
+ /*
+ * For now.
+ */
+ rval = printf("%s:%s", key, info);
+ return(rval);
+}
+
+static char *
+t_getdate(char *buf, size_t buflen) {
+ size_t n;
+ time_t t;
+ struct tm *p;
+
+ t = time(NULL);
+ p = localtime(&t);
+ n = strftime(buf, buflen - 1, "%A %d %B %H:%M:%S %Y\n", p);
+ return(n != 0U ? buf : NULL);
+}
+
+/*
+ * Some generally used utilities.
+ */
+#ifdef DNS_SUPPORT
+struct dns_errormap {
+ isc_result_t result;
+ const char *text;
+} dns_errormap[] = {
+ { ISC_R_SUCCESS, "ISC_R_SUCCESS" },
+ { ISC_R_EXISTS, "ISC_R_EXISTS" },
+ { ISC_R_NOTFOUND, "ISC_R_NOTFOUND" },
+ { ISC_R_NOSPACE, "ISC_R_NOSPACE" },
+ { ISC_R_UNEXPECTED, "ISC_R_UNEXPECTED" },
+ { ISC_R_UNEXPECTEDEND, "ISC_R_UNEXPECTEDEND" },
+ { ISC_R_RANGE, "ISC_R_RANGE" },
+ { DNS_R_LABELTOOLONG, "DNS_R_LABELTOOLONG" },
+ { DNS_R_BADESCAPE, "DNS_R_BADESCAPE" },
+ /* { DNS_R_BADBITSTRING, "DNS_R_BADBITSTRING" }, */
+ /* { DNS_R_BITSTRINGTOOLONG, "DNS_R_BITSTRINGTOOLONG"}, */
+ { DNS_R_EMPTYLABEL, "DNS_R_EMPTYLABEL" },
+ { DNS_R_BADDOTTEDQUAD, "DNS_R_BADDOTTEDQUAD" },
+ { DNS_R_UNKNOWN, "DNS_R_UNKNOWN" },
+ { DNS_R_BADLABELTYPE, "DNS_R_BADLABELTYPE" },
+ { DNS_R_BADPOINTER, "DNS_R_BADPOINTER" },
+ { DNS_R_TOOMANYHOPS, "DNS_R_TOOMANYHOPS" },
+ { DNS_R_DISALLOWED, "DNS_R_DISALLOWED" },
+ { DNS_R_EXTRATOKEN, "DNS_R_EXTRATOKEN" },
+ { DNS_R_EXTRADATA, "DNS_R_EXTRADATA" },
+ { DNS_R_TEXTTOOLONG, "DNS_R_TEXTTOOLONG" },
+ { DNS_R_SYNTAX, "DNS_R_SYNTAX" },
+ { DNS_R_BADCKSUM, "DNS_R_BADCKSUM" },
+ { DNS_R_BADAAAA, "DNS_R_BADAAAA" },
+ { DNS_R_NOOWNER, "DNS_R_NOOWNER" },
+ { DNS_R_NOTTL, "DNS_R_NOTTL" },
+ { DNS_R_BADCLASS, "DNS_R_BADCLASS" },
+ { DNS_R_PARTIALMATCH, "DNS_R_PARTIALMATCH" },
+ { DNS_R_NEWORIGIN, "DNS_R_NEWORIGIN" },
+ { DNS_R_UNCHANGED, "DNS_R_UNCHANGED" },
+ { DNS_R_BADTTL, "DNS_R_BADTTL" },
+ { DNS_R_NOREDATA, "DNS_R_NOREDATA" },
+ { DNS_R_CONTINUE, "DNS_R_CONTINUE" },
+ { DNS_R_DELEGATION, "DNS_R_DELEGATION" },
+ { DNS_R_GLUE, "DNS_R_GLUE" },
+ { DNS_R_DNAME, "DNS_R_DNAME" },
+ { DNS_R_CNAME, "DNS_R_CNAME" },
+ { DNS_R_NXDOMAIN, "DNS_R_NXDOMAIN" },
+ { DNS_R_NXRRSET, "DNS_R_NXRRSET" },
+ { DNS_R_BADDB, "DNS_R_BADDB" },
+ { DNS_R_ZONECUT, "DNS_R_ZONECUT" },
+ { DNS_R_NOTZONETOP, "DNS_R_NOTZONETOP" },
+ { DNS_R_SEENINCLUDE, "DNS_R_SEENINCLUDE" },
+ { DNS_R_SINGLETON, "DNS_R_SINGLETON" },
+ { (isc_result_t)0, NULL }
+};
+
+isc_result_t
+t_dns_result_fromtext(char *name) {
+
+ isc_result_t result;
+ struct dns_errormap *pmap;
+
+ result = ISC_R_UNEXPECTED;
+
+ pmap = dns_errormap;
+ while (pmap->text != NULL) {
+ if (strcmp(name, pmap->text) == 0)
+ break;
+ ++pmap;
+ }
+
+ if (pmap->text != NULL)
+ result = pmap->result;
+
+ return (result);
+}
+
+struct dc_method_map {
+ unsigned int dc_method;
+ const char *text;
+} dc_method_map[] = {
+
+ { DNS_COMPRESS_NONE, "DNS_COMPRESS_NONE" },
+ { DNS_COMPRESS_GLOBAL14, "DNS_COMPRESS_GLOBAL14" },
+ { DNS_COMPRESS_ALL, "DNS_COMPRESS_ALL" },
+ { 0, NULL }
+};
+
+unsigned int
+t_dc_method_fromtext(char *name) {
+ unsigned int dc_method;
+ struct dc_method_map *pmap;
+
+ dc_method = DNS_COMPRESS_NONE;
+
+ pmap = dc_method_map;
+ while (pmap->text != NULL) {
+ if (strcmp(name, pmap->text) == 0)
+ break;
+ ++pmap;
+ }
+
+ if (pmap->text != NULL)
+ dc_method = pmap->dc_method;
+
+ return(dc_method);
+}
+#endif /* DNS_SUPPORT */
+
+int
+t_bustline(char *line, char **toks) {
+ int cnt;
+ char *p;
+
+ cnt = 0;
+ if (line && *line) {
+ while ((p = strtok(line, "\t")) && (cnt < T_MAXTOKS)) {
+ *toks++ = p;
+ line = NULL;
+ ++cnt;
+ }
+ }
+ return(cnt);
+}
+
+static void
+printhelp(void) {
+ int cnt;
+ testspec_t *pts;
+
+ cnt = 1;
+ pts = &T_testlist[0];
+
+ printf("Available tests:\n");
+ while (pts->func_name) {
+ printf("\t%d\t%s\n", cnt, pts->func_name);
+ ++pts;
+ ++cnt;
+ }
+}
+
+static void
+printusage(void) {
+ printf("Usage:\n%s\n", Usage);
+}
+
+int
+t_eval(const char *filename, int (*func)(char **), int nargs) {
+ FILE *fp;
+ char *p;
+ int line;
+ int cnt;
+ int result;
+ int nfails;
+ int nprobs;
+ int npass;
+ char *tokens[T_MAXTOKS + 1];
+
+ npass = 0;
+ nfails = 0;
+ nprobs = 0;
+
+ fp = fopen(filename, "r");
+ if (fp != NULL) {
+ line = 0;
+ while ((p = t_fgetbs(fp)) != NULL) {
+
+ ++line;
+
+ /*
+ * Skip comment lines.
+ */
+ if ((isspace((unsigned char)*p)) || (*p == '#')) {
+ (void)free(p);
+ continue;
+ }
+
+ cnt = t_bustline(p, tokens);
+ if (cnt == nargs) {
+ result = func(tokens);
+ switch (result) {
+ case T_PASS:
+ ++npass;
+ break;
+ case T_FAIL:
+ ++nfails;
+ break;
+ case T_UNTESTED:
+ break;
+ default:
+ ++nprobs;
+ break;
+ }
+ } else {
+ t_info("bad format in %s at line %d\n",
+ filename, line);
+ ++nprobs;
+ }
+
+ (void)free(p);
+ }
+ (void)fclose(fp);
+ } else {
+ t_info("Missing datafile %s\n", filename);
+ ++nprobs;
+ }
+
+ result = T_UNRESOLVED;
+
+ if (nfails == 0 && nprobs == 0 && npass > 0)
+ result = T_PASS;
+ else if (nfails > 0)
+ result = T_FAIL;
+ else if (npass == 0)
+ result = T_UNTESTED;
+
+ return (result);
+}
diff --git a/tests/t_api_dhcp.c b/tests/t_api_dhcp.c
new file mode 100644
index 0000000..9ef221a
--- /dev/null
+++ b/tests/t_api_dhcp.c
@@ -0,0 +1,44 @@
+/*
+ * We have to have a number of symbols defined in order to build a
+ * DHCP program.
+ */
+
+#include <config.h>
+#include "dhcpd.h"
+
+void
+bootp(struct packet *packet) {
+}
+
+void
+dhcp(struct packet *packet) {
+}
+
+void
+dhcpv6(struct packet *packet) {
+}
+
+isc_result_t
+dhcp_set_control_state(control_object_state_t old, control_object_state_t new) {
+ return ISC_R_NOTIMPLEMENTED;
+}
+
+int
+check_collection(struct packet *p, struct lease *l, struct collection *c) {
+ return 0;
+}
+
+void
+classify (struct packet *p, struct class *c) {
+}
+
+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;
+}
+
diff --git a/tests/unit_test_sample.c b/tests/unit_test_sample.c
new file mode 100644
index 0000000..86d6336
--- /dev/null
+++ b/tests/unit_test_sample.c
@@ -0,0 +1,25 @@
+#include "config.h"
+#include "t_api.h"
+
+static void foo(void);
+
+/*
+ * T_testlist is a list of tests that are invoked.
+ */
+testspec_t T_testlist[] = {
+ { foo, "sample test" },
+ { NULL, NULL }
+};
+
+static void
+foo(void) {
+ static const char *test_desc =
+ "this is an example test, for no actual module";
+
+ t_assert("sample", 1, T_REQUIRED, test_desc);
+
+ /* ... */ /* Test code would go here. */
+
+ t_result(T_PASS);
+}
+
diff --git a/tests/unittest.sh.in b/tests/unittest.sh.in
new file mode 100755
index 0000000..0ee7ab7
--- /dev/null
+++ b/tests/unittest.sh.in
@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or 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.
+#
+# Script used to execute unit tests described by the Atffile in the current
+# directory. It exits with return value of atf-run, which will be 0 if all
+# tests passed, non-zero otherwise.
+#
+
+# Add configured path to ATF tools, atf-run and atf-report
+PATH="@ATF_BIN@:${PATH}"
+export PATH
+
+# colors if not outputting to a dumb terminal and stdout is a tty
+if test "$TERM" != dumb && { test -t 1; } 2>/dev/null; then \
+ red='\033[0;31m'
+ green='\033[0;32m'
+ noclr='\033[0m'
+
+ # if echo supports -e, we must use it to set colors
+ # (output will be "" if its supported)
+ if [ -z "`echo -e`" ]
+ then
+ dash_e="-e"
+ fi
+fi;
+
+header="===================================================="
+
+status=0
+if [ -n "@ATF_BIN@" -a -f Atffile ]
+then
+ # run the tests
+ echo "Running unit tests..."
+ atf-run > atf.out
+ status=$?
+
+ # set color based on success/failure
+ if [ $status -eq 0 ]
+ then
+ color=$green
+ else
+ color=$red
+ fi
+
+ # spit out the test report
+ # We print everything upto the summary in
+ # "no color". Print the summary in our
+ # result color.
+ cat atf.out | atf-report | while read line
+ do
+ cnt=`echo $line | grep -c "Summary"`
+ if [ $cnt -eq 1 ]
+ then
+ echo $dash_e $color$header
+ fi
+ echo $line;
+ done
+ echo $dash_e $header$noclr
+
+ # clean up unless there were test failures
+ if [ $status -eq 0 ]
+ then
+ rm -f atf.out
+ fi
+fi
+exit $status