/
Ipblock.pm
3688 lines (3047 loc) · 102 KB
/
Ipblock.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package Netdot::Model::Ipblock;
use base 'Netdot::Model';
use warnings;
use strict;
use Math::BigInt;
use NetAddr::IP;
use Net::Patricia 1.19_01;
use Storable qw(nfreeze thaw);
use Scalar::Util qw(blessed);
use DBI qw(:sql_types);
=head1 NAME
Netdot::Model::Ipblock - Manipulate IP Address Space
=head1 SYNOPSIS
my $newblock = Ipblock->insert({address=>'192.168.1.0', prefix=>32});
print $newblock->cidr;
my $subnet = $newblock->parent;
print "Address Usage ", $subnet->address_usage;
=cut
my $logger = Netdot->log->get_logger('Netdot::Model::Ipblock');
BEGIN{
# Load plugins at compile time
my $ip_name_plugin_class = __PACKAGE__->config->get('DEVICE_IP_NAME_PLUGIN');
eval "require $ip_name_plugin_class";
if ( my $e = $@ ){
die $e;
}
sub _load_ip_name_plugin{
$logger->debug("Loading IP_NAME_PLUGIN: $ip_name_plugin_class");
return $ip_name_plugin_class->new();
}
my $range_dns_plugin_class = __PACKAGE__->config->get('IP_RANGE_DNS_PLUGIN');
eval "require $range_dns_plugin_class";
if ( my $e = $@ ){
die $e;
}
sub _load_range_dns_plugin{
$logger->debug("Loading IP_RANGE_DNS_PLUGIN: $range_dns_plugin_class");
return $range_dns_plugin_class->new();
}
}
my $IPV4 = Netdot->get_ipv4_regex();
my $IPV6 = Netdot->get_ipv6_regex();
my $ip_name_plugin = __PACKAGE__->_load_ip_name_plugin();
my $range_dns_plugin = __PACKAGE__->_load_range_dns_plugin();
=head1 CLASS METHODS
=cut
##################################################################
=head2 int2ip - Convert a decimal IP into a string address
Arguments:
address (decimal)
version (4 or 6)
Returns:
string
Example:
my $address = Ipblock->int2ip($number, $version);
=cut
sub int2ip {
my ($class, $address, $version) = @_;
unless ( defined($address) ) {
$class->throw_fatal(sprintf("Missing required argument: address"));
}
unless ( defined($version) ){
$class->throw_fatal(sprintf("Missing required argument: version "));
}
my $val;
if ( $version == 4 ){
$val = NetAddr::IP->new($address)->addr();
}elsif ( $version == 6 ) {
my $bigint = new Math::BigInt $address;
# Use the compressed version
$val = NetAddr::IP->new6($bigint)->short();
# Per RFC 5952 recommendation
$val = lc($val);
}else{
$class->throw_fatal(sprintf("Invalid IP version: %s", $version));
}
return $val;
}
##################################################################
=head2 search - Search Ipblock objects
We override the base search method for these reasons:
- Ipblock objects are stored as decimal integers, so
there must be a conversion prior to searching
- Allow the user to specify a CIDR address
Arguments:
Hash with field/value pairs
Returns:
Array of Ipblock objects, iterator or undef
Examples:
my @objs = Ipblock->search(field => $keyword);
=cut
sub search {
my ($class, @args) = @_;
$class->isa_class_method('search');
# Class::DBI::search() might include an extra 'options' hash ref
# at the end. In that case, we want to extract the
# field/value hash first.
my $opts = @args % 2 ? pop @args : {};
my %args = @args;
if ( defined $args{status} ){
my $statusid = $class->_get_status_id($args{status});
$args{status} = $statusid;
}
if ( defined $args{address} ){
if ( $args{address} =~ /.+\/\d+$/ ){
# Address is in CIDR format
my ($address, $prefix) = split /\//, $args{address};
$args{address} = $class->ip2int($address);
$args{prefix} = $prefix;
}elsif ( $args{address} =~ /\D/ ){
# Address contains non-digits
if ( $class->matches_ip($args{address}) ){
# Ony convert to integer if address matches valid IP formats
$args{address} = $class->ip2int($args{address});
}else{
$class->throw_user(sprintf("Address %s does not match valid IP v4/v6 formats", $args{address}));
}
}
if ( $class->config->get('DB_TYPE') eq 'mysql' ){
# Deal with mysql bug
# http://bugs.mysql.com/bug.php?id=60213
# We have to build our own query
my @keys = keys %args;
my @vals = values %args;
my $q = join(' AND ', map { "$_=?" } @keys);
my @cols = ('id');
my %essential = $class->meta_data->get_column_order_brief;
push @cols, keys %essential;
my $cols = join ',', @cols;
my $dbh = $class->db_Main();
my $sth = $dbh->prepare_cached("SELECT $cols FROM ipblock WHERE $q;");
for my $i (1..scalar(@keys)){
if ( $keys[$i-1] eq 'address' ){
# Notice that we force the value to be a string
$sth->bind_param($i, "".$vals[$i-1], SQL_INTEGER);
}else{
$sth->bind_param($i, $vals[$i-1]);
}
}
$sth->execute;
return $class->sth_to_objects($sth);
}
}
return $class->SUPER::search( %args, $opts );
}
##################################################################
=head2 search_like - Search IP Blocks that match the specified regular expression
We override the base method to adapt to the specific nature of Ipblock objects.
When specifying an address search, a Perl regular expression is expected.
The regular expression is applied to the CIDR version of the address.
The result set is limited by the configuration variable 'IPMAXSEARCH'
If search is performed on other fields, it behaves as base method (See Class::DBI).
Arguments:
hash with key/value pairs
Returns:
array of Ipblock objects sorted by address
Examples:
my @ips = Ipblock->search_like(address=>'^192.*\/32')
Returns all /32 addresses starting with 192
=cut
sub search_like {
my ($class, %argv) = @_;
$class->isa_class_method('search_like');
foreach my $key ( keys %argv ){
if ( $key eq 'address' ){
my $pattern = $argv{address};
$pattern =~ s/\./\\./g;
# we assume we are looking for IPs that start with the pattern
# because I do not think it is likely to search for a middle substring
# without knowing the beginning.
my $prefix = $pattern; # this is string prefix not the IP prefix
my $ip_version;
my $has_prefix = 0;
my $ip_pattern; # the pattern without IP prefix
my $slash_prefix;
my $lower;
my $upper;
$prefix =~ s/^\^//;
if ( $prefix =~ /\/\d+$/ ) { # if it has an IP prefix slash in it
$has_prefix = 1;
($prefix, $slash_prefix) = split /\//, $prefix;
}
$ip_pattern = $prefix;
if ( $prefix =~ /\./ ) { # IPv4
$ip_version = 4;
$prefix =~ s/^(.*)\\\.[^\.]*/$1/;
$lower = $prefix;
$upper = $prefix;
do {
$lower = $lower . "\\" . ".0";
$upper = $upper . "\\" . ".255";
} while (($lower =~ tr/\.//) < 3);
$lower =~ s/\\\./\./g;
$upper =~ s/\\\./\./g;
} elsif ( $prefix =~ /:/ ) { #IPv6
$ip_version = 6;
$prefix =~ s/^(.*):[^:]*/$1/;
$lower = $prefix;
$upper = $prefix;
do {
$lower = $lower . ":0";
$upper = $upper . ":FFFF";
} while (($lower =~ tr/://) < 7);
} else { # no indication (no '.' or ':'): search the old way
my @ipb;
my $it = $class->retrieve_all;
while ( my $ipb = $it->next ) {
if ( $ipb->cidr() =~ /$pattern/ ){
push @ipb, $ipb;
}
if ( scalar(@ipb) > $class->config->get('IPMAXSEARCH') ){
last;
}
}
@ipb = sort { $a->address_numeric <=> $b->address_numeric } @ipb;
return @ipb;
}
$lower = $class->ip2int($lower);
$upper = $class->ip2int($upper);
my $dbh = $class->db_Main;
my $sth;
eval {
$sth = $dbh->prepare("SELECT address, prefix, version FROM ipblock WHERE address>=? AND address<=?");
$sth->execute($lower, $upper);
};
$class->throw_fatal("$@") if $@;
my @ipb;
while ( my ($ipbn, $prf, $ver) = $sth->fetchrow_array() ){
if ( $ipbn && $prf && $ver ) {
# $ipbn is the numerical format
# $ipbh is the human-readable format
# $ipb is the Ipblock object
my $ipb = $class->search(address=>$ipbn)->first;
my $ipbh = $class->int2ip($ipbn, $ver);
# traditionally expected matching method
if ( $ipb->cidr() =~ /$pattern/ ){
push @ipb, $ipb;
}
# an alternative matching method which also might help
elsif ( defined($slash_prefix) && $slash_prefix ne "" && $ipbh =~ /$ip_pattern/ && $prf eq $slash_prefix ){
push @ipb, $ipb;
} elsif ( (!defined($slash_prefix) || (defined($slash_prefix) && $slash_prefix eq "")) && $ipbh =~ /$ip_pattern/ ) {
# this case might be the same as the first 'if'
push @ipb, $ipb;
}
# checking for upper limit of number of matches
if ( scalar(@ipb) > $class->config->get('IPMAXSEARCH') ){
last;
}
} # `prefix` in the database should be non-nullable defined by netdot.meta.
# Therefore we do not look at the case when it is not defined.
}
@ipb = sort { $a->address_numeric <=> $b->address_numeric } @ipb;
return @ipb;
}else{
return $class->SUPER::search_like(%argv);
}
}
}
##################################################################
=head2 keyword_search - Search by keyword
The list of search fields includes Entity, Site, Description and Comments
The result set is limited by the configuration variable 'IPMAXSEARCH'
Arguments:
string or substring
Returns:
array of Ipblock objects
Examples:
Ipblock->keyword_search('Administration');
=cut
sub keyword_search {
my ($class, $string) = @_;
$class->isa_class_method('keyword_search');
# Add wildcards
my $crit = "%" . $string . "%";
my @sites = Site->search_like (name => $crit );
my @ents = Entity->search_like(name => $crit );
my %blocks; # Hash to prevent dups
map { $blocks{$_} = $_ } __PACKAGE__->search_like(description => $crit);
map { $blocks{$_} = $_ } __PACKAGE__->search_like(info => $crit);
# Use the SiteSubnet relationship if available
map { $blocks{$_->subnet} = $_->subnet } map { $_->subnets } @sites;
# Add the entities related to the sites matching the criteria
map { push @ents, $_->entity } map { $_->entities } @sites;
# Get the Ipblocks related to those entities
map { $blocks{$_} = $_ }
map { $_->used_blocks, $_->owned_blocks } @ents;
my @ipb;
foreach ( keys %blocks ){
push @ipb, $blocks{$_};
last if (scalar (@ipb) > $class->config->get('IPMAXSEARCH'));
}
@ipb = sort { $a->address_numeric <=> $b->address_numeric } @ipb;
wantarray ? ( @ipb ) : $ipb[0];
}
##################################################################
=head2 get_unused_subnets - Retrieve subnets with no addresses
Arguments:
version - 4 or 6 (defaults to all)
Returns:
Array of Ipblock objects
Examples:
my @unused = Ipblock->get_unused_subnets(version=>4);
=cut
sub get_unused_subnets {
my ($class, %args) = @_;
$class->isa_class_method('get_unused_subnets');
my @ids;
my $query = "SELECT subnet.id, address.id
FROM ipblockstatus, ipblock subnet
LEFT JOIN ipblock address ON (address.parent=subnet.id)
WHERE subnet.status=ipblockstatus.id
AND ipblockstatus.name='Subnet'";
if ( $args{version} ){
$query .= " AND subnet.version=$args{version}";
}
my $dbh = $class->db_Main;
my $sth = $dbh->prepare_cached($query);
$sth->execute();
my $rows = $sth->fetchall_arrayref();
my %subs;
foreach my $row ( @$rows ){
my ($subnet, $address) = @$row;
if ( defined $address ){
$subs{$subnet}{$address} = 1;
}else{
$subs{$subnet} = {};
}
}
foreach my $subnet ( keys %subs ){
if ( !keys %{$subs{$subnet}} ){
push @ids, $subnet;
}
}
my @result;
foreach my $id ( @ids ){
my $ip = Ipblock->retrieve($id);
# Ignore multicast blocks
if ( $ip->is_multicast ){
next;
}
push @result, $ip;
}
@result = sort { $a->address_numeric <=> $b->address_numeric } @result;
return @result;
}
##################################################################
=head2 get_subnet_addr - Get subnet address for a given address
Arguments:
address ipv4 or ipv6 address
prefix dotted-quad netmask or prefix length
Returns:
In scalar context, returns subnet address
In list context, returns subnet address and prefix length
Examples:
my ($subnet,$prefix) = Ipblock->get_subnet_addr( address => $addr
prefix => $prefix );
=cut
sub get_subnet_addr {
my ($class, %args) = @_;
$class->isa_class_method('get_subnet_addr');
my $ip;
unless($ip = $class->netaddr(address=>$args{address}, prefix=>$args{prefix})){
$class->throw_fatal("Invalid IP: $args{address}/$args{prefix}");
}
return wantarray ? ($ip->network->addr, $ip->masklen) : $ip->network->addr;
}
##################################################################
=head2 is_loopback - Check if address is a loopback address
Arguments:
address - dotted quad ip address. Required unless called as object method.
prefix - dotted quad or prefix length. Optional.
NetAddr::IP will assume it is a host (/32 or /128)
Returns:
1 or 0
Example:
my $flag = $ipblock->is_loopback;
my $flag = Ipblock->is_loopback('127.0.0.1');
=cut
sub is_loopback{
my ( $self, $address, $prefix ) = @_;
my ($netaddr, $version);
if ( ref($self) ){
# Called as object method
$netaddr = $self->netaddr;
$version = $self->version;
}else{
# Called as class method
$self->throw_fatal("Missing required arguments when called as class method: address")
unless ( defined $address );
if ( !($netaddr = NetAddr::IP->new($address, $prefix))){
my $str = ( $address && $prefix ) ? (join '/', $address, $prefix) : $address;
$self->throw_user("Invalid IP: $str");
}
$version = $netaddr->version;
}
if ( $version == 4 &&
$netaddr->within(new NetAddr::IP '127.0.0.0', '255.0.0.0') ){
return 1;
}elsif ( $version == 6 &&
$netaddr == NetAddr::IP->new6('::1') ){
return 1;
}
return 0;
}
##################################################################
=head2 is_link_local - Check if address is v6 Link Local
Can be called as either class or instance method
Arguments:
address - IPv6 address. Required if called as class method
prefix - Prefix length. Optional. NetAddr::IP will assume it is a host (/128)
Returns:
1 or 0
Example:
my $flag = Ipblock->is_link_local('fe80::1');
my $flag = $ipblock->is_link_local();
=cut
sub is_link_local{
my ( $self, $address, $prefix ) = @_;
my $class = ref($self);
my $ip;
if ( $class ){
$ip = $self->netaddr();
}else{
$self->throw_fatal("Missing required arguments: address")
unless $address;
my $str;
if ( !($ip = NetAddr::IP->new6($address, $prefix))){
$str = ( $address && $prefix ) ? (join '/', $address, $prefix) : $address;
$self->throw_user("Invalid IP: $str");
}
}
if ( $ip->within(NetAddr::IP->new6("fe80::/10")) ) {
return 1;
}
return 0;
}
##################################################################
=head2 is_multicast - Check if address is a multicast address
Arguments:
address - dotted quad ip address. Required unless called as object method
prefix - dotted quad or prefix length. Optional. NetAddr::IP will assume it is a host (/32 or /128)
Returns:
True (1) or False (0)
Example:
my $flag = $ipblock->is_multicast();
my $flag = Ipblock->is_multicast('239.255.0.1');
=cut
sub is_multicast{
my ($self, $address, $prefix) = @_;
my ($netaddr, $version);
if ( ref($self) ){
# Called as object method
$netaddr = $self->netaddr;
$version = $self->version;
}else{
# Called as class method
$self->throw_fatal("Missing required arguments when called as class method: address")
unless ( defined $address );
if ( !($netaddr = Ipblock->netaddr($address, $prefix))){
my $str = ( $address && $prefix ) ? (join '/', $address, $prefix) : $address;
$self->throw_user("Invalid IP: $str");
}
$version = $netaddr->version;
}
if ( $version == 4 &&
$netaddr->within(new NetAddr::IP "224.0.0.0/4") ){
return 1;
}elsif ( $version == 6 &&
$netaddr->within(new6 NetAddr::IP "FF00::/8") ){
return 1;
}
return 0;
}
##################################################################
=head2 within - Check if address is within block
Arguments:
address - dotted quad ip address. Required.
block - dotted quad network address. Required.
Returns:
True or false
Example:
Ipblock->within('127.0.0.1', '127.0.0.0/8');
=cut
sub within{
my ($class, $address, $block) = @_;
$class->isa_class_method('within');
$class->throw_fatal("Ipblock::within: Missing required arguments: address and/or block")
unless ( $address && $block );
unless ( $block =~ /\// ){
$class->throw_user("Ipblock::within: $block not a valid CIDR string")
}
my ($baddr, $bprefix) = split /\//, $block;
if ( (my $ip = NetAddr::IP->new($address)) &&
(my $network = NetAddr::IP->new($baddr, $bprefix))
){
return 1 if $ip->within($network);
}
return 0;
}
##################################################################
=head2 insert - Insert a new block
Modified Arguments:
status - name of, id or IpblockStatus object (default: Container)
validate(flag) - Optionally skip validation step
no_update_tree - Do not update IP tree
Returns:
New Ipblock object or 0
Examples:
Ipblock->insert(\%data);
=cut
sub insert {
my ($class, $argv) = @_;
$class->isa_class_method('insert');
$class->throw_fatal("Missing required arguments: address")
unless ( exists $argv->{address} );
if ( $argv->{address} =~ /.+\/\d+$/o ){
# Address is in CIDR format
my ($a,$p) = split /\//, $argv->{address};
$argv->{address} = $a;
$argv->{prefix} ||= $p; # Only if not passed explicitly
}
unless ( $argv->{status} ){
if (defined $argv->{prefix} &&
($class->matches_v4($argv->{address}) && $argv->{prefix} eq '32') ||
($class->matches_v6($argv->{address}) && $argv->{prefix} eq '128')) {
$argv->{status} = "Static";
} else {
$argv->{status} = "Container";
}
}
# $ip is a NetAddr::IP object;
my $ip = $class->_prevalidate($argv->{address}, $argv->{prefix});
$argv->{address} = $ip->addr;
$argv->{prefix} = $ip->masklen;
$argv->{version} = $ip->version;
my $statusid = $class->_get_status_id($argv->{status});
$argv->{status} = $statusid;
my $timestamp = $class->timestamp;
$argv->{first_seen} = $timestamp;
$argv->{last_seen} = $timestamp;
my $no_update_tree = $argv->{no_update_tree};
delete $argv->{no_update_tree};
my $validate = 1;
if ( defined $argv->{validate} ){
$validate = $argv->{validate};
delete $argv->{validate};
}
my $newblock = $class->SUPER::insert($argv);
# Update tree unless we're told not to do so for speed reasons
# (usually because it will be rebuilt at the end of a device update)
unless ( $no_update_tree ){
eval {
$newblock->_update_tree();
};
if ( my $e = $@ ){
# assume any errors from _update_tree are caused by $newblock
$newblock->delete();
$e->rethrow() if ref($e);
$class->throw_fatal($e);
}
}
#####################################################################
# Now check for rules
# We do it after inserting because having the object and the tree
# makes things much simpler. Workarounds welcome.
# Notice that we might be told to skip validation
#####################################################################
# This is a funny hack to avoid the address being shown in numeric.
# It also makes sure that the object's attributes are updated before
# calling validation methods
my $id = $newblock->id;
undef $newblock;
$newblock = $class->retrieve($id);
if ( $validate ){
# We need to delete the object before bailing out
eval {
$newblock->_validate($argv);
};
if ( my $e = $@ ){
$newblock->delete();
$e->rethrow() if ref($e);
}
}
# Inherit some of parent's values if it's not an address
if ( !$newblock->is_address && $newblock->parent ){
$newblock->SUPER::update({owner=>$newblock->parent->owner});
}
# Generate a hostaudit entry if necessary to trigger
# a DHCP update
if ( $newblock->status->name eq 'Dynamic' ){
my %args;
$args{operation} = 'insert';
my (@fields, @values);
foreach my $col ( $newblock->columns ){
if ( defined $newblock->$col ){
push @fields, $col;
if ( $newblock->$col && blessed($newblock->$col) ){
push @values, $newblock->$col->get_label();
}else{
push @values, $newblock->$col;
}
}
}
$args{fields} = join ',', @fields;
$args{values} = join ',', map { "'$_'" } @values if @values;
$newblock->_host_audit(%args);
}
# Reserve first or last N addresses
if ( !$newblock->is_address && $newblock->status->name eq 'Subnet' ){
$newblock->reserve_first_n();
}
return $newblock;
}
#########################################################################
=head2 reserve_first_n - Reserve first (or last) N addresses in subnet
Based on config option SUBNET_AUTO_RESERVE
Arguments:
None
Returns:
True
Examples:
$block->reserve_first_n();
=cut
sub reserve_first_n {
my ($self) = @_;
$self->isa_object_method('reserve_first_n');
my $class = ref($self);
my $num = $class->config->get('SUBNET_AUTO_RESERVE');
if ( $num && $num < $self->num_addr ){
for ( 1..$num ){
my $strategy = $class->config->get('SUBNET_AUTO_RESERVE_STRATEGY');
my $addr = $self->get_next_free(strategy=>$strategy);
eval {
$class->insert({address=>$addr, status=>'Reserved',
parent=>$self->id, no_update_tree=>1,
validate=>0});
};
if ( my $e = $@ ){
# Dups are possible when running parallel processes
# Just warn and go on
$logger->warn("Ipblock::reserve_first_n: Failed to insert address: $e");
}
}
}
1;
}
##################################################################
=head2 get_covering_block - Get the closest available block that contains a given block
When a block is searched and not found, it is useful in some cases to show
the closest existing block that would contain it.
Arguments:
IP address and (optional) prefix length
Returns:
Ipblock object or undef if not found
Examples:
my $ip = Ipblock->get_covering_block(address=>$address, prefix=>$prefix);
=cut
sub get_covering_block {
my ($class, %args) = @_;
$class->isa_class_method('get_covering_block');
$class->throw_fatal('Ipblock::get_covering_block: Missing required arguments: address')
unless ( $args{address} );
my @ipargs = ($args{address});
push @ipargs, $args{prefix} if defined $args{prefix};
my $ip = NetAddr::IP->new(@ipargs);
return unless defined $ip;
my $tree = $class->_tree_get($ip->version);
# Search for this IP in the tree. We should get the parent node
my $n = $class->_tree_find(address => $ip->addr,
prefix => $ip->masklen,
tree => $tree,
version => $ip->version,
);
if ( $n ){
return Ipblock->retrieve($n);
}
}
##################################################################
=head2 get_roots - Get a list of root IP blocks
Root IP blocks are blocks at the top of the hierarchy.
This list does not include end node addresses.
Arguments:
IP version [4|6|all]
Returns:
Array of Ipblock objects, ordered by prefix length
Examples:
@list = Ipblock->get_roots($rootversion);
=cut
sub get_roots {
my ($class, $version) = @_;
$class->isa_class_method('get_roots');
$version ||= 4;
my %where = (parent => undef);
my %opts = (order_by => 'address');
my $len;
my @ipb;
if ( $version eq '4' || $version eq 'all' ){
$len = 32;
$where{version} = 4;
$where{prefix} = { '!=', $len };
push @ipb, $class->search_where(\%where, \%opts);
}
if ( $version eq '6' || $version eq 'all' ){
$len = 128;
$where{version} = 6;
$where{prefix} = { '!=', $len };
push @ipb, $class->search_where(\%where, \%opts);
}
wantarray ? ( @ipb ) : $ipb[0];
}
##################################################################
=head2 numhosts - Number of hosts (/32s) in a subnet.
Including network and broadcast addresses
Arguments:
x: the mask length (i.e. 24)
Returns:
a power of 2
=cut
sub numhosts {
## include the network and broadcast address in this count.
## will return a power of 2.
my ($class, $x) = @_;
$class->isa_class_method('numhosts');
return 2**(32-$x);
}
##################################################################
=head2 numhosts_v6 - Number of hosts (/128s) in a v6 block.
Arguments:
x: the mask length (i.e. 64)
Returns:
a power of 2
=cut
sub numhosts_v6 {
my ($class, $x) = @_;
$class->isa_class_method('numhosts');
return Math::BigInt->new(2)->bpow(128-$x);
}
##################################################################
=head2 shorten - Hide the unimportant octets from an ip address, based on the subnet
Arguments:
Hash with following keys
ipaddr a string with the ip address (i.e. 192.0.0.34)
mask the network mask (i.e. 16)
Returns:
String with just the host parts of the ip address (i.e. 0.34)
Note: No support for IPv6 yet.
=cut
sub shorten {
my ($class, %args) = @_;
$class->isa_class_method('shorten');
my ($ipaddr, $mask) = ($args{ipaddr}, $args{mask});
# this code hides the insignificant (unchanging) octets from the ip address based on the subnet
if( $mask <= 7 ) {
# no insignificant octets (128.223.112.0)
$ipaddr = $ipaddr;
} elsif( $mask <= 15 ) {
# first octet is insignificant (a.223.112.0)
$ipaddr = substr($ipaddr, index($ipaddr,".")+1);
} elsif( $mask <= 23 ) {
# second octet is insignificant (a.a.112.0)
$ipaddr = substr($ipaddr, index($ipaddr,".",index($ipaddr,".")+1)+1);
} else {
# mask is 24 or bigger, show the entire ip address (would be a.a.a.0, show 128.223.112.0)
$ipaddr = $ipaddr;
}
return $ipaddr;
}
##################################################################
=head2 subnetmask - Mask length of a subnet that can hold $x hosts
Arguments:
An integer power of 2
Returns:
integer, 0-32
Examples:
my $mask = Ipblock->subnetmask(256)
=cut
sub subnetmask {
my ($class, $x) = @_;
$class->isa_class_method('subnetmask');
return 32 - (log($x)/log(2));
}
##################################################################
=head2 subnetmask_v6 - IPv6 version of subnetmask
Arguments:
An integer power of 2
Returns:
integer, 0-128
=cut
sub subnetmask_v6 {
my ($class, $x) = @_;
$class->isa_class_method('subnetmask_v6');
return 128 - (log($x)/log(2));
}
##################################################################
=head2 build_tree - Saves IPv4 or IPv6 hierarchy in the DB
Arguments:
IP version [4|6]
Returns:
True if successful
Examples:
Ipblock->build_tree('4');
=cut
sub build_tree {
my ($class, $version) = @_;
$class->isa_class_method('build_tree');