概要

  • ネットワーク上のサーバの ping による死活監視および WakeOnLan によるリモート起動を行う。
  • 監視するのはスクリプトを呼び出した時1回だけで履歴は取らない。
  • 再度監視したい場合はタイトルがリンクになっているので、そこをクリックする。
  • Windows/IIS 版を追加。(2012/03/28)
  • CPAN:Doxygen-Filter-Perl 用タグの書き方が間違っていたのを訂正。(2012/03/28)
  • Linux/Apache 版で<form>が閉じていなかったのを修正。(2012/03/28)
  • 関数内で print するのを止め、HTML文字列を返すように変更。(2012/03/29)
  • NIC が2枚以上ある場合に対応。「/」で連結して書く。(2012/05/30)
  • Name の先頭に「#」が付いている行はコメントとして無視される。(2012/06/04)

ソース

index.cgi (Linux/Apache)

すべてを展開すべてを収束
  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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
-
|
!
 
 
 
 
 
 
 
 
 
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
-
|
!
|
|
|
|
|
|
|
!
|
|
|
|
|
|
!
 
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
-
|
!
 
 
 
 
-
|
|
-
|
|
|
|
!
|
|
-
-
|
!
!
!
 
 
 
 
-
|
|
|
|
|
|
|
-
|
-
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
!
|
|
!
 
 
#!/usr/bin/perl
##	@mainpage	NetWatcher
# サーバの監視およびリモート起動
 
use strict;
use warnings;
use utf8;
use Encode;
use YAML::Syck;
use CGI::Pretty qw( -no_xhtml *table );    # //HTML 4.01 Transitional//EN
use Text::xSV::Slurp qw( xsv_slurp );
use Net::Ping::External qw( ping );
use Net::Wake;
use Parallel::ForkManager;
use IPC::Shareable;
 
$YAML::Syck::ImplicitUnicode = 1;
 
#my $charsetConsole	= 'CP932';
my $charsetConsole    = 'UTF-8';
my $charsetFile        = 'UTF-8';
 
binmode( STDIN,  ":encoding($charsetConsole)" );
binmode( STDOUT, ":encoding($charsetConsole)" );
binmode( STDERR, ":encoding($charsetConsole)" );
 
my $cginame        = 'NetWatcher';            ##< CGI名
my $cookiename    = 'NWCookie';            ##< 現在は使用していない
my $configfile    = './conf/config.txt';    ##< 設定ファイル
my $csvoption    = { sep_char => "\t" };    ##< 設定ファイルを読み込むためのオプション
my $maxchildren    = 10;                    ##< 子プロセスの最大数
my $markignore    = '#';                    ##< 行頭がこの文字列で始まっている行は無視する。
 
my @Targets = readXSV( $configfile, $csvoption );
my %Targets = map{ $_->{'Name'} => $_; } @Targets;
 
#print Dump( \%Targets ) . "\n";
#exit;
 
my %results = pingTargets( \@Targets );
 
my $q = new CGI;
$q->charset( $charsetFile );
my $scripturl = $q->url(  );
 
my @paramnames = $q->param();
 
printHeader();
 
if ( @paramnames ){
    action();
}
printForm();
 
printFooter();
 
exit;
 
##	@function readXSV( $fname, %$opt )
#	CSV(TSV)ファイルを読み込んでヘッダ行をキーとしたハッシュの配列を返す。
#	@param	fname	[in] ファイル名
#	@param	opt		[in] Text::CSV_XS に渡されるオプション
#	@return	CSVを配列化したもの
sub readXSV
{
    my( $fname, $opt ) = @_;
    $opt = { binary => 1, %{$opt} };
    open( my $fhin, "<:encoding($charsetFile)", encode( $charsetConsole, $fname ) )
        or die( "$fname: $!" );
    my @body = <$fhin>;
    close( $fhin );
    my $ret = xsv_slurp( 
        string => join( "", @body ), 
        text_csv => $opt, 
    );
    return ( ref( $ret ) eq 'ARRAY' ) 
        ? @{ $ret } 
        : $ret ;
}
 
##	@function pingTargets( @$targets_ref )
#	ターゲット情報に従い、各ターゲット宛に ping を打ち、結果をハッシュにして返す。
#	@param	targets_ref	[in] ターゲット情報の配列のリファレンス
#	@return	ping 結果のハッシュ
sub pingTargets
{
    my( $targets_ref ) = @_;
 
    my $handle = tie my %results, 'IPC::Shareable', undef, { destroy => 1 };
    %results = ();
 
    my $pm = Parallel::ForkManager->new( $maxchildren );
 
#	$pm->run_on_start(
#		sub {
#			my( $pid, $ident ) = @_;
#			print "** $ident started, pid: $pid\n";
#		}
#	);
 
    foreach my $target ( @{$targets_ref} ){
        my $name    = $target->{ 'Name' } || '';
        if ( substr( $name, 0, length($markignore) ) eq $markignore ){
            next;
        }
        my $ip        = $target->{ 'IP' } || '';
        $pm->start( $name ) and next;
        my $status    = ping( host => $ip, timeout => 1 );
        $handle->shlock;
        $results{ $name } = $status;
        $handle->shunlock;
        $pm->finish( $name );
    }
    $pm->wait_all_children;
 
    my %ret = %results;
    $handle->remove();
 
    return %ret;
}
 
##	@function printHeader()
#	HTTP ヘッダおよび HTML ヘッダを出力する。
sub printHeader
{
    my $cookieval = decode( 'utf8', 
        $q->cookie( encode( 'utf8', $cookiename ) ) || '' 
    );
#	$cookieval .= 'あ';
    my $cookie = $q->cookie( 
        '-name'        => encode( 'utf8', $cookiename ), 
        '-value'    => encode( 'utf8', $cookieval ),
    );
    print $q->header( '-cookie' => [ $cookie ] );
    print $q->start_html( 
        '-title'    => $cginame, 
        '-lang'        => 'ja-JP', 
        '-head'        => [ 
            $q->meta( { '-http_equiv'    => 'Content-style-type',    '-content'    => 'text/css' } ),
            $q->meta( { '-http_equiv'    => 'Content-script-type',    '-content'    => 'text/javascript' } ), 
        ], 
        '-style'    => [ { 'src' => 'NetWatcher.css' }, ], 
    );
    print $q->h1( $q->a( { -href => $scripturl }, $cginame ) );
}
 
##	@function printFooter()
#	HTML フッタを出力する。
sub printFooter
{
    print $q->end_html . "\n";
}
 
##	@function action()
#	フォームから送信されたコマンドを処理する。
sub action
{
    print $q->h2( 'Command' );
    print $q->start_table( { '-summary' => 'Servers', '-border' => 1 } );
    foreach my $key ( @paramnames ){
        print $q->Tr( 
            { -class => 'even' }, 
            $q->th( { -class => 'title' }, $key ), $q->td( $q->param( $key ) ) 
        );
    }
    print $q->end_table();
    my $target = $q->param( 'Wake' ) || '';
    if ( $target && $Targets{$target} ){
        foreach my $mac ( split( /\//, $Targets{$target}{'MAC'} ) ){
            Net::Wake::by_udp( undef, $mac );
        }
    }
}
 
##	@function printForm()
#	フォームを出力する。
sub printForm
{
    print $q->h2( 'ステータス' );
    print $q->start_form( 
        '-action' => $scripturl, 
        '-enctype' => ( 'multipart/form-data' ), 
    );
    print $q->start_table( { '-summary' => 'Statuses', '-border' => 1 } );
    print $q->Tr( $q->th( { -class => 'title' }, [ 'Name', 'Type', 'IP', 'Status', 'Wake', '備考' ] ) );
    for( my $i=0; $i<@Targets; ++$i ){
        my $name    = $Targets[ $i ]{'Name'} || '';
        if ( substr( $name, 0, length($markignore) ) eq $markignore ){
            next;
        }
        my $type    = $Targets[ $i ]{'Type'} || '';
        my $ip        = $Targets[ $i ]{'IP'} || '';
        my $status    = ( $results{ $name } ) 
            ? $q->td( { -class => 'state_up' }, 'Up' ) 
            : $q->td( { -class => 'state_down' }, 'Down' );
        my $comment    = $Targets[ $i ]{'Comment'} || '';
        print $q->Tr( 
            { -class => ( $i % 2 ) ? 'odd' : 'even' }, 
            $q->th( $name ), 
            $q->td( [ $type, $ip ] ), 
            $status, 
            $q->td( [ $q->submit( -name=>'Wake', -value=>$name ), $comment, ] ), 
        );
    }
    print $q->end_table();
    $q->end_form;
}
 
# EOF

index_IIS.cgi (Windows/IIS)

すべてを展開すべてを収束
  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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
-
|
!
 
 
 
 
 
 
 
 
 
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
 
 
-
|
|
|
|
-
|
-
|
!
|
|
-
|
|
!
|
|
!
-
|
!
|
|
!
 
 
 
 
-
-
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
-
|
!
 
 
 
 
-
|
|
-
|
|
|
|
!
|
|
-
-
|
!
!
!
 
 
 
 
-
|
|
|
|
|
|
|
-
|
-
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
!
|
|
!
 
 
#!/usr/bin/perl
# NetWatcher (IIS用)
# サーバの監視およびリモート起動
 
use strict;
use warnings;
use utf8;
use Encode;
use YAML::Syck;
use CGI::Pretty qw( -no_xhtml *table );    # //HTML 4.01 Transitional//EN
use Text::xSV::Slurp qw( xsv_slurp );
use Net::Ping::External qw( ping );
use Net::Wake;
use threads;
use threads::shared;
 
$YAML::Syck::ImplicitUnicode = 1;
 
#my $charsetConsole	= 'CP932';
my $charsetConsole    = 'UTF-8';
my $charsetFile        = 'UTF-8';
 
# Encode モジュールは Thread Safe ではない。
# http://search.cpan.org/dist/Encode/encoding.pm
#binmode( STDIN,  ":encoding($charsetConsole)" );
#binmode( STDOUT, ":encoding($charsetConsole)" );
#binmode( STDERR, ":encoding($charsetConsole)" );
 
my $encoder = find_encoding( $charsetConsole );
 
my $cginame        = 'NetWatcher';
my $cookiename    = 'NWCookie';            ##< 現在は使用していない
my $configfile    = './conf/config.txt';
my $csvoption    = { sep_char => "\t" };
my $markignore    = '#';                    ##< 行頭がこの文字列で始まっている行は無視する。
 
my @Targets = readXSV( $configfile, $csvoption );
my %Targets = map{ $_->{'Name'} => $_; } @Targets;
 
#print $encoder->encode( Dump( \%Targets ) . "\n" );
#exit;
 
my %results = pingTargets( \@Targets );
 
my $q = new CGI;
$q->charset( $charsetConsole );
my $scripturl = $q->url( -path_info=>1 );
 
my @paramnames = $q->param();
 
printHeader();
 
if ( @paramnames ){
    action();
}
printForm();
 
printFooter();
 
exit;
 
##	@function readXSV( $fname, %$opt )
#	CSV(TSV)ファイルを読み込んでヘッダ行をキーとしたハッシュの配列を返す。
#	@param	fname	[in] ファイル名
#	@param	opt		[in] Text::CSV_XS に渡されるオプション
#	@return	CSVを配列化したもの
sub readXSV
{
    my( $fname, $opt ) = @_;
    $opt = { binary => 1, %{$opt} };
    open( my $fhin, "<:encoding($charsetFile)", encode( $charsetConsole, $fname ) )
        or die( "$fname: $!" );
    my @body = <$fhin>;
    close( $fhin );
    my $ret = xsv_slurp( 
        string => join( "", @body ), 
        text_csv => $opt, 
    );
    return ( ref( $ret ) eq 'ARRAY' ) 
        ? @{ $ret } 
        : $ret ;
}
 
##	@function pingTargets( @%targetsref )
#	ターゲット情報に従い、各ターゲット宛に ping を打ち、結果をハッシュにして返す。
#	@param	targets	[in] ターゲット情報の配列
#	@return	ping 結果のハッシュ
sub pingTargets
{
    my( $targets_ref ) = @_;
 
    my %results;
    my %threads;
    foreach my $target ( @{$targets_ref} ) {
        my $name    = $target->{ 'Name' } || '';
        if ( substr( $name, 0, length($markignore) ) eq $markignore ){
            next;
        }
        my $ip        = $target->{ 'IP' } || '';
        $threads{ $name } = threads->new( 
            sub {
                my( $ip ) = @_;
                return ping( host => $ip, timeout => 1 );
            }, 
            $ip 
        );
    }
    foreach my $t ( keys( %threads ) ){
        $results{ $t } = $threads{ $t }->join();
    }
 
    return %results;
}
 
##	@function printHeader()
#	HTTP ヘッダおよび HTML ヘッダを出力する。
sub printHeader
{
    if ( defined( $ENV{PERLXS} ) && $ENV{PERLXS} eq 'PerlIS' ){
        print $encoder->encode( "HTTP/1.0 200 OK\n" );
    }
 
    my $cookieval = decode( 'utf8', 
        $q->cookie( encode( 'utf8', $cookiename ) ) || '' 
    );
#	$cookieval .= 'あ';
    my $cookie = $q->cookie( 
        '-name'        => encode( 'utf8', $cookiename ), 
        '-value'    => encode( 'utf8', $cookieval ),
    );
    print $encoder->encode( $q->header( '-cookie' => [ $cookie ] ) );
    print $encoder->encode( $q->start_html( 
        '-title'    => $cginame, 
        '-lang'        => 'ja-JP', 
        '-head'        => [ 
            $q->meta( { '-http_equiv'    => 'Content-style-type',    '-content'    => 'text/css' } ),
            $q->meta( { '-http_equiv'    => 'Content-script-type',    '-content'    => 'text/javascript' } ), 
        ], 
        '-style'    => [ { 'src' => 'NetWatcher.css' }, ], 
    ) );
    print $encoder->encode( $q->h1( $q->a( { -href => $scripturl }, $cginame ) ) );
}
 
##	@function printFooter()
#	HTML フッタを出力する。
sub printFooter
{
    print $encoder->encode( $q->end_html . "\n" );
}
 
##	@function action()
#	フォームから送信されたコマンドを処理する。
sub action
{
    print $encoder->encode( $q->h2( 'Command' ) );
    print $encoder->encode( $q->start_table( { '-summary' => 'Servers', '-border' => 1 } ) );
    foreach my $key ( @paramnames ){
        print $encoder->encode( $q->Tr( 
            { -class => 'even' }, 
            $q->th( { -class => 'title' }, $key ), $q->td( $q->param( $key ) ) 
        ) );
    }
    print $encoder->encode( $q->end_table() );
    my $target = $q->param( 'Wake' ) || '';
    if ( $target && $Targets{$target} ){
        foreach my $mac ( split( /\//, $Targets{$target}{'MAC'} ) ){
            Net::Wake::by_udp( undef, $mac );
        }
    }
}
 
##	@function printForm()
#	フォームを出力する。
sub printForm
{
    print $encoder->encode( $q->h2( 'ステータス' ) );
    print $encoder->encode( $q->start_form( 
        '-action' => $scripturl, 
        '-enctype' => ( 'multipart/form-data' ), 
    ) );
    print $encoder->encode( $q->start_table( { '-summary' => 'Statuses', '-border' => 1 } ) );
    print $encoder->encode( $q->Tr( $q->th( { -class => 'title' }, [ 'Name', 'Type', 'IP', 'Status', 'Wake', '備考' ] ) ) );
    for( my $i=0; $i<@Targets; ++$i ){
        my $name    = $Targets[ $i ]{'Name'} || '';
        if ( substr( $name, 0, length($markignore) ) eq $markignore ){
            next;
        }
        my $type    = $Targets[ $i ]{'Type'} || '';
        my $ip        = $Targets[ $i ]{'IP'} || '';
        my $status    = ( $results{ $name } ) 
            ? $q->td( { -class => 'state_up' }, 'Up' ) 
            : $q->td( { -class => 'state_down' }, 'Down' );
        my $comment    = $Targets[ $i ]{'Comment'} || '';
        print $encoder->encode( $q->Tr( 
            { -class => ( $i % 2 ) ? 'odd' : 'even' }, 
            $q->th( $name ), 
            $q->td( [ $type, $ip ] ), 
            $status, 
            $q->td( [ $q->submit( -name=>'Wake', -value=>$name ), $comment, ] ), 
        ) );
    }
    print $encoder->encode( $q->end_table() );
    print $encoder->encode( $q->end_form() );
}
 
# EOF

config.txt

  • サーバ情報設定TSVファイル。
  • MAC は WakeOnLan のパケットを送信する NIC の MAC アドレス。
  • NIC が2枚以上ある場合は、「/」で連結して書く。
  • Name の先頭に「#」が付いている行はコメントとして無視される。
    "Name"	"Type"	"IP"	"MAC"	"Comment"
    "Srv01"	"Win2003"	"192.168.0.10"	"XX:XX:XX:XX:XX:XX"	"Web Hosting"
    "Srv02"	"CentOS6"	"192.168.0.11"	"XX:XX:XX:XX:XX:XX/XX:XX:XX:XX:XX:XX"	"Mail, MySQL"
    "#Srv03"	"Win2008R2"	"192.168.0.12"	"XX:XX:XX:XX:XX:XX"	"Streaming"
    "Srv04"	"MacOSX 10.6"	"192.168.0.13"	"XX:XX:XX:XX:XX:XX"	"FileSrv"

NetWatcher.conf

  • Apache 用設定ファイル。
  • .htaccess でも代用可能。
    すべてを展開すべてを収束
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
    
     
     
     
     
     
     
     
     
     
     
     
     
     
    
    <Directory "/var/www/html/NetWatcher">
        Options ExecCGI Indexes
        DirectoryIndex index.cgi
        Order allow,deny
        Allow from 127.0.0.1
        Allow from 192.168.0.0/24
        Allow from 10.8.0.0/24
    </Directory>
     
    <Directory "/var/www/html/NetWatcher/conf">
        Options None
        Order allow,deny
    </Directory>

システム設定

パッケージ追加

  • EPEL, RPMForge リポジトリを追加しておくこと。
    リポジトリ追加
    # yum install perl-Net-Ping-External perl-Parallel-ForkManager perl-IPC-Shareable

SELinux設定

  • audit.log を元にポリシーを作成しインストールする。
    ログ分析
    # semodule -d mypol
    # setenforce 0
    # service auditd rotate
    (NetWatcher実行)
    # setenforce 1
    # grep "index.cgi\|ping" /var/log/audit/audit.log | audit2allow -M mypol_NetWatcher
    # semodule -i mypol_NetWatcher.pp
  • mypol_NetWatcher.te
    こんなポリシーができてるはず。
    module mypol_NetWatcher 1.0;
    
    require {
            type httpd_sys_script_t;
            type tmpfs_t;
            class capability { setuid net_raw };
            class sem { unix_read write unix_write read destroy create };
            class shm { write associate read create unix_read getattr unix_write  destroy };
            class file { read write };
            class rawip_socket { write getopt create read setopt };
    }
    
    #============= httpd_sys_script_t ==============
    allow httpd_sys_script_t self:capability { setuid net_raw };
    allow httpd_sys_script_t self:rawip_socket { write getopt create read setopt };
    allow httpd_sys_script_t self:sem { unix_read write unix_write read destroy create };
    allow httpd_sys_script_t self:shm { unix_read associate read create write getattr unix_write destroy };
    allow httpd_sys_script_t tmpfs_t:file { read write };
  • gWakeOnLan
    gWakeOnLan is a GTK+ utility to awake turned off machines using the Wake on LAN feature.

添付ファイル: fileNetWatcher.zip 303件 [詳細]

リロード   新規 下位ページ作成 編集 凍結 差分 添付 コピー 名前変更   ホーム 一覧 検索 最終更新 バックアップ リンク元   ヘルプ   最終更新のRSS
Last-modified: Tue, 21 Feb 2017 11:44:49 JST (277d)