]>
Commit | Line | Data |
---|---|---|
98bd7ca0 DH |
1 | #! /usr/bin/perl -w |
2 | ||
49a7fb58 | 3 | # Copyright (C) 2007-2022 Internet Systems Consortium, Inc. ("ISC") |
706792c9 DH |
4 | # |
5 | # Permission to use, copy, modify, and distribute this software for any | |
6 | # purpose with or without fee is hereby granted, provided that the above | |
7 | # copyright notice and this permission notice appear in all copies. | |
8 | # | |
9 | # THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES | |
10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR | |
12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT | |
15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | # | |
17 | # Internet Systems Consortium, Inc. | |
429a56d7 TM |
18 | # PO Box 360 |
19 | # Newmarket, NH 03857 USA | |
706792c9 | 20 | # <info@isc.org> |
2c85ac9b | 21 | # https://www.isc.org/ |
706792c9 | 22 | |
98bd7ca0 DH |
23 | use strict; |
24 | use English; | |
25 | use Time::HiRes qw( sleep ); | |
26 | use Socket; | |
27 | use Socket6; | |
28 | use IO::Select; | |
29 | ||
30 | use dhcp_client; | |
31 | ||
32 | # XXX: for debugging | |
33 | use Data::Dumper; | |
34 | ||
35 | # not-yet-standard options | |
36 | my $OPT_TIME_SERVERS = 40; | |
37 | my $OPT_TIME_OFFSET = 41; | |
38 | ||
39 | # DOCSIS sub-options | |
40 | my $DOCSIS_OPT_ORO = 1; | |
41 | # 2 to 31 are reserved | |
42 | my $DOCSIS_OPT_TFTP_SERVERS = 32; | |
43 | my $DOCSIS_OPT_CONFIG_FILE_NAME = 33; | |
44 | my $DOCSIS_OPT_SYSLOG_SERVERS = 34; | |
45 | my $DOCSIS_OPT_TLV5 = 35; | |
46 | my $DOCSIS_OPT_DEVICE_ID = 36; | |
47 | my $DOCSIS_OPT_CCC = 37; | |
48 | my $DOCSIS_OPT_VERS = 38; | |
49 | ||
50 | # well-known addresses | |
51 | my $All_DHCP_Relay_Agents_and_Servers = "ff02::1:2"; | |
52 | my $All_DHCP_Servers = "ff05::1:3"; | |
53 | ||
54 | # ports | |
55 | my $client_port = 546; | |
56 | my $server_port = 547; | |
57 | ||
58 | # create a new Solicit message | |
59 | my $msg = dhcp_client::msg->new($MSG_INFORMATION_REQUEST); | |
60 | ||
61 | # add the Client Identifier (required by DOCSIS and RFC 3315) | |
62 | $msg->add_option($OPT_CLIENTID, dhcp_client::duid()); | |
63 | ||
64 | # add Elapsed Time, set to 0 on first packet (required by RFC 3315) | |
65 | $msg->add_option($OPT_ELAPSED_TIME, "\x00\x00"); | |
66 | ||
67 | # add IA_NA for each interface (required by DOCSIS and RFC 3315) | |
68 | # XXX: should this be a single interface only? | |
69 | my $iaid = 0; | |
70 | foreach my $iface (dhcp_client::iface()) { | |
71 | my $option_data = pack("NNN", ++$iaid, 0, 0); | |
72 | $msg->add_option($OPT_IA_NA, $option_data); | |
73 | } | |
74 | ||
75 | # add Reconfigure Accept (required by DOCSIS) | |
76 | $msg->add_option($OPT_RECONF_ACCEPT, ""); | |
77 | ||
78 | # add Options Request (required by DOCSIS, recommended by RFC 3315) | |
79 | my @oro = ( $OPT_TIME_SERVERS, $OPT_TIME_OFFSET ); | |
80 | $msg->add_option($OPT_ORO, pack("n*", @oro)); | |
81 | ||
82 | ||
83 | # add Vendor Class option (required by DOCSIS) | |
84 | $msg->add_option($OPT_VENDOR_CLASS, pack("N", 4491) . "docsis3.0"); | |
85 | ||
86 | # add Vendor-specific Information Option option (required by DOCSIS) | |
87 | my $vsio = pack("N", 4491); | |
88 | ||
89 | # ORO (required by DOCSIS) | |
90 | my @docsis_oro = ( $DOCSIS_OPT_TFTP_SERVERS ); | |
91 | $vsio .= pack("nnC*", $DOCSIS_OPT_ORO, 0+@docsis_oro, @docsis_oro); | |
92 | ||
93 | # TLV5 data: CMTS DOCSIS version number 3.0 (required by DOCSIS) | |
f6b8f48d | 94 | my $tlv5_data = "\x01\x02\x03\x0"; |
98bd7ca0 DH |
95 | $vsio .= pack("nn", $DOCSIS_OPT_TLV5, length($tlv5_data)) . $tlv5_data; |
96 | ||
97 | # DOCSIS Device (required by DOCSIS) | |
98 | my $docsis_device_id = dhcp_client::mac_addr_binary(); | |
99 | $vsio .= pack("nn", $DOCSIS_OPT_DEVICE_ID, length($docsis_device_id)); | |
100 | $vsio .= $docsis_device_id; | |
101 | ||
102 | $msg->add_option($OPT_VENDOR_OPTS, $vsio); | |
103 | ||
104 | # add Rapid Commit option (required by DOCSIS) | |
105 | $msg->add_option($OPT_RAPID_COMMIT, ""); | |
106 | ||
107 | # timeout parameters, from DOCSIS | |
108 | my $IRT = $SOL_TIMEOUT; | |
109 | my $MRT = $SOL_MAX_RT; | |
110 | my $MRC = 1; # DOCSIS says 4, RFC 3315 says it SHOULD be 0 | |
111 | my $MRD = 0; | |
112 | ||
113 | # sleep a random amount of time between 0 and 1 second, required by RFC 3315 | |
114 | # XXX: this seems pretty stupid | |
115 | sleep(rand($SOL_MAX_DELAY)); | |
116 | ||
117 | my $RT; | |
118 | my $count = 0; | |
119 | my $mrd_end_time; | |
120 | if ($MRD != 0) { | |
121 | $mrd_end_time = time() + $MRD; | |
122 | } | |
123 | my $reply_msg; | |
124 | do { | |
125 | # create our socket, and send our Solicit | |
126 | socket(SOCK, PF_INET6, SOCK_DGRAM, getprotobyname('udp')) || die; | |
127 | my $addr = inet_pton(AF_INET6, $All_DHCP_Servers); | |
128 | my $packet = $msg->packet(); | |
f6b8f48d | 129 | my $send_ret = send(SOCK, $packet, 0, |
98bd7ca0 DH |
130 | pack_sockaddr_in6($server_port, $addr)); |
131 | if (not defined($send_ret)) { | |
f6b8f48d | 132 | printf STDERR |
98bd7ca0 DH |
133 | "Error \%d sending DHCPv6 Solicit message;\n\%s\n", |
134 | 0+$ERRNO, $ERRNO; | |
135 | exit(1); | |
136 | } elsif ($send_ret != length($packet)) { | |
137 | print STDERR "Unable to send entire DHCPv6 Solicit message.\n"; | |
138 | exit(1); | |
139 | } | |
140 | $count++; | |
141 | ||
142 | my $RAND = rand(0.2) - 0.1; | |
143 | if (defined $RT) { | |
144 | $RT = 2*$RT + $RAND*$RT; | |
145 | if (($RT > $MRT) && ($MRT != 0)) { | |
146 | $RT = $MRT + $RAND*$RT; | |
147 | } | |
148 | } else { | |
149 | $RT = $IRT + $RAND*$IRT; | |
150 | } | |
151 | ||
152 | my $rt_end_time = time() + $RT; | |
f6b8f48d | 153 | if (defined($mrd_end_time) && ($mrd_end_time > $rt_end_time)) { |
98bd7ca0 DH |
154 | $rt_end_time = $mrd_end_time; |
155 | } | |
156 | ||
157 | for (;;) { | |
158 | my $timeout = $rt_end_time - time(); | |
159 | if ($timeout < 0) { | |
160 | # print STDERR "Timeout waiting for DHCPv6 Advertise ", | |
161 | # "or Reply message.\n"; | |
162 | last; | |
163 | } | |
164 | ||
165 | my @ready = IO::Select->new(\*SOCK)->can_read($timeout); | |
166 | ||
167 | if (@ready) { | |
168 | my $reply; | |
169 | my $recv_ret; | |
f6b8f48d | 170 | |
98bd7ca0 DH |
171 | $recv_ret = recv(SOCK, $reply, 1500, 0); |
172 | if (not defined $recv_ret) { | |
f6b8f48d TM |
173 | printf STDERR |
174 | "Error \%d receiving DHCPv6 " . | |
98bd7ca0 DH |
175 | "message;\n\%s\n", |
176 | 0+$ERRNO, $ERRNO; | |
177 | exit(1); | |
178 | } | |
179 | ||
180 | $reply_msg = dhcp_client::msg::decode($reply); | |
181 | if (($reply_msg->{msg_type} == $MSG_ADVERTISE) || | |
182 | ($reply_msg->{msg_type} == $MSG_REPLY)) { | |
183 | last; | |
184 | } | |
185 | } | |
186 | } | |
187 | ||
f6b8f48d | 188 | } until ($reply_msg || |
98bd7ca0 DH |
189 | (($MRC != 0) && ($count > $MRC)) || |
190 | (defined($mrd_end_time) && ($mrd_end_time > time()))); | |
191 | ||
192 | unless ($reply_msg) { | |
193 | if (($MRC != 0) && ($count >= $MRC)) { | |
f6b8f48d | 194 | print STDERR |
98bd7ca0 DH |
195 | "No reply after maximum retransmission count.\n"; |
196 | } else { | |
f6b8f48d | 197 | print STDERR |
98bd7ca0 DH |
198 | "No reply after maximum retransmission duration.\n"; |
199 | } | |
200 | } | |
201 | ||
202 | if ($reply_msg && ($reply_msg->{msg_type} == $MSG_REPLY)) { | |
203 | print "Got DHCPv6 Reply message.\n"; | |
204 | exit(0); | |
205 | } | |
206 | ||
207 | #$Data::Dumper::Useqq = 1; | |
208 | #print Dumper($msg), "\n"; | |
209 | #print Dumper($msg->packet()), "\n"; | |
210 | # | |
211 | #print "packet length: ", length($msg->packet()), "\n"; |