File Coverage

blib/lib/Netstack/Utils/Ip.pm
Criterion Covered Total %
statement 85 129 65.8
branch 35 88 39.7
condition 17 66 25.7
subroutine 14 15 93.3
pod 0 11 0.0
total 151 309 48.8


line stmt bran cond sub pod time code
1             package Netstack::Utils::Ip;
2              
3             #------------------------------------------------------------------------------
4             # 加载扩展模块功能
5             #------------------------------------------------------------------------------
6 1     1   55926 use 5.016;
  1         12  
7 1     1   481 use Moose;
  1         405552  
  1         7  
8 1     1   7097 use namespace::autoclean;
  1         7126  
  1         3  
9 1     1   491 use Netstack::Utils::Set;
  1         5  
  1         3077  
10              
11             #------------------------------------------------------------------------------
12             # 定义 ipv4 正则表达式:单地址(1.1.1.1)、连续地址(1.1.1.1-2.2.2.2)和(1.1.1.1-10)
13             #------------------------------------------------------------------------------
14             has addrRegex => (
15             is => 'ro',
16             default => sub {
17             qr/^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
18             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx;
19             }
20             );
21              
22             #------------------------------------------------------------------------------
23             # 定义 rangeRegex 捕捉 1.1.1.1-2.2.2.2
24             #------------------------------------------------------------------------------
25             has rangeRegex => (
26             is => 'ro',
27             default => sub {
28             qr/^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
29             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})
30             -
31             (?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
32             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx;
33             },
34             );
35              
36             #------------------------------------------------------------------------------
37             # 定义 subRangeRegex 捕捉 1.1.1.1-100
38             #------------------------------------------------------------------------------
39             has subRangeRegex => (
40             is => 'ro',
41             default => sub {
42             qr/^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
43             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})
44             -
45             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx;
46             },
47             );
48              
49             #------------------------------------------------------------------------------
50             # 定义 ip/mask 正则表达式
51             #-----------------------------------------------------------------------------
52             has ipMask => (
53             is => 'ro',
54             default => sub {
55             qr/^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
56             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})
57             \/
58             (?:[1-9]|[12][0-9]|3[0-2])$/mx;
59             },
60             );
61              
62             #------------------------------------------------------------------------------
63             # 定义 addrWithRange 捕捉 1.1.1.1-2.2.2.2
64             #------------------------------------------------------------------------------
65             has ipRange => (
66             is => 'ro',
67             default => sub {
68             qr/(^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
69             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})
70             -
71             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$)
72             |
73             (^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
74             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})
75             -
76             (?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
77             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$)/mx;
78             },
79             );
80              
81             #------------------------------------------------------------------------------
82             # isIpv4 检验是否为 V4 地址
83             #------------------------------------------------------------------------------
84             sub isIpv4 {
85 4   50 4 0 8 my $ip = shift || return;
86              
87             # Check for invalid chars
88 4 50       15 unless ( $ip =~ m/^[\d\.]+$/ ) {
89 0         0 return 0;
90             }
91              
92 4 50       9 if ( $ip =~ m/^\./ ) {
93 0         0 return 0;
94             }
95              
96 4 50       15 if ( $ip =~ m/\.$/ ) {
97 0         0 return 0;
98             }
99              
100             # Single Numbers are considered to be IPv4
101 4 50 33     11 if ( $ip =~ m/^(\d+)$/ and $1 < 256 ) { return 1 }
  0         0  
102              
103             # Count quads
104 4         9 my $n = ( $ip =~ tr/\./\./ );
105              
106             # IPv4 must have from 1 to 4 quads
107 4 50 33     13 unless ( $n >= 0 and $n < 4 ) {
108 0         0 return 0;
109             }
110              
111             # Check for empty quads
112 4 50       8 if ( $ip =~ m/\.\./ ) {
113 0         0 return 0;
114             }
115              
116 4         11 foreach ( split /\./, $ip ) {
117             # Check for invalid quads
118 16 50 33     49 unless ( $_ >= 0 and $_ < 256 ) {
119 0         0 return 0;
120             }
121             }
122 4         23 return 1;
123             }
124              
125             #------------------------------------------------------------------------------
126             # 定义 Netstack::Utils::Ip 方法属性
127             #------------------------------------------------------------------------------
128             sub getRangeFromIpRange {
129 2     2 0 11 my ( $self, $ipMin, $ipMax ) = @_;
130             # 入参检查
131 2 50 33     10 confess __PACKAGE__ . "必须提供 ipRange (min, max)"
132             unless ( defined $ipMin and defined $ipMax );
133 2 50 33     17 confess __PACKAGE__ . "must provide two ipv4 object"
134             unless ( isIpv4($ipMin) and isIpv4($ipMax) );
135              
136             # 将 ipaddr 转换为十进制
137 2         5 my $min = $self->changeIpToInt($ipMin);
138 2         3 my $max = $self->changeIpToInt($ipMax);
139             # 返回计算结果
140             return wantarray
141 2 100       33 ? ( $min, $max )
142             : Netstack::Utils::Set->new( $min, $max );
143             }
144              
145             #------------------------------------------------------------------------------
146             # getRangeFromIpMask 通过 ip/mask掩码方式生成 IpSet 集合对象
147             #------------------------------------------------------------------------------
148             sub getRangeFromIpMask {
149 2     2 0 11 my ( $self, $ip, $mask ) = @_;
150              
151             # 匹配 1.1.1.1-2.2.2.2 样式
152 2 50       15 if (
    50          
153             $ip =~ /^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
154             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})
155             -
156             (?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
157             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx
158             )
159             {
160 0         0 my ( $min, $max ) = split( /-/, $ip );
161 0         0 return $self->getRangeFromIpRange( $min, $max );
162             }
163             # 匹配 1.1.1.1-30 样式
164             elsif (
165             $ip =~ /^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
166             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})
167             -
168             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx
169             )
170             {
171 0         0 my ( $min, $suffix ) = split( /-/, $ip );
172             # 异常拦截
173 0 0 0     0 confess __PACKAGE__ . "请正常填写$suffix,确保处于(0 .. 255)" if $suffix < 0 || $suffix > 255;
174 0         0 my ($max) = $min =~ /^(\d+\.\d+\.\d+\.)/;
175 0         0 $max .= $suffix;
176 0         0 return $self->getRangeFromIpRange( $min, $max );
177             }
178             # 兜底的处理逻辑,兼容 1.1.1.1 或 1.1.1.1/32
179 2         5 my $ipToInt = $self->changeIpToInt($ip);
180 2   50     5 $mask = $self->changeMaskToNumForm( $mask // 32 );
181 2         7 my $maskStr = ( '1' x $mask ) . ( '0' x ( 32 - $mask ) );
182 2         6 my $min = $ipToInt & oct( "0b" . $maskStr );
183 2         5 my $max = $min + oct( "0b" . ( '1' x ( 32 - $mask ) ) );
184             # 返回计算结果
185             return wantarray
186 2 100       36 ? ( $min, $max )
187             : Netstack::Utils::Set->new( $min, $max );
188             }
189              
190             #------------------------------------------------------------------------------
191             # getNetIpFromIpMask
192             #------------------------------------------------------------------------------
193             sub getNetIpFromIpMask {
194 1     1 0 7 my ( $self, $ip, $mask ) = @_;
195 1   50     3 $mask = $self->changeMaskToNumForm($mask) // 32;
196              
197             # 检查是否主机ip
198 1 50       3 return $ip if $mask == 32;
199             # 非主机ip转换为10进制
200 1         3 my $ipInt = $self->changeIpToInt($ip);
201 1         5 my $maskStr = ( '1' x $mask ) . ( '0' x ( 32 - $mask ) );
202 1         4 my $netIpNum = $ipInt & oct( "0b" . $maskStr );
203 1         3 return $self->changeIntToIp($netIpNum);
204             }
205              
206             #------------------------------------------------------------------------------
207             # changeIntToIp => # 168512809 变为 10.11.77.41
208             #------------------------------------------------------------------------------
209             sub changeIntToIp {
210 3     3 0 13 my ( $self, $num ) = @_;
211             # 入参校验
212 3 50 33     12 confess "ERROR: 调用changeIntToIp异常,入参需保证在[0 .. 4294967295]之间"
213             unless ( $num >= 0 and $num <= 4294967295 );
214             # 将数字转为 32 位 二进制格式,每隔8位代表 IPV4 的一部分,使用 . 连结
215             my $ipaddr = join(
216 3         48 '.', map { oct( "0b" . $_ ) }
  12         27  
217             split( /(?=(?:[01]{8})+$)/, sprintf( "%032b", $num ) )
218             );
219             # 返回计算结果
220 3         13 return $ipaddr;
221             }
222              
223             #------------------------------------------------------------------------------
224             # changeIpToInt => 把 10.11.77.41 变为 168512809
225             #------------------------------------------------------------------------------
226             sub changeIpToInt {
227 10     10 0 21 my ( $self, $addr ) = @_;
228 10 50       20 confess __PACKAGE__ . "必须提供正确的IPv4地址 $addr" unless defined $addr;
229             # IPV4 样式判断
230 10 50       36 unless (
231             $addr =~ /^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
232             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx
233             )
234             {
235 0 0       0 if ( $addr =~ /any|all/i ) {
236 0         0 $addr = "0.0.0.0";
237             }
238             else {
239 0         0 confess __PACKAGE__ . " ERROR: IP地址 ($addr) 格式有误, 调用函数changeIpToInt失败!";
240             }
241             }
242             # 主体处理逻辑
243 10 50 33     24 my @addr = map { ( not defined || /^\s*$/ ) ? 0 : $_ } split( /\./, $addr );
  40         88  
244 10 50       20 confess __PACKAGE__ . "ipv4 $addr 解析异常,请正确提供IPV4地址" if scalar @addr != 4;
245 10         28 my $ipNum = ( $addr[0] << 24 ) + ( $addr[1] << 16 ) + ( $addr[2] << 8 ) + $addr[3];
246              
247             # 返回计算结果
248 10         36 return $ipNum;
249             }
250              
251             #------------------------------------------------------------------------------
252             # changeMaskToNumForm => 把 255.255.255.0 变为 24
253             #------------------------------------------------------------------------------
254             sub changeMaskToNumForm {
255 5     5 0 15 my ( $self, $mask ) = @_;
256             # 入参校验
257 5 50       10 confess __PACKAGE__ . "ERROR: 调用changeMaskToNumForm异常,请正确提供 mask,如255.0.0.0"
258             unless defined $mask;
259              
260 5 100       21 if (
    50          
261             $mask =~ /^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
262             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx
263             )
264             {
265 2         5 my $IpStr = sprintf( "%032b", $self->changeIpToInt($mask) );
266 2 50       10 if ( $IpStr =~ /01/ ) {
    50          
267 0         0 confess "ERROR: 函数changeMaskToNumForm入参校验不通过,请正确填写入参";
268             }
269             elsif ( $IpStr =~ /^(1+)/ ) {
270 2         5 $mask = length($1);
271             }
272             else {
273 0         0 $mask = 0;
274             }
275             }
276             elsif ( $mask !~ /^\d+$/o ) {
277 0         0 confess __PACKAGE__ . "ERROR: 调用changeMaskToNumForm异常,转换后的掩码必须为[1..32]的数字";
278             }
279             # 边界条件判断
280 5 50 33     18 if ( $mask < 0 or $mask > 32 ) {
281 0         0 confess __PACKAGE__ . "ERROR: 调用changeMaskToNumForm异常,IPV4掩码越限需保证在[1..32]";
282             }
283             # 返回计算结果
284 5         15 return $mask;
285             }
286              
287             #------------------------------------------------------------------------------
288             # changeWildcardToMaskForm => 反掩码转换为正掩码,如0.0.0.255 改为255.0.0.0
289             #------------------------------------------------------------------------------
290             sub changeWildcardToMaskForm {
291 1     1 0 9 my ( $self, $wildcard ) = @_;
292 1 50       8 if (
293             $wildcard =~ /(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.
294             (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.
295             (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.
296             (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])/mx
297             )
298             {
299 1         7 my ( $p1, $p2, $p3, $p4 ) = ( $1 ^ 255, $2 ^ 255, $3 ^ 255, $4 ^ 255 );
300 1         6 my $mask = "$p1.$p2.$p3.$p4";
301 1         3 return $mask;
302             }
303             else {
304 0         0 confess __PACKAGE__ . "ERROR: 调用changeWildcardToMaskForm异常,请正确填写反掩码";
305             }
306             }
307              
308             #------------------------------------------------------------------------------
309             # changeMaskToIpForm => 掩码转IP格式,如把 24 变为 255.255.255.0
310             #------------------------------------------------------------------------------
311             sub changeMaskToIpForm {
312 2     2 0 12 my ( $self, $mask ) = @_;
313              
314             # 本身就匹配正则表达式
315 2 100 33     14 if (
    50          
316             $mask =~ /^(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\.){3}
317             (?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$/mx
318             )
319             {
320 1         3 return $mask;
321             }
322             # 处于正常区间
323             elsif ( $mask >= 0 and $mask <= 32 ) {
324 1         5 my $maskString = ( '1' x $mask ) . ( '0' x ( 32 - $mask ) );
325 1         7 my @ip = $maskString =~ /([01]{8})/g;
326             # 数组组装成 ipv4
327 1         2 my $addr = join( ".", map { oct( "0b" . $_ ) } @ip );
  4         10  
328 1         6 return $addr;
329             }
330             else {
331 0         0 confess __PACKAGE__ . "ERROR: 调用changeMaskToIpForm异常,请正确填写掩码格式";
332             }
333             }
334              
335             #------------------------------------------------------------------------------
336             # getIpMaskFromRange => 转换 IpSet 为 Subnet
337             #------------------------------------------------------------------------------
338             sub getIpMaskFromRange {
339 1     1 0 7 my ( $self, $min, $max ) = @_;
340             # 入参检查
341 1 50 33     4 confess "调用函数getIpMaskFromRange失败,请正确提供 min max "
342             if not defined $min || not defined $max;
343             # 异常拦截
344 1 50 33     10 confess "请正确填写 min max 区间值,必须介于 (0 .. 4294967295)之间"
      33        
      33        
345             unless ( $min >= 0 and $min <= 4294967295 )
346             and ( $max >= 0 || $max <= 4294967295 );
347              
348             # 将最小值转为 IPADDR
349 1         3 my $minIp = $self->changeIntToIp($min);
350             # 集合区间计数
351 1         3 my $range = $max - $min + 1;
352 1         7 my $mask = int( 32 - log($range) / log(2) );
353             # TODOS:数学计算
354 1 50 33     18 if ( $min == ( $min & ( ( 1 << 32 ) - ( 1 << ( 32 - $mask ) ) ) )
355             and $max == $min + ( 1 << ( 32 - $mask ) ) - 1 )
356             {
357 1         6 return $minIp . '/' . $mask;
358             }
359             else {
360 0           return $minIp . '-' . $self->changeIntToIp($max);
361             }
362             }
363              
364             #------------------------------------------------------------------------------
365             # getRangeFromService => Services 切割
366             #------------------------------------------------------------------------------
367             sub getRangeFromService {
368 0     0 0   my ( $self, $service ) = @_;
369             # 异常拦截
370 0 0         confess __PACKAGE__ . " Error: 必须提供服务端口如TCP/80"
371             unless defined $service;
372             # 切割变量
373 0           my ( $proto, $port ) = split( '/', $service );
374              
375             # 边界条件处理
376 0           my $protoNum;
377 0 0 0       if ( $proto eq '0' or $proto =~ /any|all/i ) {
    0          
378             return wantarray
379 0 0         ? ( 0, 16777215 )
380             : Netstack::Utils::Set->new( 0, 16777215 );
381             }
382             elsif ( $proto =~ /tcp|udp|icmp|\d+/i ) {
383 0 0         if ( $proto =~ /tcp/i ) {
    0          
    0          
    0          
384 0           $protoNum = 6;
385             }
386             elsif ( $proto =~ /udp/i ) {
387 0           $protoNum = 17;
388             }
389             elsif ( $proto =~ /icmp/i ) {
390 0           $protoNum = 1;
391             }
392             elsif ( $proto =~ /\d+/i ) {
393 0           $protoNum = $proto;
394             }
395             }
396             # 协议代码移位计算
397 0           my $protoValue = $protoNum << 16;
398              
399 0           my ( $min, $max );
400 0 0         if ( defined $port ) {
401 0           ( $min, $max ) = split( /-|\s+/, $port );
402 0   0       $max //= $min;
403 0 0 0       confess __PACKAGE__ . "必须确保服务端口处于区间 (0 .. 65535)"
      0        
      0        
404             unless ( $min >= 0 and $min <= 65535 )
405             and ( $max >= 0 and $max <= 65535 );
406             }
407             else {
408 0           $min = 0;
409 0           $max = 0;
410             }
411             return wantarray
412 0 0         ? ( $protoValue + $min, $protoValue + $max )
413             : Netstack::Utils::Set->new( $protoValue + $min, $protoValue + $max );
414             }
415              
416             __PACKAGE__->meta->make_immutable;
417             1;