summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitch Jackson <mitch@freeside.biz>2018-06-25 14:07:52 -0500
committerMitch Jackson <mitch@freeside.biz>2018-10-23 17:14:36 -0400
commit2ee7f0c27233d800254d5244fc5913d881b48800 (patch)
tree638d31a6d69d7a577a7038484a1251d70b2237f5
parentd5357a8ed89d3bfb9f861cd073a472a88768f9a1 (diff)
RT# 30783 Add network block enumerating utils
-rw-r--r--FS/FS/IP_Mixin.pm28
-rwxr-xr-xFS/FS/addr_block.pm18
-rw-r--r--FS/FS/svc_IP_Mixin.pm69
3 files changed, 110 insertions, 5 deletions
diff --git a/FS/FS/IP_Mixin.pm b/FS/FS/IP_Mixin.pm
index 8920ceb..07fa9e7 100644
--- a/FS/FS/IP_Mixin.pm
+++ b/FS/FS/IP_Mixin.pm
@@ -270,9 +270,10 @@ sub router {
=item used_addresses [ BLOCK ]
-Returns a list of all addresses (in BLOCK, or in all blocks)
-that are in use. If called as an instance method, excludes
-that instance from the search.
+Returns a list of all addresses that are in use by a service. If called as an
+instance method, excludes that instance from the search.
+
+Does not filter by block, will return ALL used addresses. ref:f197bdbaa1
=cut
@@ -287,6 +288,27 @@ sub _used_addresses {
die "$class->_used_addresses not implemented";
}
+=item used_addresses_in_block [ FS::addr_block ]
+
+Returns a list of all addresses in use within the given L<FS::addr_block>
+
+=cut
+
+sub used_addresses_in_block {
+ my ($self, $block) = @_;
+
+ (
+ $block->ip_gateway ? $block->ip_gateway : (),
+ $block->NetAddr->broadcast->addr,
+ map { $_->_used_addresses_in_block($block, $self ) } @subclasses
+ );
+}
+
+sub _used_addresses_in_block {
+ my $class = shift;
+ die "$class->_used_addresses_in_block not implemented";
+}
+
=item is_used ADDRESS
Returns a string describing what object is using ADDRESS, or
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
index f07de49..a39e1f1 100755
--- a/FS/FS/addr_block.pm
+++ b/FS/FS/addr_block.pm
@@ -238,6 +238,23 @@ sub cidr {
$self->NetAddr->cidr;
}
+=item free_addrs
+
+Returns a sorted list of free addresses in the block.
+
+=cut
+
+sub free_addrs {
+ my $self = shift;
+
+ my %used_addr_map =
+ map {$_ => 1}
+ FS::IP_Mixin->used_addresses_in_block($self),
+ FS::Conf->new()->config('exclude_ip_addr');
+
+ grep { !exists $used_addr_map{$_} } map { $_->addr } $self->NetAddr->hostenum;
+}
+
=item next_free_addr
Returns a NetAddr::IP object corresponding to the first unassigned address
@@ -433,4 +450,3 @@ now because that's the smallest block that makes any sense at all.
=cut
1;
-
diff --git a/FS/FS/svc_IP_Mixin.pm b/FS/FS/svc_IP_Mixin.pm
index 8b2b5f1..ce9218c 100644
--- a/FS/FS/svc_IP_Mixin.pm
+++ b/FS/FS/svc_IP_Mixin.pm
@@ -3,7 +3,8 @@ use base 'FS::IP_Mixin';
use strict;
use NEXT;
-use FS::Record qw(qsearchs qsearch);
+use Carp qw(croak carp);
+use FS::Record qw(qsearchs qsearch dbh);
use FS::Conf;
use FS::router;
use FS::part_svc_router;
@@ -90,6 +91,9 @@ sub svc_ip_check {
}
sub _used_addresses {
+
+ # Returns all addresses in use. Does not filter with $block. ref:f197bdbaa1
+
my ($class, $block, $exclude) = @_;
my $ip_field = $class->table_info->{'ip_field'}
or return ();
@@ -107,6 +111,69 @@ sub _used_addresses {
});
}
+sub _used_addresses_in_block {
+ my ($class, $block) = @_;
+
+ croak "_used_addresses_in_block() requires an FS::addr_block parameter"
+ unless ref $block && $block->isa('FS::addr_block');
+
+ my $ip_field = $class->table_info->{'ip_field'};
+ if ( !$ip_field ) {
+ carp "_used_addresses_in_block() skipped, no ip_field";
+ return;
+ }
+
+ my $block_na = $block->NetAddr;
+
+ my $octets;
+ if ($block->ip_netmask >= 24) {
+ $octets = 3;
+ } elsif ($block->ip_netmask >= 16) {
+ $octets = 2;
+ } elsif ($block->ip_netmask >= 8) {
+ $octets = 1;
+ }
+
+ # e.g.
+ # SELECT ip_addr
+ # FROM svc_broadband
+ # WHERE ip_addr != ''
+ # AND ip_addr != '0e0'
+ # AND ip_addr LIKE '10.0.2.%';
+ #
+ # For /24, /16 and /8 this approach is fast, even when svc_broadband table
+ # contains 650,000+ ip records. For other allocations, this approach is
+ # not speedy, but usable.
+ #
+ # Note: A use case like this would could greatly benefit from a qsearch()
+ # parameter to bypass FS::Record objects creation and just
+ # return hashrefs from DBI. 200,000 hashrefs are many seconds faster
+ # than 200,000 FS::Record objects
+ my %qsearch = (
+ table => $class->table,
+ select => $ip_field,
+ hashref => { $ip_field => { op => '!=', value => '' }},
+ extra_sql => " AND $ip_field != '0e0' ",
+ );
+ if ( $octets ) {
+ my $block_str = join('.', (split(/\D/, $block_na->first))[0..$octets-1]);
+ $qsearch{extra_sql} .= " AND $ip_field LIKE ".dbh->quote("${block_str}.%");
+ }
+
+ if ( $block->ip_netmask % 8 ) {
+ # Some addresses returned by qsearch may be outside the network block,
+ # so each ip address is tested to be in the block before it's returned.
+ return
+ grep { $block_na->contains( NetAddr::IP->new( $_ ) ) }
+ map { $_->$ip_field }
+ qsearch( \%qsearch );
+ }
+
+ return
+ map { $_->$ip_field }
+ qsearch( \%qsearch );
+}
+
sub _is_used {
my ($class, $addr, $exclude) = @_;
my $ip_field = $class->table_info->{'ip_field'}