恥ずかしい勘違いから生まれた、DHCP6の不要かつ部分的な実装
Revision | 3a2139426df753d37656236297caa3d9e00dc28c (tree) |
---|---|
Zeit | 2021-08-11 04:20:49 |
Autor | dyknon <dyknon@user...> |
Commiter | dyknon |
sending packet OK.
@@ -0,0 +1,2 @@ | ||
1 | +test: | |
2 | + sudo perl test.pl |
@@ -4,17 +4,21 @@ package Net::DHCP6::Message; | ||
4 | 4 | use strict; |
5 | 5 | use warnings; |
6 | 6 | |
7 | -our $VERSION = "0.0.1" | |
7 | +our $VERSION = "0.0.1"; | |
8 | 8 | |
9 | 9 | # TODO: should be crypto-safe RNG? |
10 | 10 | our $RNG = sub{ int rand shift }; |
11 | 11 | |
12 | +sub gen_xid { | |
13 | + $RNG->(2**24); | |
14 | +} | |
15 | + | |
12 | 16 | sub new { |
13 | 17 | my $class = shift; |
14 | 18 | my $type = shift; |
15 | 19 | my $id = ref $_[0] ? undef : shift; |
16 | 20 | unless(defined $id){ |
17 | - $id = $RNG->(2**24); | |
21 | + $id = $class->gen_xid; | |
18 | 22 | } |
19 | 23 | my $self = { |
20 | 24 | type => $type, |
@@ -88,3 +92,15 @@ sub parse { | ||
88 | 92 | } |
89 | 93 | $class->new($type, $xid, @option); |
90 | 94 | } |
95 | + | |
96 | +use overload ( | |
97 | + '""' => sub { | |
98 | + my $self = shift; | |
99 | + my $name = "Message" . $self->type; | |
100 | + "($name " . sprintf("xid=0x%06x", $self->xid) | |
101 | + . join("", map{" $_"} $self->options) | |
102 | + . ")"; | |
103 | + } | |
104 | +); | |
105 | + | |
106 | +1; |
@@ -0,0 +1,235 @@ | ||
1 | +package Net::DHCP6::MessageExchange; | |
2 | +# RFC8415 15. | |
3 | + | |
4 | +use strict; | |
5 | +use warnings; | |
6 | + | |
7 | +use Exporter; | |
8 | +use Net::DHCP6::Message; | |
9 | +use Net::DHCP6::Parameters qw( | |
10 | + DHCP6_ADDR_ALL_SERVERS | |
11 | + DHCP6_PORT_CLIENT | |
12 | + DHCP6_PORT_SERVER | |
13 | +); | |
14 | +use Socket qw/getaddrinfo AF_INET6 SOCK_DGRAM AI_PASSIVE/; | |
15 | +use POSIX qw/EAGAIN EWOULDBLOCK/; | |
16 | +use Time::HiRes; | |
17 | +use IO::Select; | |
18 | + | |
19 | +our @ISA = qw/Exporter/; | |
20 | +our @EXPORT_OK = qw/NEXT LAST/; | |
21 | +our $VERSION = "0.0.1"; | |
22 | +our $bufsize = 2 ** 16; | |
23 | + | |
24 | +sub get_default_local_address { | |
25 | + my @ai = getaddrinfo(undef, DHCP6_PORT_CLIENT, { | |
26 | + flags => AI_PASSIVE, | |
27 | + family => AF_INET6, | |
28 | + socktype => SOCK_DGRAM, | |
29 | + }); | |
30 | + die $ai[0] if("$ai[0]"); | |
31 | + $ai[1]->{addr}; | |
32 | +} | |
33 | +sub get_default_peer_address { | |
34 | + my @ai = getaddrinfo(DHCP6_ADDR_ALL_SERVERS, DHCP6_PORT_SERVER, { | |
35 | + flags => 0, | |
36 | + family => AF_INET6, | |
37 | + socktype => SOCK_DGRAM, | |
38 | + }); | |
39 | + die $ai[0] if("$ai[0]"); | |
40 | + $ai[1]->{addr}; | |
41 | +} | |
42 | + | |
43 | +use constant NEXT => 0; | |
44 | +use constant LAST => 1; | |
45 | + | |
46 | +sub new { | |
47 | + my $class = shift; | |
48 | + my %args = @_; | |
49 | + if(!exists $args{sock}){ | |
50 | + $args{sock} = IO::Socket::IP->new( | |
51 | + Family => AF_INET6, | |
52 | + Type => SOCK_DGRAM, | |
53 | + Blocking => 0, | |
54 | + ) or die "error while creating socket: $@"; | |
55 | + $args{local} //= undef; | |
56 | + } | |
57 | + if(exists $args{local}){ | |
58 | + $args{local} //= get_default_local_address; | |
59 | + $args{sock}->bind($args{local}) or die "error while binding: $!"; | |
60 | + delete $args{local}; | |
61 | + } | |
62 | + my $self = { | |
63 | + %args, | |
64 | + xid => $args{xid} // Net::DHCP6::Message::gen_xid, | |
65 | + peer => $args{peer} // get_default_peer_address, | |
66 | + start => undef, | |
67 | + rt => undef, | |
68 | + sumrt => 0, | |
69 | + rc => 0, | |
70 | + }; | |
71 | + bless $self, $class; | |
72 | +} | |
73 | + | |
74 | +sub irt { ... } | |
75 | +sub mrc { ... } | |
76 | +sub mrt { ... } | |
77 | +sub mrd { ... } | |
78 | +sub failed { ... } | |
79 | +sub recved { ... } | |
80 | + | |
81 | +sub now { | |
82 | + my $self = shift; | |
83 | + Time::HiRes::time; | |
84 | +} | |
85 | +sub sock { | |
86 | + my $self = shift; | |
87 | + $self->{sock}; | |
88 | +} | |
89 | +sub xid { | |
90 | + my $self = shift; | |
91 | + $self->{xid}; | |
92 | +} | |
93 | +sub peer { | |
94 | + my $self = shift; | |
95 | + $self->{peer}; | |
96 | +} | |
97 | +sub start { | |
98 | + my $self = shift; | |
99 | + $self->{start}; | |
100 | +} | |
101 | +sub elapsed { | |
102 | + my $self = shift; | |
103 | + return undef if(!defined $self->start); | |
104 | + $self->now() - $self->start(); | |
105 | +} | |
106 | +sub rt { | |
107 | + my $self = shift; | |
108 | + $self->{rt}; | |
109 | +} | |
110 | +sub rc { | |
111 | + my $self = shift; | |
112 | + $self->{rc}; | |
113 | +} | |
114 | +sub rand { | |
115 | + my $self = shift; | |
116 | + rand(0.2) - 0.1; | |
117 | +} | |
118 | +sub sumrt { | |
119 | + my $self = shift; | |
120 | + $self->{sumrt}; | |
121 | +} | |
122 | +sub timer_req { | |
123 | + my $self = shift; | |
124 | + my $diff = $self->sumrt() - $self->elapsed(); | |
125 | + $diff < 0 ? 0 : $diff; | |
126 | +} | |
127 | + | |
128 | +sub next_rt { | |
129 | + my $self = shift; | |
130 | + my $rt_prev = $self->rt; | |
131 | + my $rt; | |
132 | + if(defined $rt_prev){ | |
133 | + $rt = 2 * $rt_prev + $self->rand() * $rt_prev; | |
134 | + }else{ | |
135 | + $rt = $self->irt() + $self->rand() * $self->irt(); | |
136 | + } | |
137 | + if($self->mrt && $rt > $self->mrt){ | |
138 | + $rt = $self->mrt() + $self->rand() * $self->mrt(); | |
139 | + } | |
140 | + $self->{rt} = $rt; | |
141 | +} | |
142 | + | |
143 | +sub next_sumrt { | |
144 | + my $self = shift; | |
145 | + if( | |
146 | + $self->mrd && $self->{sumrt} > $self->mrd || | |
147 | + $self->mrc && $self->{rc}++ > $self->mrc | |
148 | + ){ | |
149 | + $self->{sumrt} = undef; | |
150 | + return undef; | |
151 | + } | |
152 | + $self->{sumrt} += $self->next_rt; | |
153 | + if($self->mrd && $self->{sumrt} > $self->mrd){ | |
154 | + $self->{sumrt} = $self->mrd; | |
155 | + } | |
156 | + $self->{sumrt}; | |
157 | +} | |
158 | + | |
159 | +sub send { | |
160 | + my $self = shift; | |
161 | + my $mes = $self->message; | |
162 | + $self->sock->send($mes->serialize, 0, $self->peer) | |
163 | + or die "sending error: $!"; | |
164 | +} | |
165 | + | |
166 | +sub next { | |
167 | + my $self = shift; | |
168 | + if(defined $self->start){ | |
169 | + my $recved = $self->recv; | |
170 | + goto EXIT if(!$recved); | |
171 | + goto EXIT if($recved->xid != $self->xid); | |
172 | + if($self->recved($recved) == LAST){ | |
173 | + return LAST; | |
174 | + } | |
175 | + | |
176 | +EXIT: | |
177 | + if($self->elapsed >= $self->sumrt){ | |
178 | + if(!defined $self->next_sumrt){ | |
179 | + $self->failed; | |
180 | + return LAST; | |
181 | + } | |
182 | + $self->send; | |
183 | + } | |
184 | + return NEXT; | |
185 | + }else{ | |
186 | + $self->flush_recv; | |
187 | + $self->send; | |
188 | + $self->{start} = $self->now; | |
189 | + $self->next_sumrt; | |
190 | + return NEXT; | |
191 | + } | |
192 | +} | |
193 | + | |
194 | +sub recv { | |
195 | + my $self = shift; | |
196 | + my $buf; | |
197 | + my $from = $self->sock->recv($buf, $bufsize, 0); | |
198 | + if(!defined $from){ | |
199 | + if($! == EAGAIN || $! == EWOULDBLOCK){ | |
200 | + return undef; | |
201 | + }else{ | |
202 | + die "socket io error: $!"; | |
203 | + } | |
204 | + } | |
205 | + my $parsed = eval { Net::DHCP6::Message->parse($buf); }; | |
206 | + if(wantarray){ | |
207 | + ($parsed, $from); | |
208 | + }else{ | |
209 | + $parsed; | |
210 | + } | |
211 | +} | |
212 | + | |
213 | +sub flush_recv { | |
214 | + my $self = shift; | |
215 | + while(1){ | |
216 | + my $buf; | |
217 | + my $from = $self->sock->recv($buf, 1, 0); | |
218 | + if(!defined $from){ | |
219 | + if($! == EAGAIN || $! == EWOULDBLOCK){ | |
220 | + last; | |
221 | + }else{ | |
222 | + die "socket io error"; | |
223 | + } | |
224 | + } | |
225 | + last if(length $buf == 0); | |
226 | + } | |
227 | +} | |
228 | + | |
229 | +sub loop { | |
230 | + my $self = shift; | |
231 | + my $select = IO::Select->new($self->sock); | |
232 | + while($self->next == NEXT){ | |
233 | + $select->can_read($self->timer_req); | |
234 | + } | |
235 | +} |
@@ -4,7 +4,7 @@ package Net::DHCP6::Option; | ||
4 | 4 | use strict; |
5 | 5 | use warnings; |
6 | 6 | |
7 | -our $VERSION = "0.0.1" | |
7 | +our $VERSION = "0.0.1"; | |
8 | 8 | our %OptionPkgs; |
9 | 9 | |
10 | 10 | # should overridden |
@@ -57,12 +57,12 @@ sub parse { | ||
57 | 57 | |
58 | 58 | sub register_option { |
59 | 59 | my $class = shift; |
60 | - $OptionPkgs{$class->code} = $class; | |
60 | + $OptionPkgs{+$class->code} = $class; | |
61 | 61 | } |
62 | 62 | |
63 | 63 | sub find_subclass { |
64 | 64 | my $class = shift; |
65 | - my $code = shift; | |
65 | + my $code = +shift; | |
66 | 66 | |
67 | 67 | if($class eq __PACKAGE__ && exists $OptionPkgs{$code}){ |
68 | 68 | $OptionPkgs{$code}; |
@@ -81,16 +81,30 @@ sub import { | ||
81 | 81 | my ($full, $snake); |
82 | 82 | if($import eq "raw"){ |
83 | 83 | $full = "Net::DHCP6::Option"; |
84 | - $snake = "raw"; | |
84 | + $snake = ""; | |
85 | 85 | }else{ |
86 | 86 | require "Net/DHCP6/Option/$import.pm"; |
87 | 87 | $full = "Net::DHCP6::Option::$import"; |
88 | 88 | $snake = $import; |
89 | 89 | $snake =~ s/[^A-Z_]\K(?=[A-Z]+)/_/g; |
90 | 90 | $snake = lc $snake; |
91 | + $snake = "_" . $snake; | |
92 | + } | |
93 | + { | |
94 | + no strict "refs"; | |
95 | + *{"${into}::d6opt_new${snake}"} = sub{ $full->new(@_); }; | |
91 | 96 | } |
92 | - *{"${into}::d6opt_new_${snake}"} = sub{ $full->new(@_); }; | |
93 | 97 | } |
94 | 98 | } |
95 | 99 | |
100 | +use overload ( | |
101 | + '""' => sub { | |
102 | + my $self = shift; | |
103 | + my $name = $self->name // "Option$self->code"; | |
104 | + "($name 0x" | |
105 | + . join("", map{sprintf("%02x", $_)}unpack("C*", $self->data)) | |
106 | + . ")"; | |
107 | + } | |
108 | +); | |
109 | + | |
96 | 110 | 1; |
@@ -6,13 +6,20 @@ use warnings; | ||
6 | 6 | use Net::DHCP6::SimpleOption; |
7 | 7 | use Net::DHCP6::Parameters qw/DHCP6_OPT_ELAPSED_TIME/; |
8 | 8 | |
9 | -our $VERSION = "0.0.1" | |
9 | +our $VERSION = "0.0.1"; | |
10 | 10 | our @ISA = qw/Net::DHCP6::SimpleOption/; |
11 | 11 | |
12 | 12 | use constant code => DHCP6_OPT_ELAPSED_TIME; |
13 | 13 | use constant name => "ElapsedTime"; |
14 | 14 | use constant len => 2; |
15 | 15 | |
16 | +sub new_special { | |
17 | + my $class = shift; | |
18 | + my $val = shift; | |
19 | + my $self = ref $val ? $val : \int($val * 100); | |
20 | + bless $self, $class; | |
21 | +} | |
22 | + | |
16 | 23 | sub data { |
17 | 24 | my $self = shift; |
18 | 25 | die "out of range" unless($self->time >= 0 && $self->time < 2**16); |
@@ -6,7 +6,7 @@ use warnings; | ||
6 | 6 | use Net::DHCP6::Option; |
7 | 7 | use Net::DHCP6::Parameters qw/DHCP6_OPT_ORO/; |
8 | 8 | |
9 | -our $VERSION = "0.0.1" | |
9 | +our $VERSION = "0.0.1"; | |
10 | 10 | our @ISA = qw/Net::DHCP6::Option/; |
11 | 11 | |
12 | 12 | use constant code => DHCP6_OPT_ORO; |
@@ -35,6 +35,7 @@ sub len { | ||
35 | 35 | |
36 | 36 | sub data { |
37 | 37 | my $self = shift; |
38 | + die "out of range" if(join "", map{$_ < 0 || $_ >= 2**16} @$self); | |
38 | 39 | pack("S>*", @$self); |
39 | 40 | } |
40 | 41 |
@@ -4,7 +4,7 @@ package Net::DHCP6::Parameters; | ||
4 | 4 | use strict; |
5 | 5 | use warnings; |
6 | 6 | |
7 | -our $VERSION = "0.0.1" | |
7 | +our $VERSION = "0.0.1"; | |
8 | 8 | |
9 | 9 | use Exporter; |
10 | 10 | our @ISA = ("Exporter"); |
@@ -12,7 +12,18 @@ our @ISA = ("Exporter"); | ||
12 | 12 | our @EXPORT_OK; |
13 | 13 | our %EXPORT_TAGS; |
14 | 14 | |
15 | -our %MessageType = ( | |
15 | +# RFC8415 7. | |
16 | +use constant DHCP6_NETWORK_CONSTANTS => ( | |
17 | + DHCP6_ADDR_ALL_SERVERS => "ff05::1:2", | |
18 | + DHCP6_ADDR_ALL_ONLY_SERVERS => "ff05::1:3", | |
19 | + DHCP6_PORT_CLIENT => 546, | |
20 | + DHCP6_PORT_SERVER => 547, | |
21 | +); | |
22 | +use constant {DHCP6_NETWORK_CONSTANTS}; | |
23 | +push @EXPORT_OK, keys %{{DHCP6_NETWORK_CONSTANTS}}; | |
24 | +$EXPORT_TAGS{NetworkConstant} = [keys %{{DHCP6_NETWORK_CONSTANTS}}]; | |
25 | + | |
26 | +use constant DHCP6_MESSAGE_TYPES => ( | |
16 | 27 | DHCP6_MT_SOLICIT => 1, |
17 | 28 | DHCP6_MT_ADVERTISE => 2, |
18 | 29 | DHCP6_MT_REQUEST => 3, |
@@ -49,11 +60,11 @@ our %MessageType = ( | ||
49 | 60 | DHCP6_MT_STATE => 34, |
50 | 61 | DHCP6_MT_CONTACT => 35, |
51 | 62 | ); |
52 | -use constant \%MessageType; | |
53 | -push @EXPORT_OK, keys %MessageType; | |
54 | -$EXPORT_TAGS{MessageType} = [keys %MessageType]; | |
63 | +use constant {DHCP6_MESSAGE_TYPES}; | |
64 | +push @EXPORT_OK, keys %{{DHCP6_MESSAGE_TYPES}}; | |
65 | +$EXPORT_TAGS{MessageType} = [keys %{{DHCP6_MESSAGE_TYPES}}]; | |
55 | 66 | |
56 | -our %OptionCode = ( | |
67 | +use constant DHCP6_OPTION_CODES => ( | |
57 | 68 | DHCP6_OPT_CLIENTID => 1, |
58 | 69 | DHCP6_OPT_SERVERID => 2, |
59 | 70 | DHCP6_OPT_IA_NA => 3, |
@@ -196,11 +207,11 @@ our %OptionCode = ( | ||
196 | 207 | DHCP6_OPT_V6_DOTS_ADDRESS => 142, |
197 | 208 | DHCP6_OPT_IPv6_ADDRESS_ANDSF => 143, |
198 | 209 | ); |
199 | -use constant \%OptionCode; | |
200 | -push @EXPORT_OK, keys %OptionCode; | |
201 | -$EXPORT_TAGS{OptionCode} = [keys %OptionCode]; | |
210 | +use constant {DHCP6_OPTION_CODES}; | |
211 | +push @EXPORT_OK, keys %{{DHCP6_OPTION_CODES}}; | |
212 | +$EXPORT_TAGS{OptionCode} = [keys %{{DHCP6_OPTION_CODES}}]; | |
202 | 213 | |
203 | -our %StatusCode = ( | |
214 | +use constant DHCP6_STATUS_CODES => ( | |
204 | 215 | DHCP_ST_SUCCESS => 0, |
205 | 216 | DHCP_ST_UNSPEC_FAIL => 1, |
206 | 217 | DHCP_ST_NO_ADDRS_AVAIL => 2, |
@@ -225,8 +236,8 @@ our %StatusCode = ( | ||
225 | 236 | DHCP_ST_DNS_UPDATE_NOT_SUPPORTED => 21, |
226 | 237 | DHCP_ST_EXCESSIVE_TIME_SKEW => 22, |
227 | 238 | ); |
228 | -use constant \%StatusCode; | |
229 | -push @EXPORT_OK, keys %StatusCode; | |
230 | -$EXPORT_TAGS{StatusCode} = [keys %StatusCode]; | |
239 | +use constant {DHCP6_STATUS_CODES}; | |
240 | +push @EXPORT_OK, keys %{{DHCP6_STATUS_CODES}}; | |
241 | +$EXPORT_TAGS{StatusCode} = [keys %{{DHCP6_STATUS_CODES}}]; | |
231 | 242 | |
232 | 243 | 1; |
@@ -4,7 +4,7 @@ use strict; | ||
4 | 4 | use warnings; |
5 | 5 | use Net::DHCP6::Option; |
6 | 6 | |
7 | -our $VERSION = "0.0.1" | |
7 | +our $VERSION = "0.0.1"; | |
8 | 8 | our @ISA = qw/Net::DHCP6::Option/; |
9 | 9 | |
10 | 10 | sub new { |
@@ -0,0 +1,83 @@ | ||
1 | +package Net::DHCP6::Stateless; | |
2 | +# RFC8415 6.1. | |
3 | +# RFC8415 18.2.6. | |
4 | + | |
5 | +use strict; | |
6 | +use warnings; | |
7 | + | |
8 | +use Net::DHCP6::MessageExchange qw/NEXT LAST/; | |
9 | + | |
10 | +use Net::DHCP6::Parameters qw( | |
11 | + DHCP6_MT_REPLY | |
12 | + DHCP6_MT_INFORMATION_REQUEST | |
13 | + DHCP6_OPT_INF_MAX_RT | |
14 | + DHCP6_OPT_INFORMATION_REFRESH_TIME | |
15 | +); | |
16 | +use Net::DHCP6::Message; | |
17 | +use Net::DHCP6::Option qw/ElapsedTime OptionRequest/; | |
18 | + | |
19 | +our $VERSION = "0.0.1"; | |
20 | +our @ISA = qw/Net::DHCP6::MessageExchange/; | |
21 | + | |
22 | +use constant irt => 1; | |
23 | +use constant mrc => 0; | |
24 | +use constant mrd => 0; | |
25 | + | |
26 | +sub add_elements_if_not_exists { | |
27 | + my $array = shift; | |
28 | + for my $elem(@_){ | |
29 | + if(!grep{$_ == $elem} @$array){ | |
30 | + push @$array, $elem; | |
31 | + } | |
32 | + } | |
33 | +} | |
34 | + | |
35 | +sub new { | |
36 | + my $class = shift; | |
37 | + my %opts = @_; | |
38 | + die "requesting options are not given" if(ref $opts{request} ne "ARRAY"); | |
39 | + | |
40 | + $opts{mrt} //= 3600; | |
41 | + if($opts{auto_request} // 1){ | |
42 | + add_elements_if_not_exists($opts{request}, | |
43 | + DHCP6_OPT_INF_MAX_RT, | |
44 | + DHCP6_OPT_INFORMATION_REFRESH_TIME | |
45 | + ); | |
46 | + } | |
47 | + delete $opts{auto_request}; | |
48 | + $opts{failed} //= sub{}; | |
49 | + $opts{recved} //= sub{ 1; }; | |
50 | + | |
51 | + Net::DHCP6::MessageExchange::new($class, %opts); | |
52 | +} | |
53 | + | |
54 | +sub mrt { | |
55 | + my $self = shift; | |
56 | + $self->{mrt}; | |
57 | +} | |
58 | + | |
59 | +sub failed { | |
60 | + my $self = shift; | |
61 | + $self->{failed}->(); | |
62 | +} | |
63 | + | |
64 | +sub recved { | |
65 | + my $self = shift; | |
66 | + my $recved = shift; | |
67 | + return NEXT if($recved->type != DHCP6_MT_REPLY); | |
68 | + $self->{recved}->($recved) ? LAST : NEXT; | |
69 | +} | |
70 | + | |
71 | +sub requests { | |
72 | + my $self = shift; | |
73 | + @{$self->{request}} | |
74 | +} | |
75 | + | |
76 | +sub message { | |
77 | + my $self = shift; | |
78 | + Net::DHCP6::Message->new(DHCP6_MT_INFORMATION_REQUEST, | |
79 | + d6opt_new_option_request($self->requests), | |
80 | + d6opt_new_elapsed_time($self->elapsed // 0), | |
81 | + ); | |
82 | +} | |
83 | + |
@@ -0,0 +1,22 @@ | ||
1 | +#!/bin/perl | |
2 | + | |
3 | +use strict; | |
4 | +use warnings; | |
5 | +use lib "."; | |
6 | + | |
7 | +use IO::Socket::IP; | |
8 | +use Data::Dumper; | |
9 | + | |
10 | +use Net::DHCP6::Parameters qw( | |
11 | + DHCP6_OPT_DNS_SERVERS | |
12 | +); | |
13 | +use Net::DHCP6::Stateless; | |
14 | + | |
15 | +my $client = Net::DHCP6::Stateless->new( | |
16 | + request => [], | |
17 | + failed => sub{print("timeout\n");}, | |
18 | + recved => sub{print(shift."\n"); 1;}, | |
19 | +); | |
20 | + | |
21 | +local $| = 1; | |
22 | +$client->loop; |