cacti
snmp

CactiのDownは何をもって検知しているのか調べてみた

More than 3 years have passed since last update.

経緯

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という情報が!

host.php
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"と処理しているようです。
ping.php
    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と判断しているようです。
ping.php
    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しないかどうかで判断しているようです。
ping.php
    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していないかどうかで判断しているようです。
ping.php
    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の疎通確認方法を見ていきましたが、今回確認できたことで、個人的にとても勉強になりました。