SNMPトラップによる LANのLink Up/Down検知 をCloudWatchに通知する


高機能なルータには、LANケーブルが挿されたり抜かれたりすると、Link Up/Downイベントが通知されるSNMPの機能がついているものがあります。
これがあれば、誰かが勝手にLANにつなごうとしているのを検出できたり、何かのはずみでLANケーブルが抜けてしまったことを検知できることになります。
さらに、この検知をCloudWatchに通知すれば、集中管理できそうです。

とは言いながら、そもそもCloudWatchに通知するためのLANケーブルが抜けてしまっては、CloudWatchに通知できないので、参考情報の一つととらえてください。

一方、このSNMPと同様の機能が、Windowsにもありますので、今回はWindowsをルータとみなして、SNMPのLink Up/Down検知およびCloudWatchへの通知をしてみたいとおもいます。

ネットワーク環境

こんな感じのネットワークを想定しています。

 別のネットワーク
  |
 Windows10(監視対象のルータ想定)
  |
 ルータ - SNMP監視サーバ
  |
 CloudWatch

Windows10を監視対象のルータとして見立てています。Windows10には2つのネットワークインタフェースがあり、片方のネットワークケーブルが断線した場合を考えます。

SNMPエージェントをWindows10で動作させ、常時監視をします。
SNMPエージェントがネットワークインタフェースのダウンを検知すると、SNMP監視サーバに通知します。
SNMP監視サーバは、通知を検知すると、通知内容を判別し、CloudWatchに通知します。

SNMPエージェントが「snmpd」、SNMP監視サーバが「snmptrapd」です。また、「snmp」の各種コマンドを使えば、ローカルまたはリモートマシンのSNMPの情報を取得することができます。

SNMPエージェントのインストール

Windows10とUbuntuそれぞれでのインストール方法を示しておきます。

Windows10の場合

OS標準の機能ですが、手動で有効化する必要があります。
設定から「アプリと機能」を開きます。

「オプション機能の管理」を選択します。

「機能の追加」を押下し、リストの中から「簡易ネットワーク管理プロトコル(SNMP)」を選択します。

そうすると、コンピュータの管理の「サービス」に「SNMPサービス」が現れます。おそらく、状態は実行中になっているかと思います。

次に、SNMPサービスをダブルクリックしてプロパティを表示します。
「セキュリティ」タブを選択します。

受け付けるコミュニティ名として、コミュニティ「public」、権利「読み取りのみ」を追加します。
また、SNMPパケットを受け付けるホスト名またはIPアドレスを指定します。ここでは単純に、「すべてのホストからSNMPパケットを受け付ける」を選択しています。

これで、SNMP監視サーバから、SNMPの各種情報を取得できるようになりました。

Ubuntuの場合

Ubuntuの場合のインストールは、以下を実行します。

# apt-get install snmpd

以下のファイルを修正する必要があります。

 /etc/snmp/snmpd.conf

/etc/snmp/snmpd.conf
・・・

#  Listen for connections from the local system only
#agentAddress  udp:127.0.0.1:161
#  Listen for connections on all interfaces (both IPv4 *and* IPv6)
agentAddress udp:161,udp6:[::1]:161

・・・

# rocommunity public  default    -V systemonly
rocommunity public default

・・・

これで再起動すれば、他のPCからSNMP情報を取得できるようになります。

# /etc/init.d/snmpd restart

SNMPの情報の取得

Ubuntuで、以下を実行します。

# apt-get install snmp snmp-mibs-downloader

これで、SNMPの情報取得のための各種コマンドを実行できるようになりました。
その前に、以下のファイルで、修正が必要です。

 /etc/snmp/snmp.conf

mibs:をコメントアウトし、mibs allを追記します。

/etc/snmp/snmp.conf
# As the snmp packages come without MIB files due to license reasons, loading
# of MIBs is disabled by default. If you added the MIBs you can reenable
# loading them by commenting out the following line.
#mibs :
mibs all

これで準備ができたはずなのですが、環境によって以下のようなエラーが出る場合があります。

Bad operator (INTEGER): At line 73 in /usr/share/mibs/ietf/SNMPv2-PDU

その場合は、以下を実行します。

# wget http://www.iana.org/assignments/ianaippmmetricsregistry-mib/ianaippmmetricsregistry-mib -O /usr/share/snmp/mibs/iana/IANA-IPPM-METRICS-REGISTRY-MIB
# wget http://pastebin.com/raw.php?i=p3QyuXzZ -O /usr/share/snmp/mibs/ietf/SNMPv2-PDU
# wget http://pastebin.com/raw.php?i=gG7j8nyk -O /usr/share/snmp/mibs/ietf/IPATM-IPMC-MIB

(参考)
https://docs.linuxconsulting.mn.it/notes/net-snmp-errors

それでは、取得してみましょう。

ローカルホストの場合

$ snmpwalk -v1 localhost -c public

リモートホストの場合

$ snmpwalk -v1 【SNMPエージェントのホスト名】 -c public

すごくたくさんの情報が取得されたかと思います。以下のように指定すると、特定の情報のみ取得します。

$ snmpwalk -v1 localhost -c public .1.3.6.1.2.1.2.2.1.2

$ snmpwalk -v1 localhost -c public RFC1213-MIB:ifDescr

実は、上の2つは同じ情報を指しています。
 .1.3.6.1.2.1.2.2.1.2

 RFC1213-MIB:ifDescr
は同じです。そのOIDと名前のマッピング情報が「snmp-mibs-downloader」にあったわけです。

マッピング情報は、以下の方法で、相互に調べることができます。

$ snmptranslate .1.3.6.1.2.1.2.2.1.10
RFC1213-MIB::ifInOctets

$ snmptranslate -On RFC1213-MIB::ifInOctets
.1.3.6.1.2.1.2.2.1.10

ちなみに、SNMPの項目は、以下を実行することで一覧取得できます。多すぎて面喰いますが。

$ snmptranslate -Tp

どのようなOIDがあるかどうかは、以下の方の記事が参考になります。
 ネットワーク機器のSNMP MIB/OIDまとめ

SNMPトラップの設定

LANのLink Up/Down を通知するには、トラップ機能を使います。
通常、監視しているサーバから、snmpwalkやsnmpgetコマンドを使って、SNMPエージェントに対して情報取得を要求するのですが、トラップは、SNMPエージェント側から通知をするための機能です。

Windowsでは、SNMPサービスのプロパティから設定します。

通知する際のコミュニティ名を指定し、トラップ通知先をIPアドレスまたはホスト名を指定しておきます。

SNMPトラップの受信設定

SNMPトラップの受信側では、以下をインストールし、デーモンとして起動しておく必要があります。

# apt-get install snmptrapd

次に、以下のファイルを修正します。

 /etc/snmp/snmptrapd.conf

以下の部分のpublicの行のコメントを外します。Windows側から通知する際のコミュニティ名と合わせてください。

/etc/snmp/snmptrapd.conf
#authCommunity log,execute,net private 
authCommunity log,execute,net public

変更が完了したら、snmptrapd を再起動します。

# /etc/init.d/snmptrapd restart

準備ができました。
WindowsのLANケーブルを抜いてみてください。

ちょっとすると、/var/log/syslog に以下のようなログが記録されているかと思います。

Oct  6 01:47:34 【SNMP監視サーバのホスト名】 snmptrapd[1072]: 2019-10-06 01:47:34 【SNMPエージェントのホスト名】 [【SNMPエージェントのIPアドレス】] (via UDP: [【SNMPエージェントのIPアドレス】]:59663->[【SNMP監視サーバのIPアドレス】]:162) TRAP, SNMP v1, community public#012#011RFC1155-SMI::enterprises.311.1.1.3.1.1 Link Down Trap (0) Uptime: 23:18:52.53#012#011RFC1213-MIB::ifIndex.32 = INTEGER: 32

SNMPトラップをCloudWatchにアップする

snmptrapdには、SNMPトラップを受信したとき、指定されたプログラムを起動して、受信したSNMPトラップ情報を渡してくれる機能があります。
下記のような感じにしています。(traphandleのところ)

/etc/snmp/snmptrapd.conf

/etc/snmp/snmptrapd.conf
## send mail when get any events
#traphandle default /usr/bin/traptoemail -s smtp.example.org [email protected]
traphandle .1.3.6.1.6.3.1.1.5.* /home/XXXXXXXXXX/link_trap.sh

「.1.3.6.1.6.3.1.1.5.*」 の部分で、SNMPトラップで転送したいOIDを指定しています。

あとは、指定したプログラム(今回はシェルスクリプト)を実装し、その中でCloudWatchに上げればよいわけです。

実は、指定したプログラム(シェルスクリプト)には、以下のような情報が標準入力として渡されます。

【SNMPエージェントのホスト名】
UDP: [【SNMPエージェントのIPアドレス】]:59663->[【SNMP監視サーバのIPアドレス】]:162
DISMAN-EXPRESSION-MIB::sysUpTimeInstance 0:17:30:04.71
SNMPv2-MIB::snmpTrapOID.0 IF-MIB::linkDown
RFC1213-MIB::ifIndex.35 35
SNMP-COMMUNITY-MIB::snmpTrapAddress.0 【SNMPエージェントのIPアドレス】
SNMP-COMMUNITY-MIB::snmpTrapCommunity.0 "public"
SNMPv2-MIB::snmpTrapEnterprise.0 RFC1155-SMI::enterprises.311.1.1.3.1.1

これがlinkDownのときの通知内容です。
このうち欲しかったのは以下の情報です。

【SNMPエージェントのホスト名】
SNMPv2-MIB::snmpTrapOID.0 IF-MIB::linkDown
RFC1213-MIB::ifIndex.35 35

先頭行には、送信元ホスト名があります。
linkDown通知であることがわかります。
35とは、linkDownの事象となったLANポートの番号です。Windowsでは、vEthernetやらBluetoothネットワークなど、たくさんの仮想的なネットワークインタフェースが動いており、それぞれに重ならないように番号がついています。
ルータの場合は、純粋にLAN端子の番号になると思います。

snmptrapd.confで指定したスクリプトは以下となります。
標準入力された情報から必要な情報を抽出し、CloudWatchに通知しています。(bashシェルスクリプトは初心者なのでもっと効率的に書けるのでは。。。)

/home/XXXXXXXXXX/link_trap.sh
#!/bin/bash

export AWS_CREDENTIAL_FILE=/home/XXXXXXXX/awscreds

while read line
do
    if [ -z "$host" ]; then
        host=${line}
    else
        set ${line}
        if [[ ${1} =~ "RFC1213-MIB::ifIndex." ]]; then
            ifIndex=${2}
        fi

        if [ ${1} = "SNMPv2-MIB::snmpTrapOID.0" ]; then
            if [ ${2} = 'IF-MIB::linkUp' ]; then
                link=1
            elif [ ${2} = 'IF-MIB::linkDown' ]; then
                link=0
            fi
        fi
    fi
done

if [ "$link" -a "$ifIndex" ]; then
    echo aws cloudwatch put-metric-data --namespace SNMP_IFLink --metric-name IFLink --dimensions "host=$host,ifNumber=$ifIndex" --value $link
    aws cloudwatch put-metric-data --namespace SNMP_IFLink --metric-name IFLink --dimensions "host=$host,ifNumber=$ifIndex" --value $link
fi

AWS_CREDENTIAL_FILEに指定するファイルには以下の形式で記載しておきます。当然ながら、そのIAMユーザにはCloudWatchへput-metric-dataする権利がある必要があります。

AWSAccessKeyId=YYYYYYYYYYYYYYYYYYYY
AWSSecretKey=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

aws cloudwatch *** の部分が、CloudWatchに通知している個所になります。
コマンドラインからAWSを操作するためのaws-cliを使っています。

(参考)
 https://docs.aws.amazon.com/cli/latest/reference/cloudwatch/put-metric-data.html

SNMPトラップ機能をリスタートし、LANケーブルを抜いてみましょう。

# /etc/init.d/snmptrapd restart

ちょっとわかりにくいかもしれませんが、CloudWatchに情報がアップされているのがわかります。
メトリクスネームスペースはSNMP_IFLinkとしています。

メトリクスの値は、Link Up/Down の情報です。Upの場合は1、Downの場合は0としています。また、どのルータのどのLAN端子番号で発生したのかがわかるように、ディメンジョンとしてhostとifNumberが渡っているのがわかります。

以上