1 package FS::addr_block;
5 use FS::Record qw( qsearchs qsearch dbh );
12 @ISA = qw( FS::Record );
16 FS::addr_block - Object methods for addr_block records
22 $record = new FS::addr_block \%hash;
23 $record = new FS::addr_block { 'column' => 'value' };
25 $error = $record->insert;
27 $error = $new_record->replace($old_record);
29 $error = $record->delete;
31 $error = $record->check;
35 An FS::addr_block record describes an address block assigned for broadband
36 access. FS::addr_block inherits from FS::Record. The following fields are
41 =item blocknum - primary key, used in FS::svc_broadband to associate
42 services to the block.
44 =item routernum - the router (see FS::router) to which this
47 =item ip_gateway - the gateway address used by customers within this block.
49 =item ip_netmask - the netmask of the block, expressed as an integer.
51 =item manual_flag - prohibit automatic ip assignment from this block when true.
53 =item agentnum - optional agent number (see L<FS::agent>)
63 Create a new record. To add the record to the database, see "insert".
67 sub table { 'addr_block'; }
71 Adds this record to the database. If there is an error, returns the error,
72 otherwise returns false.
76 Deletes this record from the database. If there is an error, returns the
77 error, otherwise returns false.
81 return 'Block must be deallocated before deletion'
87 =item replace OLD_RECORD
89 Replaces OLD_RECORD with this one in the database. If there is an error,
90 returns the error, otherwise returns false.
92 At present it's not possible to reallocate a block to a different router
93 except by deallocating it first, which requires that none of its addresses
94 be assigned. This is probably as it should be.
97 my ( $new, $old ) = ( shift, shift );
99 unless($new->routernum == $old->routernum) {
100 my @svc = $self->svc_broadband;
102 return 'Block has assigned addresses: '.
103 join ', ', map {$_->ip_addr} @svc;
106 return 'Block is already allocated'
107 if($new->routernum && $old->routernum);
116 Checks all fields to make sure this is a valid record. If there is an error,
117 returns the error, otherwise returns false. Called by the insert and replace
126 $self->ut_number('routernum')
127 || $self->ut_ip('ip_gateway')
128 || $self->ut_number('ip_netmask')
129 || $self->ut_enum('manual_flag', [ '', 'Y' ])
130 || $self->ut_agentnum_acl('agentnum', 'Broadband global configuration')
132 return $error if $error;
135 # A routernum of 0 indicates an unassigned block and is allowed
136 return "Unknown routernum"
137 if ($self->routernum and not $self->router);
139 my $self_addr = $self->NetAddr;
140 return "Cannot parse address: ". $self->ip_gateway . '/' . $self->ip_netmask
143 if (not $self->blocknum) {
145 my $block_addr = $_->NetAddr;
146 if($block_addr->contains($self_addr)
147 or $self_addr->contains($block_addr)) { $_; };
148 } qsearch( 'addr_block', {});
150 return "Block intersects existing block ".$_->ip_gateway."/".$_->ip_netmask;
160 Returns the FS::router object corresponding to this object. If the
161 block is unassigned, returns undef.
167 return qsearchs('router', { routernum => $self->routernum });
172 Returns a list of FS::svc_broadband objects associated
179 return qsearch('svc_broadband', { blocknum => $self->blocknum });
184 Returns a NetAddr::IP object for this block's address and netmask.
190 new NetAddr::IP ($self->ip_gateway, $self->ip_netmask);
195 Returns a CIDR string for this block's address and netmask, i.e. 10.4.20.0/24
201 $self->NetAddr->cidr;
206 Returns a NetAddr::IP object corresponding to the first unassigned address
207 in the block (other than the network, broadcast, or gateway address). If
208 there are no free addresses, returns false. There are never free addresses
209 when manual_flag is true.
216 return '' if $self->manual_flag;
218 my $conf = new FS::Conf;
219 my @excludeaddr = $conf->config('exclude_ip_addr');
222 ( (map { $_->NetAddr->addr }
224 qsearch('svc_broadband', { blocknum => $self->blocknum }))
228 my @free = $self->NetAddr->hostenum;
229 while (my $ip = shift @free) {
230 if (not grep {$_ eq $ip->addr;} @used) { return $ip; };
237 =item allocate -- deprecated
239 Allocates this address block to a router. Takes an FS::router object
242 At present it's not possible to reallocate a block to a different router
243 except by deallocating it first, which requires that none of its addresses
244 be assigned. This is probably as it should be.
249 my ($self, $router) = @_;
250 carp "deallocate deprecated -- use replace";
252 return 'Block must be allocated to a router'
253 unless(ref $router eq 'FS::router');
255 my $new = new FS::addr_block {$self->hash};
256 $new->routernum($router->routernum);
257 return $new->replace($self);
261 =item deallocate -- deprecated
263 Deallocates the block (i.e. sets the routernum to 0). If any addresses in the
264 block are assigned to services, it fails.
269 carp "deallocate deprecated -- use replace";
272 my $new = new FS::addr_block {$self->hash};
274 return $new->replace($self);
279 Splits this address block into two equal blocks, occupying the same space as
280 the original block. The first of the two will also have the same blocknum.
281 The gateway address of each block will be set to the first usable address, i.e.
282 (network address)+1. Since this method is designed for use on unallocated
283 blocks, this is probably the correct behavior.
285 (At present, splitting allocated blocks is disallowed. Anyone who wants to
286 implement this is reminded that each split costs three addresses, and any
287 customers who were using these addresses will have to be moved; depending on
288 how full the block was before being split, they might have to be moved to a
289 different block. Anyone who I<still> wants to implement it is asked to tie it
290 to a configuration switch so that site admins can disallow it.)
296 # We should consider using Attribute::Handlers/Aspect/Hook::LexWrap/
297 # something to atomicize functions, so that we can say
299 # sub split_block : atomic {
301 # instead of repeating all this AutoCommit verbage in every
302 # sub that does more than one database operation.
304 my $oldAutoCommit = $FS::UID::AutoCommit;
305 local $FS::UID::AutoCommit = 0;
312 return 'Block is already allocated';
315 #TODO: Smallest allowed block should be a config option.
316 if ($self->NetAddr->masklen() ge 30) {
317 return 'Cannot split blocks with a mask length >= 30';
321 $ip[0] = $self->NetAddr;
322 @ip = map {$_->first()} $ip[0]->split($self->ip_netmask + 1);
325 $new[$_] = new FS::addr_block {$self->hash};
326 $new[$_]->ip_gateway($ip[$_]->addr);
327 $new[$_]->ip_netmask($ip[$_]->masklen);
330 $new[1]->blocknum('');
332 $error = $new[0]->replace($self);
338 $error = $new[1]->insert;
344 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
354 Returns the agent (see L<FS::agent>) for this address block, if one exists.
359 qsearchs('agent', { 'agentnum' => shift->agentnum } );
364 Returns text including the router name, gateway ip, and netmask for this
371 my $router = $self->router;
372 ($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr;
379 Minimum block size should be a config option. It's hardcoded at /30 right
380 now because that's the smallest block that makes any sense at all.