経緯
snmpでロードアベレージなどの値が取得できない状態なのに、CactiではDownを検知しない、という状態が何回かありました。
前々から気になっていたのですが、せっかくなので今回調べてみました。
私の環境では、snmpでの疎通確認しか使用していない為、正しくない表現もあると思います。
その場合、ご指摘頂けましたら幸いです。
確認した環境
Cacti公式サイトからv0.8.8bをダウンロードして、ソースコードを調べてみます。
※ なお、以下にCactiのソースコードを記載していきますが、ライセンスはGNU General Public Licenseとなっています。
詳細
1. monitorプラグインから調べ始める
CactiでホストがDownすると、「Attention・・・」と音声を流してくれたりするプラグインです。
よく夜勤で眠くなると、眠気覚ましに流すあれですw
monitorプラグインのv1.3-1を確認。
monitor.php
を見ていきましたが、$host_down
がtrue / false で音を出す、出さないの内容でしたので、コードは割愛。
となると、Cacti本体でやはりDownやUpの判断をしているようなので、本体のコードを確認していきます。
2. host情報に何か手がかりはあるか?
Cacti本体だと、それなりの情報になるので、まずはホストに関する情報があるであろう、host.php
から確認しました。
down
で検索をかけると、monitorプラグインでも出てきた$host_down
と、その近くに$ping = new Net_Ping
という情報が!
if (($host["availability_method"] == AVAIL_PING) ||
($host["availability_method"] == AVAIL_SNMP_AND_PING) ||
($host["availability_method"] == AVAIL_SNMP_OR_PING)) {
/* create new ping socket for host pinging */
$ping = new Net_Ping;
$ping->host = $host;
$ping->port = $host["ping_port"];
/* perform the appropriate ping check of the host */
if ($ping->ping($host["availability_method"], $host["ping_method"],
$host["ping_timeout"], $host["ping_retries"])) {
$host_down = false;
$color = "#000000";
}else{
$host_down = true;
$color = "#ff0000";
}
3. Pingに手がかりがあるのか?
コマンドを発行する場所だと思われるlib
ディレクトリにping.php
がある。
function
の後にping_icmp
/ping_snmp
/ping_udp
/ping_tcp
と実行する箇所が!
恐らくこの部分が確認処理の箇所だと思うので、以下該当項目別で実際にソースコードを確認していきます。
-
ping_icmp
では、"min/avg/max"の結果が取れれば疎通可、取れなければTime out、それ以外のホストまで辿り着けなければ"Destination address not specified"と処理しているようです。
function ping_icmp() {
(中略)
if (strtolower(PHP_OS) != "winnt") {
$position = strpos($result, "min/avg/max");
if ($position > 0) {
$output = trim(str_replace(" ms", "", substr($result, $position)));
$pieces = explode("=", $output);
$results = explode("/", $pieces[1]);
$this->ping_status = $results[1];
$this->ping_response = "ICMP Ping Success (" . $results[1] . " ms)";
return true;
}else{
$this->status = "down";
$this->ping_response = "ICMP ping Timed out";
return false;
}
}else{
$position = strpos($result, "Minimum");
if ($position > 0) {
$output = trim(substr($result, $position));
$pieces = explode(",", $output);
$results = explode("=", $pieces[2]);
$this->ping_status = trim(str_replace("ms", "", $results[1]));
$this->ping_response = "ICMP Ping Success (" . $this->ping_status . " ms)";
return true;
}else{
$this->status = "down";
$this->ping_response = "ICMP ping Timed out";
return false;
}
}
}else{
$this->ping_status = "down";
$this->ping_response = "Destination address not specified";
return false;
-
ping_snmp
では、sysuptime
のOIDを用いて確認。
OIDで取得したsnmpの起動時刻が、get_time
で出した現在時刻との差分が+-10%以上であればレスポンスあり、それ以外の場合にはDownと判断しているようです。
function ping_snmp() {
/* initialize variables */
$this->snmp_status = "down";
$this->snmp_response = "Host did not respond to SNMP";
$output = "";
/* get start time */
$this->start_time();
/* by default, we look at sysUptime */
if ($this->avail_method == AVAIL_SNMP_GET_NEXT) {
if (version_compare("5", phpversion(), "<")) {
$oid = ".1.3";
}else{
$oid = ".1.3.6.1.2.1.1.3.0";
}
}else if ($this->avail_method == AVAIL_SNMP_GET_SYSDESC) {
$oid = ".1.3.6.1.2.1.1.1.0";
}else {
$oid = ".1.3.6.1.2.1.1.3.0";
}
(中略)
/* determine total time +- ~10% */
$this->time = $this->get_time($this->precision);
/* check result for uptime */
if (strlen($output)) {
/* calculte total time */
$this->snmp_status = $this->time*1000;
$this->snmp_response = "Host responded to SNMP";
return true;
}else{
$this->snmp_status = "down";
$this->snmp_response = "Host did not respond to SNMP";
return false;
}
} /* ping_snmp */
-
ping_udp
では、ソケット接続で通信を確認。
まずソケット通信ができているか否か。その上で、通信がtime outしないかどうかで判断しているようです。
function ping_udp() {
(中略)
/* get start time */
$this->start_time();
/* write to the socket */
socket_write($this->socket, $this->request, $this->request_len);
/* get the socket response */
switch(socket_select($r = array($this->socket), $w = NULL, $f = NULL, $to_sec, $to_usec)) {
case 2:
/* connection refused */
$error = "refused";
break;
case 1:
/* get the end time */
$this->time = $this->get_time($this->precision);
/* get packet response */
$code = @socket_recv($this->socket, $this->reply, 256, 0);
/* get the error, if applicable */
$err = socket_last_error($this->socket);
/* set the return message */
$this->ping_status = $this->time * 1000;
$this->ping_response = "UDP Ping Success (" . $this->time*1000 . " ms)";
$this->close_socket();
return true;
case 0:
/* timeout */
$error = "timeout";
break;
}
$retry_count++;
}
} else {
$this->ping_response = "Destination address not specified";
$this->ping_status = "down";
return false;
}
} /* end ping_udp */
-
ping_tcp
でもソケット接続で通信を確認。
まずソケット通信ができているか否か。その上で、通信がtime outしていないかどうかで判断しているようです。
function ping_tcp() {
(中略)
/* set start time */
$this->start_time();
/* allow immediate return */
socket_set_nonblock($this->socket);
@socket_connect($this->socket, $host_ip, $this->port);
socket_set_block($this->socket);
switch(socket_select($r = array($this->socket), $w = array($this->socket), $f = array($this->socket), $to_sec, $to_usec)){
case 2:
/* connection refused */
$this->time = $this->get_time($this->precision);
if (($this->time*1000) <= $this->timeout) {
$this->ping_response = "TCP Ping connection refused (" . $this->time*1000 . " ms)";
$this->ping_status = $this->time*1000;
}
$this->close_socket();
return true; /* "connection refused" says: host is alive (else ping would time out) */
case 1:
/* connected, so calculate the total time and return */
$this->time = $this->get_time($this->precision);
if (($this->time*1000) <= $this->timeout) {
$this->ping_response = "TCP Ping Success (" . $this->time*1000 . " ms)";
$this->ping_status = $this->time*1000;
}
$this->close_socket();
return true;
case 0:
/* timeout */
$this->ping_response = "TCP ping timed out";
$this->ping_status = "down";
$this->close_socket();
return false;
}
}
} else {
$this->ping_response = "Destination address not specified";
$this->ping_status = "down";
return false;
}
} /* end ping_tcp */
以上、簡単ですが、Cactiの疎通確認方法を見ていきましたが、今回確認できたことで、個人的にとても勉強になりました。