summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/DHCPv6/000-badmsgtype.pl144
-rw-r--r--tests/DHCPv6/010-solicit-noclientid.pl192
-rw-r--r--tests/DHCPv6/011-solicit-serverid.pl195
-rw-r--r--tests/DHCPv6/020-advertise-mcast.pl144
-rw-r--r--tests/DHCPv6/030-request-noclientid.pl192
-rw-r--r--tests/DHCPv6/031-request-noserverid.pl192
-rw-r--r--tests/DHCPv6/032-request-badduid.pl196
-rw-r--r--tests/DHCPv6/110-information-request-ia_na.pl192
-rw-r--r--tests/DHCPv6/111-information-request-ia_ta.pl192
-rw-r--r--tests/DHCPv6/112-badduid.pl188
-rw-r--r--tests/DHCPv6/210-solicit-nohost.pl192
-rw-r--r--tests/DHCPv6/211-solicit-opt-in-na.pl198
-rw-r--r--tests/DHCPv6/212-solicit-opt-in-na-norapidcommit.pl198
-rw-r--r--tests/DHCPv6/280-release-nohost.pl155
-rw-r--r--tests/DHCPv6/281-release-bad-address.pl167
-rw-r--r--tests/DHCPv6/282-release-no-address.pl163
-rw-r--r--tests/DHCPv6/283-release.pl169
-rw-r--r--tests/DHCPv6/290-decline-nohost.pl155
-rw-r--r--tests/DHCPv6/291-decline-bad-address.pl167
-rw-r--r--tests/DHCPv6/292-decline-no-address.pl163
-rw-r--r--tests/DHCPv6/293-decline.pl169
-rw-r--r--tests/DHCPv6/README62
-rw-r--r--tests/DHCPv6/dhcp_client.pm435
-rw-r--r--tests/DHCPv6/stubcli-opt-in-na.pl197
-rw-r--r--tests/DHCPv6/stubcli.pl192
-rw-r--r--tests/DHCPv6/test-a.conf67
-rw-r--r--tests/DHCPv6/test-b.conf60
27 files changed, 4736 insertions, 0 deletions
diff --git a/tests/DHCPv6/000-badmsgtype.pl b/tests/DHCPv6/000-badmsgtype.pl
new file mode 100644
index 00000000..c6dc202b
--- /dev/null
+++ b/tests/DHCPv6/000-badmsgtype.pl
@@ -0,0 +1,144 @@
+#! /usr/bin/perl -w
+
+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 00000000..89f4bc47
--- /dev/null
+++ b/tests/DHCPv6/010-solicit-noclientid.pl
@@ -0,0 +1,192 @@
+#! /usr/bin/perl -w
+
+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 00000000..f77fd42e
--- /dev/null
+++ b/tests/DHCPv6/011-solicit-serverid.pl
@@ -0,0 +1,195 @@
+#! /usr/bin/perl -w
+
+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 00000000..b1ee8e6f
--- /dev/null
+++ b/tests/DHCPv6/020-advertise-mcast.pl
@@ -0,0 +1,144 @@
+#! /usr/bin/perl -w
+
+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 00000000..e7460426
--- /dev/null
+++ b/tests/DHCPv6/030-request-noclientid.pl
@@ -0,0 +1,192 @@
+#! /usr/bin/perl -w
+
+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 00000000..0a2a94b2
--- /dev/null
+++ b/tests/DHCPv6/031-request-noserverid.pl
@@ -0,0 +1,192 @@
+#! /usr/bin/perl -w
+
+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 00000000..bb61952e
--- /dev/null
+++ b/tests/DHCPv6/032-request-badduid.pl
@@ -0,0 +1,196 @@
+#! /usr/bin/perl -w
+
+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 00000000..03a44ecd
--- /dev/null
+++ b/tests/DHCPv6/110-information-request-ia_na.pl
@@ -0,0 +1,192 @@
+#! /usr/bin/perl -w
+
+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 00000000..aa9eba28
--- /dev/null
+++ b/tests/DHCPv6/111-information-request-ia_ta.pl
@@ -0,0 +1,192 @@
+#! /usr/bin/perl -w
+
+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 00000000..41ccdeb3
--- /dev/null
+++ b/tests/DHCPv6/112-badduid.pl
@@ -0,0 +1,188 @@
+#! /usr/bin/perl -w
+
+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 00000000..5ca80250
--- /dev/null
+++ b/tests/DHCPv6/210-solicit-nohost.pl
@@ -0,0 +1,192 @@
+#! /usr/bin/perl -w
+
+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 00000000..2dfbbcc9
--- /dev/null
+++ b/tests/DHCPv6/211-solicit-opt-in-na.pl
@@ -0,0 +1,198 @@
+#! /usr/bin/perl -w
+
+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 00000000..f7517eb5
--- /dev/null
+++ b/tests/DHCPv6/212-solicit-opt-in-na-norapidcommit.pl
@@ -0,0 +1,198 @@
+#! /usr/bin/perl -w
+
+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 00000000..2b84481d
--- /dev/null
+++ b/tests/DHCPv6/280-release-nohost.pl
@@ -0,0 +1,155 @@
+#! /usr/bin/perl -w
+
+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 00000000..635e701e
--- /dev/null
+++ b/tests/DHCPv6/281-release-bad-address.pl
@@ -0,0 +1,167 @@
+#! /usr/bin/perl -w
+
+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 00000000..26f06b7d
--- /dev/null
+++ b/tests/DHCPv6/282-release-no-address.pl
@@ -0,0 +1,163 @@
+#! /usr/bin/perl -w
+
+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 00000000..8d82a30c
--- /dev/null
+++ b/tests/DHCPv6/283-release.pl
@@ -0,0 +1,169 @@
+#! /usr/bin/perl -w
+
+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 00000000..4d8e8893
--- /dev/null
+++ b/tests/DHCPv6/290-decline-nohost.pl
@@ -0,0 +1,155 @@
+#! /usr/bin/perl -w
+
+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 00000000..1ebd2992
--- /dev/null
+++ b/tests/DHCPv6/291-decline-bad-address.pl
@@ -0,0 +1,167 @@
+#! /usr/bin/perl -w
+
+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 00000000..f9ed7177
--- /dev/null
+++ b/tests/DHCPv6/292-decline-no-address.pl
@@ -0,0 +1,163 @@
+#! /usr/bin/perl -w
+
+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 00000000..bb7d06e4
--- /dev/null
+++ b/tests/DHCPv6/293-decline.pl
@@ -0,0 +1,169 @@
+#! /usr/bin/perl -w
+
+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 00000000..c559de24
--- /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 00000000..85ff0cc3
--- /dev/null
+++ b/tests/DHCPv6/dhcp_client.pm
@@ -0,0 +1,435 @@
+#! /usr/bin/perl -w
+
+
+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 00000000..f400031b
--- /dev/null
+++ b/tests/DHCPv6/stubcli-opt-in-na.pl
@@ -0,0 +1,197 @@
+#! /usr/bin/perl -w
+
+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 00000000..5ca80250
--- /dev/null
+++ b/tests/DHCPv6/stubcli.pl
@@ -0,0 +1,192 @@
+#! /usr/bin/perl -w
+
+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 00000000..9fe42364
--- /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 00000000..4b673806
--- /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;
+}
+
+