summaryrefslogtreecommitdiff
path: root/FS/bin/freeside-pingd
blob: fc9f8a3783c61bdd3d546d7700577972048a3ba3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/perl

use strict;
use FS::Daemon ':all';
use FS::UID qw(dbh adminsuidsetup);
use FS::Record qw( dbh qsearch qsearchs );
use FS::addr_status;
use FS::Conf;
use Getopt::Std;
use Net::Ping;

my @TARGETS = (
  'tower_sector',
  'svc_broadband',
  # could add others here
);

my $timeout  = 5.0; # seconds

# useful opts: scan interval, timeout, verbose, max forks
# maybe useful opts: interface, protocol, packet size, no-fork

my $interval;

our %opt;
getopts('vxi:', \%opt);
my $user = shift or die usage();

if (!$opt{x}) {
  daemonize1('freeside-pingd');
  drop_root();
  daemonize2();
}

if ($opt{i}) {
  $interval = $opt{i};
}

sub debug {
  warn(@_, "\n") if $opt{v};
}

adminsuidsetup($user);
$FS::UID::AutoCommit = 1;

if ( !$interval ) {
  my $conf = FS::Conf->new;
  $interval = $conf->config('pingd-interval');
  if ( !$interval ) {
    debug("no pingd-interval configured; exiting");
    exit(0);
  }
}

while(1) {
  daemon_reconnect();
  my @addrs_to_scan;
  foreach my $table (@TARGETS) {
    # find addresses that need to be scanned (haven't been yet, or are
    # expired)
    my $expired = time - $interval;
    debug("checking addresses from $table");

    my $statement = "SELECT ip_addr FROM $table
      LEFT JOIN addr_status USING (ip_addr)
      WHERE $table.ip_addr IS NOT NULL
        AND (addr_status.ip_addr IS NULL OR addr_status._date <= ?)
      ORDER BY COALESCE(addr_status._date, 0)";
    my $addrs = dbh->selectcol_arrayref($statement, {}, $expired);
    die dbh->errstr if !defined $addrs;
    debug("found ".scalar(@$addrs));
    push @addrs_to_scan, @$addrs;
  }

  # fork to handle this since we're going to spend most of our time
  # waiting for remote machines to respond
  foreach my $addr (@addrs_to_scan) {
    daemon_fork( \&scan, $addr );
  }

  debug("waiting for scan to complete");
  # wait until finished
  daemon_wait();

  # sleep until there's more work to do:
  # the oldest record that still has an expire time in the future
  # (as opposed to records for dead addresses, which will not be rescanned)
  my $next_expire = FS::Record->scalar_sql(
    'SELECT MIN(_date) FROM addr_status WHERE _date + ? > ?',
    $interval, time
  ) || time;
  my $delay = $next_expire + $interval - time;
  # but at least scan every $interval seconds, to pick up new addresses
  $delay = $interval if $delay > $interval;

  if ( $delay > 0 ) {
    debug("it is now ".time."; sleeping for $delay");
    sleep($delay);
  } else {
    debug("it is now ".time."; continuing");
  }

} # main loop

sub scan {
  # currently just sends a single ping; it might be more useful to send
  # several of them and estimate packet loss.

  my $addr = shift;
  my $addr_status = qsearchs('addr_status', { 'ip_addr' => $addr })
                    || FS::addr_status->new({ 'ip_addr' => $addr });

  $addr_status->select_for_update if $addr_status->addrnum;
  my $ping = Net::Ping->new;
  $ping->hires;
  debug "pinging $addr";
  my ($result, $latency) = $ping->ping($addr, $timeout);
  debug "status $result, delay $latency";
  $addr_status->set('up', $result ? 'Y' : '');
  $addr_status->set('delay', int($latency * 1000));
  $addr_status->set('_date', time);
  my $error = $addr_status->addrnum ?
                $addr_status->replace :
                $addr_status->insert;
  if ( $error ) {
    die "ERROR: could not update status for $addr\n$error\n";
  }
}

sub usage {
  "Usage:
  freeside-pingd [ -i INTERVAL ] [ -v ] [ -x ] <username>
";
}