cleanup
[freeside.git] / FS / bin / freeside-pingd
1 #!/usr/bin/perl
2
3 use strict;
4 use FS::Daemon ':all';
5 use FS::UID qw(dbh adminsuidsetup);
6 use FS::Record qw( dbh qsearch qsearchs );
7 use FS::addr_status;
8 use Getopt::Std;
9 use Net::Ping;
10
11 my @TARGETS = (
12   'tower_sector',
13   'svc_broadband',
14   # could add others here
15 );
16
17 my $interval = 300; # seconds
18 my $timeout  = 5.0; # seconds
19
20 # useful opts: scan interval, timeout, verbose, max forks
21 # maybe useful opts: interface, protocol, packet size, no-fork
22
23 our %opt;
24 getopts('vxi:', \%opt);
25 my $user = shift or die usage();
26
27 if (!$opt{x}) {
28   daemonize1('freeside-pingd');
29   drop_root();
30   daemonize2();
31 }
32
33 if ($opt{i}) {
34   $interval = $opt{i};
35 }
36
37 sub debug {
38   warn(@_, "\n") if $opt{v};
39 }
40
41 adminsuidsetup($user);
42 $FS::UID::AutoCommit = 1;
43
44 while(1) {
45   daemon_reconnect();
46   my @addrs_to_scan;
47   foreach my $table (@TARGETS) {
48     # find addresses that need to be scanned (haven't been yet, or are
49     # expired)
50     my $expired = time - $interval;
51     debug("checking addresses from $table");
52
53     my $statement = "SELECT ip_addr FROM $table
54       LEFT JOIN addr_status USING (ip_addr)
55       WHERE $table.ip_addr IS NOT NULL
56         AND (addr_status.ip_addr IS NULL OR addr_status._date <= ?)
57       ORDER BY COALESCE(addr_status._date, 0)";
58     my $addrs = dbh->selectcol_arrayref($statement, {}, $expired);
59     die dbh->errstr if !defined $addrs;
60     debug("found ".scalar(@$addrs));
61     push @addrs_to_scan, @$addrs;
62   }
63
64   # fork to handle this since we're going to spend most of our time
65   # waiting for remote machines to respond
66   foreach my $addr (@addrs_to_scan) {
67     daemon_fork( \&scan, $addr );
68   }
69
70   debug("waiting for scan to complete");
71   # wait until finished
72   daemon_wait();
73
74   # sleep until there's more work to do:
75   # the oldest record that still has an expire time in the future
76   # (as opposed to records for dead addresses, which will not be rescanned)
77   my $next_expire = FS::Record->scalar_sql(
78     'SELECT MIN(_date) FROM addr_status WHERE _date + ? > ?',
79     $interval, time
80   ) || time;
81   my $delay = $next_expire + $interval - time;
82   # but at least scan every $interval seconds, to pick up new addresses
83   $delay = $interval if $delay > $interval;
84
85   if ( $delay > 0 ) {
86     debug("it is now ".time."; sleeping for $delay");
87     sleep($delay);
88   } else {
89     debug("it is now ".time."; continuing");
90   }
91
92 } # main loop
93
94 sub scan {
95   # currently just sends a single ping; it might be more useful to send
96   # several of them and estimate packet loss.
97
98   my $addr = shift;
99   my $addr_status = qsearchs('addr_status', { 'ip_addr' => $addr })
100                     || FS::addr_status->new({ 'ip_addr' => $addr });
101
102   $addr_status->select_for_update if $addr_status->addrnum;
103   my $ping = Net::Ping->new;
104   $ping->hires;
105   debug "pinging $addr";
106   my ($result, $latency) = $ping->ping($addr, $timeout);
107   debug "status $result, delay $latency";
108   $addr_status->set('up', $result ? 'Y' : '');
109   $addr_status->set('delay', int($latency * 1000));
110   $addr_status->set('_date', time);
111   my $error = $addr_status->addrnum ?
112                 $addr_status->replace :
113                 $addr_status->insert;
114   if ( $error ) {
115     die "ERROR: could not update status for $addr\n$error\n";
116   }
117 }
118
119 sub usage {
120   "Usage:
121   freeside-pingd [ -i INTERVAL ] [ -v ] [ -x ] <username>
122 ";
123 }
124