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