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標準の機能ですが、手動で有効化する必要があります。
設定から「アプリと機能」を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/cbfa5c1f-9de9-7227-bd2f-33d35d975a66.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/312a3359-172c-08d2-bf22-3ff73e8f993d.png)

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/3a1cada4-dfb5-427b-bc07-bba00e1ed554.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/d1f9adad-6f0c-ff21-d9ab-81286bb299ec.png)

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

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

## Ubuntuの場合

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

```sh
# 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で、以下を実行します。

```sh
# apt-get install snmp snmp-mibs-downloader
```

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

　/etc/snmp/snmp.conf

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

```sh:/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

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

```sh
# 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」にあったわけです。

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

```sh
$ 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の項目は、以下を実行することで一覧取得できます。多すぎて面喰いますが。

```sh
$ snmptranslate -Tp
```

どのようなOIDがあるかどうかは、以下の方の記事が参考になります。
　[ネットワーク機器のSNMP MIB/OIDまとめ](https://qiita.com/Mabuchin/items/d435c0afb4f0ca17ad25)

# SNMPトラップの設定

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/944a57b2-faae-a88a-1390-0ec6744e6506.png)

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

# SNMPトラップの受信設定

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

```sh
# 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 foobar@example.org
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する権利がある必要があります。

```awscreds
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ケーブルを抜いてみましょう。

```sh
# /etc/init.d/snmptrapd restart
```

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/63f9ca3e-efcb-df47-c56c-b05efd48da95.png)

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

以上