1 package FS::svc_IP_Mixin;
2 use base 'FS::IP_Mixin';
6 use FS::Record qw(qsearchs qsearch);
9 use FS::part_svc_router;
13 Returns the address block assigned to this service.
17 Returns the router assigned to this service, if there is one.
21 #addr_block and router methods provided by FS::IP_Mixin
25 Returns the address as a L<NetAddr::IP> object. Use C<$svc->NetAddr->addr>
26 to put it into canonical string form.
32 NetAddr::IP->new($self->ip_addr);
37 Wrapper for set/get on the IP address field.
43 my $ip_field = $self->table_info->{'ip_field'}
46 $self->set($ip_field, @_);
48 $self->get($ip_field);
54 Returns a list of L<FS::router> objects allowed on this service.
60 my $svcpart = $self->svcnum ? $self->cust_svc->svcpart : $self->svcpart;
61 my @r = map { $_->router }
62 qsearch('part_svc_router', { svcpart => $svcpart });
64 if ( $self->cust_main ) {
65 my $agentnum = $self->cust_main->agentnum;
66 return grep { !$_->agentnum or $_->agentnum == $agentnum } @r;
74 Wrapper for C<ip_check> which also checks the validity of the router.
80 my $error = $self->ip_check;
81 return $error if $error;
82 if ( my $router = $self->router ) {
83 if ( grep { $_->routernum eq $router->routernum } $self->allowed_routers ) {
86 return 'Router '.$router->routername.' not available for this service';
93 my ($class, $block, $exclude) = @_;
94 my $ip_field = $class->table_info->{'ip_field'}
96 # if the service doesn't have an ip_field, then it has no IP addresses
99 my %hash = ( $ip_field => { op => '!=', value => '' } );
100 #$hash{'blocknum'} = $block->blocknum if $block;
101 $hash{'svcnum'} = { op => '!=', value => $exclude->svcnum } if ref $exclude;
102 map { my $na = $_->NetAddr; $na ? $na->addr : () }
104 table => $class->table,
106 extra_sql => " AND $ip_field != '0e0'",
111 my ($class, $addr, $exclude) = @_;
112 my $ip_field = $class->table_info->{'ip_field'}
115 my $svc = qsearchs($class->table, { $ip_field => $addr })
118 return '' if ( ref $exclude and $exclude->svcnum == $svc->svcnum );
120 my $cust_svc = $svc->cust_svc;
122 my @label = $cust_svc->label;
123 # "svc_foo 1234 (Service Desc)"
124 # this should be enough to identify it without leaking customer
125 # names across agents
126 "$label[2] $label[3] ($label[0])";
128 join(' ', $class->table, $svc->svcnum, '(unlinked service)');
132 =item attached_router
134 Returns the L<FS::router> attached via this service (as opposed to the one
135 this service is connected through), that is, a router whose "svcnum" field
136 equals this service's primary key.
138 If the 'router_routernum' pseudo-field is set, returns that router instead.
142 sub attached_router {
144 if ( length($self->get('router_routernum') )) {
145 return FS::router->by_key($self->router_routernum);
147 qsearchs('router', { 'svcnum' => $self->svcnum });
153 Returns the address block (L<FS::addr_block>) assigned to the attached_router,
156 If the 'router_blocknum' pseudo-field is set, returns that block instead.
162 if ( length($self->get('router_blocknum')) ) {
163 return FS::addr_block->by_key($self->router_blocknum);
165 my $router = $self->attached_router or return '';
166 my ($block) = $router->addr_block;
181 Returns RADIUS reply items that are relevant across all exports and
182 necessary for the IP address configuration of the service. Currently, that
183 means "Framed-Route" if there's an attached router.
192 if ( my $block = $self->attached_block ) {
193 # block routed over dynamic IP: "192.168.100.0/29 0.0.0.0 1"
195 # block routed over fixed IP: "192.168.100.0/29 192.168.100.1 1"
196 # (the "1" at the end is the route metric)
197 $reply{'Framed-Route'} = $block->cidr . ' ' .
198 ($self->ip_addr || '0.0.0.0') . ' 1';
201 $reply{'Motorola-Canopy-Gateway'} = $self->addr_block->ip_gateway
202 if FS::Conf->new->exists('radius-canopy') && $self->addr_block;
208 my ($new, $old) = @_;
209 # this modifies $old, not $new, which is a slight abuse of replace_check,
210 # but there's no way to ensure that replace_old gets called...
212 # ensure that router_routernum and router_blocknum are set to their
213 # current values, so that exports remember the service's attached router
214 # and block even after they've been replaced
215 my $router = $old->attached_router;
216 my $block = $old->attached_block;
217 $old->set('router_routernum', $router ? $router->routernum : 0);
218 $old->set('router_blocknum', $block ? $block->blocknum : 0);
219 my $err_or_ref = $new->NEXT::replace_check($old) || '';
220 # because NEXT::replace_check($old) ends up trying to AUTOLOAD replace_check
221 # which is dumb, but easily worked around
222 ref($err_or_ref) ? '' : $err_or_ref;
227 Returns the ping status record for this service's address, if there
234 my $addr = $self->ip_addr or return;
235 qsearchs('addr_status', { 'ip_addr' => $addr });
238 =item addr_status_color
240 Returns the CSS color for the ping status of this service.
244 # subject to change; should also show high/low latency (yellow?) and
245 # staleness of data (probably means the daemon is not running) and packet
246 # loss (once we measure that)
248 sub addr_status_color {
250 if ( my $addr_status = $self->addr_status ) {
251 if ( $addr_status->up ) {