不久前,我在家庭ADSL线路上遇到“松脱”现象。我在地下室,VOIP和其他一些设备(例如Netflix)上运行Linux服务器,每隔几小时就会遇到几分钟的网络故障。这丢掉了非常重要的内容,例如孩子们看动画片以及我与父母打来的电话。

与ISP交流(他们很棒,我没有抱怨),他们想知道通常,连接断开了,我不敢轻易说...所以我写了一个脚本来监视...。

它所做的所有事情都是每秒对ADSL线路的远端进行一次ping(网关)。这只是一小步,并且影响不大。

这是我编写的脚本,基于此,我能够解决问题,并将相关邮件复制到ISP,并且他们能够将问题与某个地方的问题相关联...最终我替换了我的ADSL调制解调器,问题得以解决。

长话大说,有些短代码,但这是经典的perl hack ...

我的perl有点过时,因此我正在寻找有关如何使自己的用法现代化的想法,以及其他任何提示和技巧。

#!/usr/bin/perl

use strict;
use warnings;
use autodie;

use Net::Ping;
use Mail::Sendmail;

$| = 1;

my $defaultmail = 'rolfl@xxx.yyy';
my $defaultip = "8.8.8.8"; # Google's nameserver... 

my $host = shift @ARGV;
$host ||= $defaultip;

my $mailto = shift @ARGV;
$mailto ||= $defaultmail;

sub mail {

  my $subj = shift;
  my $msg = scalar(localtime) . " -> " . $subj;
  my %mail;

  %mail = (smtp    => 'localhost:25',
           To      => $mailto,
           From    => ,$mailto,
           'X-InternetUp' => "q4312078q",
           Subject => $subj,
           Message => $msg);

  sendmail(%mail) or die;
}


my $ping = Net::Ping->new();

my $up = 0; # start with it down, and it sends an 'up mail'.
my $start = time();

mail ("Starting network monitor");

while (1) {
  sleep 1;

  if ($ping->ping($host)) {
    if (!$up) {
      my $interval = time() - $start;
      $start += $interval;
      mail(sprintf("INTERNET -> JUST COME BACK - Was DOWN for %d seconds\n", $interval));
    }
    $up = 1;
  } else {
    if ($up) {
      my $interval = time() - $start;
      $start += $interval;
      mail(sprintf("INTERNET -> JUST GONE DOWN - Was UP for %d seconds\n", $interval));
    }
    $up = 0;

  }

}


该脚本留下类似的邮件:

 Subject: INTERNET -> JUST GONE DOWN - Was UP for 65174 seconds
X-Internetup: ./netup
Date: Thu, 23 Oct 2014 14:07:23 -0400
To: rolfl@....
Content-Transfer-Encoding: quoted-printable
From: rolfl@...
Message-Id: <20141023180723.85C01440E60@mail....>

Thu Oct 23 14:07:23 2014 -> INTERNET -> JUST GONE DOWN - Was UP for 65174 s=
econds
 


#1 楼

参数处理

$defaultmail$defaultip的命名不正确-命名不一致:


$host ||= $defaultip;
…
$mailto ||= $defaultmail;



我会还建议不要完全引入$defaultmail$defaultip作为变量。要么直接将它们写在

my $host   = (shift @ARGV) || '8.8.8.8';            # Google's nameserver
my $mailto = (shift @ARGV) || 'rolfl@example.com';  # RFC 2606


,要么定义一个常数:

use constant DEFAULTS => {
    host   => '8.8.8.8',              # Google's nameserver
    mailto => 'rolfl@example.com',
};

my $host   = (shift @ARGV) // DEFAULTS->{host};
my $mailto = (shift @ARGV) // DEFAULTS->{mailto};


这里,我使用Perl 5.10中引入的逻辑定义或运算符//对事物进行了现代化。这样就可以将像'0'之类的虚假字符串视为有效参数。

理想情况下,mail子例程不应是捕获$mailto值的闭包。我想这样的草率对于像这样的快速入侵是可以接受的。

邮件

我认为邮件是一种有趣的记录工具。如果您的Internet连接断开,则该消息只会在本地MTA的队列中停留一段时间。附加到文本文件将起作用,并且结果将更易于分析。

为了便于分析,我建议使用基于ISO 8601的时间戳格式。



%mail的值分配给From时有一个逗号。我对该脚本仍然有效感到惊讶。它只是表明“只有perl可以解析Perl”。

虽然可以定义您想要的名称以X-…开头的任何邮件头,但是X-Mailer头是事实上的标准已经适用。

我不会麻烦地将%mail定义为变量。只需直接调用sendmail,并使它们看起来像命名参数即可。

Ping

Net::Ping默认使用TCP回显协议,该协议很少受支持。 Google的8.8.8.8 DNS服务器肯定不支持它,因此您的默认设置不起作用。

您可以使用ICMP探针,但是在Unix上需要root特权。

对8.8.8.8:53的TCP探针(TCP上的DNS)似乎有效。期望您的ISP的名称服务器能够通过TCP响应DNS,这也是合理的。 (否则,您的Internet服务实际上将一直处于关闭状态。)

循环

我不是while (1) { sleep 1; … }循环的粉丝。我建议将其编写为do { … } while sleep 1;

两个分支中有一些重复的代码。我认为您已将条件写得不太理想。您真正想要的是对状态转换进行操作,所以要这样写。

建议的实现

#!/usr/bin/perl

use strict;
use warnings;
use autodie;

use Mail::Sendmail;
use Net::Ping;
use POSIX qw(strftime);

use constant DEFAULTS => {
    host   => '8.8.8.8',              # Google's nameserver
    mailto => 'rolfl@example.com',
};

my $host   = (shift @ARGV) // DEFAULTS->{host};
my $mailto = (shift @ARGV) // DEFAULTS->{mailto};

sub mail {
    my $subj = shift;
    my $msg = strftime('%FT%T', localtime) . " -> " . $subj;

    sendmail(smtp    => 'localhost:25',
             To      => $mailto,
             From    => $mailto,
             'X-Mailer' => "InternetUp q4312078q",
             Subject => $subj,
             Message => $msg) or die;
}


# $| = 1; # <-- The script prints nothing to STDOUT.  This is pointless.

my $ping = Net::Ping->new('tcp');
$ping->port_number(getservbyname('domain', 'tcp'));

my $up = 0; # start with it down, and it sends an 'up mail'.
my $start = time;

mail("Starting network monitor");

do {
    my $ping_ok = $ping->ping($host);
    if ($ping_ok != $up) {
        $up = $ping_ok;

        my $interval = time - $start;
        $start += $interval;
        mail(sprintf("INTERNET -> %s for %d seconds\n",
                     $up ? "JUST CAME BACK - Was DOWN"
                         : "JUST GONE DOWN - Was UP",
                     $interval));
    }
} while sleep 1;


评论


\ $ \ begingroup \ $
@ChrisWue POSIX :: strftime()的格式说明符与系统有关:“如果您希望代码具有可移植性,则您的format(fmt)参数应仅使用ANSI C标准定义的格式说明符。 ”您可以使用POSIX :: strftime :: GNU来避免依赖系统库支持。
\ $ \ endgroup \ $
– 200_success
14-10-27在21:30