1 package FS::svc_IP_Mixin;
2 use base 'FS::IP_Mixin';
6 use Carp qw(croak carp);
7 use FS::Record qw(qsearchs qsearch dbh);
10 use FS::part_svc_router;
14 Returns the address block assigned to this service.
18 Returns the router assigned to this service, if there is one.
22 #addr_block and router methods provided by FS::IP_Mixin
26 Returns the address as a L<NetAddr::IP> object. Use C<$svc->NetAddr->addr>
27 to put it into canonical string form.
33 NetAddr::IP->new($self->ip_addr);
38 Wrapper for set/get on the IP address field.
44 my $ip_field = $self->table_info->{'ip_field'}
47 $self->set($ip_field, @_);
49 $self->get($ip_field);
55 Returns a list of L<FS::router> objects allowed on this service.
61 my $svcpart = $self->svcnum ? $self->cust_svc->svcpart : $self->svcpart;
62 my @r = map { $_->router }
63 qsearch('part_svc_router', { svcpart => $svcpart });
65 if ( $self->cust_main ) {
66 my $agentnum = $self->cust_main->agentnum;
67 return grep { !$_->agentnum or $_->agentnum == $agentnum } @r;
75 Wrapper for C<ip_check> which also checks the validity of the router.
81 my $error = $self->ip_check;
82 return $error if $error;
83 if ( my $router = $self->router ) {
84 if ( grep { $_->routernum == $router->routernum } $self->allowed_routers ) {
87 return 'Router '.$router->routername.' not available for this service';
94 my ($class, $block, $exclude_svc) = @_;
96 croak "_used_addresses() requires an FS::addr_block parameter"
97 unless ref $block && $block->isa('FS::addr_block');
99 my $ip_field = $class->table_info->{'ip_field'};
101 carp "_used_addresses() skipped, no ip_field";
105 my %qsearch = ( $ip_field => { op => '!=', value => '' });
106 $qsearch{svcnum} = { op => '!=', value => $exclude_svc->svcnum }
107 if ref $exclude_svc && $exclude_svc->svcnum;
109 my $block_na = $block->NetAddr;
112 if ($block->ip_netmask >= 24) {
114 } elsif ($block->ip_netmask >= 16) {
116 } elsif ($block->ip_netmask >= 8) {
123 # WHERE ip_addr != ''
124 # AND ip_addr != '0e0'
125 # AND ip_addr LIKE '10.0.2.%';
127 # For /24, /16 and /8 this approach is fast, even when svc_broadband table
128 # contains 650,000+ ip records. For other allocations, this approach is
129 # not speedy, but usable.
131 # Note: A use case like this would could greatly benefit from a qsearch()
132 # parameter to bypass FS::Record objects creation and just
133 # return hashrefs from DBI. 200,000 hashrefs are many seconds faster
134 # than 200,000 FS::Record objects
135 my %qsearch_param = (
136 table => $class->table,
138 hashref => \%qsearch,
139 extra_sql => " AND $ip_field != '0e0' ",
142 my $block_str = join('.', (split(/\D/, $block_na->first))[0..$octets-1]);
143 $qsearch_param{extra_sql}
144 .= " AND $ip_field LIKE ".dbh->quote("${block_str}.%");
147 if ( $block->ip_netmask % 8 ) {
148 # Some addresses returned by qsearch may be outside the network block,
149 # so each ip address is tested to be in the block before it's returned.
151 grep { $block_na->contains( NetAddr::IP->new( $_ ) ) }
152 map { $_->$ip_field }
153 qsearch( \%qsearch );
157 map { $_->$ip_field }
158 qsearch( \%qsearch_param );
162 my ($class, $addr, $exclude) = @_;
163 my $ip_field = $class->table_info->{'ip_field'}
166 my $svc = qsearchs($class->table, { $ip_field => $addr })
169 return '' if ( ref $exclude and $exclude->svcnum == $svc->svcnum );
171 my $cust_svc = $svc->cust_svc;
173 my @label = $cust_svc->label;
174 # "svc_foo 1234 (Service Desc)"
175 # this should be enough to identify it without leaking customer
176 # names across agents
177 "$label[2] $label[3] ($label[0])";
179 join(' ', $class->table, $svc->svcnum, '(unlinked service)');
183 =item attached_router
185 Returns the L<FS::router> attached via this service (as opposed to the one
186 this service is connected through), that is, a router whose "svcnum" field
187 equals this service's primary key.
189 If the 'router_routernum' pseudo-field is set, returns that router instead.
193 sub attached_router {
195 if ( length($self->get('router_routernum') )) {
196 return FS::router->by_key($self->router_routernum);
198 qsearchs('router', { 'svcnum' => $self->svcnum });
204 Returns the address block (L<FS::addr_block>) assigned to the attached_router,
207 If the 'router_blocknum' pseudo-field is set, returns that block instead.
213 if ( length($self->get('router_blocknum')) ) {
214 return FS::addr_block->by_key($self->router_blocknum);
216 my $router = $self->attached_router or return '';
217 my ($block) = $router->addr_block;
232 Returns RADIUS reply items that are relevant across all exports and
233 necessary for the IP address configuration of the service. Currently, that
234 means "Framed-Route" if there's an attached router.
243 if ( my $block = $self->attached_block ) {
244 # block routed over dynamic IP: "192.168.100.0/29 0.0.0.0 1"
246 # block routed over fixed IP: "192.168.100.0/29 192.168.100.1 1"
247 # (the "1" at the end is the route metric)
248 $reply{'Framed-Route'} = $block->cidr . ' ' .
249 ($self->ip_addr || '0.0.0.0') . ' 1';
252 $reply{'Motorola-Canopy-Gateway'} = $self->addr_block->ip_gateway
253 if FS::Conf->new->exists('radius-canopy') && $self->addr_block;
259 my ($new, $old) = @_;
260 # this modifies $old, not $new, which is a slight abuse of replace_check,
261 # but there's no way to ensure that replace_old gets called...
263 # ensure that router_routernum and router_blocknum are set to their
264 # current values, so that exports remember the service's attached router
265 # and block even after they've been replaced
266 my $router = $old->attached_router;
267 my $block = $old->attached_block;
268 $old->set('router_routernum', $router ? $router->routernum : 0);
269 $old->set('router_blocknum', $block ? $block->blocknum : 0);
270 my $err_or_ref = $new->NEXT::replace_check($old) || '';
271 # because NEXT::replace_check($old) ends up trying to AUTOLOAD replace_check
272 # which is dumb, but easily worked around
273 ref($err_or_ref) ? '' : $err_or_ref;