SMS で LTE 付き Cisco ルータを操作する

# これは何？
[Cisco 1100 LTE のSMS と IOS-XE EEMで遊ぶ](https://qiita.com/kikuta1978/items/4b1a098b1be9797ea010)の続編です。前回はSIMのセットアップと、EEMと組み合わせた簡単な動作確認を行い、以下のようにまとめました。

> 今回は、SMSのメッセージに応じて動作を振り分けるところまでやってませんが、「SyslogでSMS受信」-> 「SMSの内容を確認」-> 「EEMのアクションを分岐」といった流れも、比較的簡単に実装できそうです。ルータに送るSMSのメッセージに応じて、ルータ内部の動作をカスタムで実行したり、ルータに調査して応答させるコマンドをSMSで分けたり、トラブルシュートのパターンを操作したり、といったわりと実用的な使い方も考えられそうです。時間ができたら、やってみたいと思います！

今回は、EEMとTclスクリプトを使って、色々動作させてみました。
*クリックするとYoutubeが開きます
[![Ask Alexa to open & close my switch port](http://img.youtube.com/vi/7uyNXAcyM0M/0.jpg)](https://www.youtube.com/watch?v=7uyNXAcyM0M)

※iPhoneのSMSからルータと通信しているスクリーンショット
<img width="400" alt="IMG_4149.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/157325/30419ed5-a2e5-9f0c-46d3-4a0de73b7580.png">

EEMについては、以下の辺りを参照ください。

- [Cisco IOS-XEでルーティングテーブルの変化を捕捉して外部サービスに投稿](https://qiita.com/kikuta1978/items/42526882b57eafe480ad)
- [IOS/IOS-XE 運用管理機能アップデート P23, 24あたり](https://www.slideshare.net/CiscoJapan/iosiosxe)

マニュアルは、以下が包括的で個人的にお勧めです。

- [Embedded Event Manager Configuration Guide, Cisco IOS Release 15M&T](https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/eem/configuration/15-mt/eem-15-mt-book.html)

# 環境と準備

[Cisco 1100 LTE のSMS と IOS-XE EEMで遊ぶ](https://qiita.com/kikuta1978/items/4b1a098b1be9797ea010)と同じなので、省略。

## ルータで動作させるTclスクリプト

Cisco IOSルータでTclスクリプトを動作させる方法は、以下を参照ください。

- [Cisco IOS EEMを使ったTclスクリプトの実行](https://qiita.com/kikuta1978/items/5d7d92e7f35d0a03922b)

以下のスクリプトを、LTE付き [Cisco 1100 ルータ](https://www.cisco.com/c/ja_jp/products/collateral/routers/1000-series-integrated-services-routers-isr/datasheet-c78-739512.html)で動作させています。同じことは、Cisco 800とかでもできると思います。雑なコードでアレですが、エッセンス＆スニペット用ということで..。

```tcl:sms.tcl
# ---------------------------------------------------------------------------
# 0. EEM Script
# ---------------------------------------------------------------------------
::cisco::eem::event_register_syslog pattern "New SMS received" 

namespace import ::cisco::eem::*
namespace import ::cisco::lib::*

# ---------------------------------------------------------------------------
# 1. Procedures
# ---------------------------------------------------------------------------
proc CLIProc {clifd cmds} {
    global errorInfo cmd_output
    foreach a_cmd $cmds {
        if [catch {cli_exec $clifd $a_cmd} result] {
            error $result $errorInfo
        } else {
            set cmd_output $result
        }
    }
}
# ---------------------------------------------------------------------------
# 2. Body
# ---------------------------------------------------------------------------

if [catch {cli_open} result] {
    error $result $errorInfo
} else {
    array set cli1 $result
}

# ---------------------------------------------------------------------------
# 3. Get SMS Index Number
# ---------------------------------------------------------------------------
# array set arr_einfo [event_reqinfo]
# set msg $arr_einfo(msg)
# set syslog_line [split $msg]
# set num [lindex $syslog_line 11]
# regsub -all .$ $num "" num
# action_syslog msg "Number is $num"

# ---------------------------------------------------------------------------
# 4. Get SMS Message & From Mobile Number
# ---------------------------------------------------------------------------
set Cmds [list "enable" "cellular 0/2/0 lte sms view summary" ]
CLIProc $cli1(fd) $Cmds

foreach line [split $cmd_output \n] {
	set instr_line [split $line]
	set instr [lindex $instr_line 0]
	switch $instr {
		0 {
			regsub -all "{}" $instr_line "" instr_line
			set mobileNum [lindex $instr_line 1]
			set smsMsg [lindex $instr_line 5]
		}
	}
}

action_syslog msg "Received From : $mobileNum"
action_syslog msg "Received Message : $smsMsg"

# ---------------------------------------------------------------------------
# 5. Append SMS log file
# ---------------------------------------------------------------------------
if [file exists sms.log] {
		    puts "file sms.log being overwritten"
		}
set myfileid [open sms.log a+]
puts $myfileid $cmd_output
close $myfileid

# ---------------------------------------------------------------------------
# 6. Actions
# ---------------------------------------------------------------------------
switch -regexp $smsMsg {
	Command {
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum Cpu, Serial, Route, Ios, RttCisco, RttAmazon" ]
		CLIProc $cli1(fd) $Cmds
	}
	^[0-9]+$ {
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $smsMsg Hello from ISR1100LTE! Please send #Command# to me!" 
]
		CLIProc $cli1(fd) $Cmds
	}
	Cpu {
		set Cmds [list "enable" "show proc cpu | i CPU" ]
		CLIProc $cli1(fd) $Cmds
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $cmd_output" ]
		CLIProc $cli1(fd) $Cmds
	}
	Serial {
		set Cmds [list "enable" "sh snmp chassis" ]
		CLIProc $cli1(fd) $Cmds
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $cmd_output" ]
		CLIProc $cli1(fd) $Cmds
	}
	Route {
		set Cmds [list "enable" "sh ip route summary | be Total" ]
		CLIProc $cli1(fd) $Cmds
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $cmd_output" ]
		CLIProc $cli1(fd) $Cmds
	}
	Ios {
		set Cmds [list "enable" "sh version | i bin" ]
		CLIProc $cli1(fd) $Cmds
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $cmd_output" ]
		CLIProc $cli1(fd) $Cmds
	}
	RttCisco {
		set Cmds [list "enable" "sh ip sla statistics 100 | i Latest RTT" ]
		CLIProc $cli1(fd) $Cmds
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $cmd_output" ]
		CLIProc $cli1(fd) $Cmds
	}
	RttAmazon {
		set Cmds [list "enable" "sh ip sla statistics 103 | i Latest RTT" ]
		CLIProc $cli1(fd) $Cmds
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $cmd_output" ]
		CLIProc $cli1(fd) $Cmds
	}
	Vlan[0-9]{1,2} {
		set vlanNum [string trim $smsMsg Vlan]
                set Cmds [list "enable" "conf t" "int range gi 0/1/0 - 3" "switchport access vlan $vlanNum" "end" "write memory" ]
		CLIProc $cli1(fd) $Cmds
		append smsmsg "gi 0/1/0 - 3 is changed to " "$smsMsg"
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $smsmsg" ]
		CLIProc $cli1(fd) $Cmds
	}
	default {
		set Cmds [list "enable" "cellular 0/2/0 lte sms send $mobileNum $smsMsg" ]
		CLIProc $cli1(fd) $Cmds
	}
} 

# ---------------------------------------------------------------------------
# 7. Clear messages on Modem
# ---------------------------------------------------------------------------
if [catch {cli_exec $cli1(fd) "enable"} result] {
    error $result $errorInfo
}
if [catch {cli_write $cli1(fd) "cellular 0/2/0 lte sms delete all"} result] {
    error $result $errorInfo
}
if [catch {cli_read_pattern $cli1(fd) "Are you sure you want to delete all SMS?"} result] {
    error $result $errorInfo
}
if [catch {cli_write $cli1(fd) "\n"} result] {
    error $result $errorInfo
}
if [catch {cli_read $cli1(fd)} result] {
    error $result $errorInfo
}

if [catch {cli_close $cli1(fd) $cli1(tty_id)} result] {
    error $result $errorInfo
}
```

## 解説
### 0. EEM Script
EEM Event Detectorの記法です。

```
::cisco::eem::event_register_syslog pattern "New SMS received" 
```

組込ライブラリの読み込み（ほぼおまじない）です。

```
namespace import ::cisco::eem::*
namespace import ::cisco::lib::*
```

### 1. Procedures
IOS Execコマンドをリストに入れて、連打するプロシージャです。

```
proc CLIProc {clifd cmds} {
    global errorInfo cmd_output
    foreach a_cmd $cmds {
        if [catch {cli_exec $clifd $a_cmd} result] {
            error $result $errorInfo
        } else {
            set cmd_output $result
        }
    }
}
```

### 2. Body
ここからが本文。cli_openで、EEMでコマンドを打ち込む内部的なVTYを開きます。

```tcl

if [catch {cli_open} result] {
    error $result $errorInfo
} else {
    array set cli1 $result
}
```

### 3. Get SMS Index Number
今回は使いませんでしたが、SMSを受信すると、Syslogが出ます。このSyslogからIndex番号を取り出しました。以下の例では、「14」の部分です。

```
*Jul 27 10:02:35.033: Cellular0/2/0: New SMS received on index 14. Please issue a view command to see it
*Jul 27 10:02:35.033: %CELLWAN-2-SMS_ARCH_PATH_UNCONFIGURED: Cellular0/2/0 failed to archive SMS because 'gsm|cdma|lte sms archive path' under cellular controller is not configured.
```

[event_reqinfo](https://qiita.com/kikuta1978/items/42526882b57eafe480ad#event_reqinfo-%E3%81%A8%E3%81%AF)のTcl版を使います。Event Detectorに応じて、内部的に情報を引っ張り出せます。Tclの場合、配列を作っておきます。今回はSyslog EDを使っているので、$arr_einfo(msg)でSyslogメッセージを取り出して、刻んでます。

```tcl
array set arr_einfo [event_reqinfo]
set msg $arr_einfo(msg)
```

何をevent_reqinfoで取り出せるか？については、毎回マニュアルを見てもいいのですが、IOSコマンドで確認できるので便利です。Tcl event_reqinfo Array Names:の部分す。以下、Syslog EDの例です。

```
C1111LTE#show event manager detector ?
  all                 All available event detectors
  application         Application event detector
  cli                 CLI event detector
  config              Config event detector
  counter             Counter event detector
  env                 Environmental event detector
  generic             Generic event detector
  gold                GOLD event detector
  interface           Interface event detector
  ioswdsysmon         Ioswdsysmon event detector
  ipsla               IPSLA event detector
  mat                 mac-address-table event detector
  neighbor-discovery  neighbor discovery event detector
  none                None event detector
  oir                 OIR event detector
  resource            Resource event detector
  rf                  RF event detector
  routing             Routing event detector
  rpc                 RPC event detector
  snmp                Snmp event detector
  snmp-notification   Snmp notification event detector
  snmp-object         Snmp Object event detector
  syslog              Syslog event detector
  test                Test event detector
  timer               Timer event detector
  track               Track event detector

C1111LTE#show event manager detector syslog detail 
No.  Name                Version   Node        Type    
1    syslog              01.00     node0/0     RP      

	Tcl Configuration Syntax: 
	::cisco::eem::event_register_syslog
		 [tag <tag-val>] 
		 [pattern <pattern-val>] 
		 [sequence <sequence-val>] 
		 [timestamp <timestamp-val>] 
		 [facility <facility-val>] 
		 [mnemonic <mnemonic-val>] 
		 [occurs <occurs-val>]
		 [period <period-val>]
		 [priority {all | emergencies | alerts | critical | 
		            errors | warnings | notifications | 
		            informational | debugging}]
		 [severity_fatal]
		 [severity_critical]
		 [severity_major]
		 [severity_minor]
		 [severity_warning]
		 [severity_notification]
		 [severity_normal]
                 [severity_debugging]
		 [queue_priority {normal | low | high | last}] 
		 [maxrun <sec.msec>]
		 [ratelimit <sec.msec>]
		 [nice {0 | 1}]

	Tcl event_reqinfo Array Names: 
	event_id
	job_id
	event_type
	event_type_string
	event_pub_time
	event_pub_sec
	event_pub_msec
	event_trigger_num
	event_severity
	msg 
	priority 
	sequence 
	timestamp 
	facility 
	mnemonic 

        Applet Configuration Syntax: 
	[ no ] event [tag <tag-val>] syslog 
		 [pattern <pattern-val>] 
		 [sequence <sequence-val>] 
		 [timestamp <timestamp-val>] 
		 [facility <facility-val>] 
		 [mnemonic <mnemonic-val>] 
		 [occurs <occurs-val>]
		 [period <period-val>]
		 [priority {all | emergencies | alerts | critical | 
		            errors | warnings | notifications | 
		            informational | debugging}]
		 [severity-fatal]
		 [severity-critical]
		 [severity-major]
		 [severity-minor]
		 [severity-warning]
		 [severity-notification]
		 [severity-normal]
		 [severity-debugging]
		 [maxrun <sec.msec>]
		 [ratelimit <sec.msec>]

        Applet Built-in Environment Variables: 
	$_event_id
	$_job_id
	$_event_type
	$_event_type_string
	$_event_pub_time
	$_event_pub_sec
	$_event_pub_msec
	$_event_severity
	$_syslog_msg 
	$_syslog_priority 
	$_syslog_sequence 
	$_syslog_timestamp 
	$_syslog_facility 
	$_syslog_mnemonic 
```

IOS内部の非同期イベントをEvent Detectorが捕捉し、それに関連する情報がevent_reqinfoで内部的に提供される、というのがEEMの醍醐味の一つですね。

### 4. Get SMS Message & From Mobile Number

モデム上のSMSログを確認するコマンド（cellular 0/2/0 lte sms view summary）を実行し、電話番号と受信メッセージを取得します。

```
C1111LTE#cellular 0/2/0 lte sms view summary 
ID   FROM                  YY/MM/DD HR:MN:SC  SIZE  CONTENT
<snip>                        
9    0907717****           18-07-27 17:48:03  8     Oh yeah                     
10   0907717****           18-07-27 17:49:33  8     hogehoge                    
11   0802011****           18-07-27 18:00:09  23    wifi on sent by hsakabe     
12   0908870****           18-07-27 18:00:27  5     ^_0U^S^_                       
13   0802011****           18-07-27 18:00:54  6     sh ver                      
14   0909007****           18-07-27 19:02:39  33    Hi, I am Kazumasa. You ar...
```

最初、「インデックス番号で最新のものを取得する」（上の例では、14の行を分解）という方針で行こうと考えましたが、SMSメッセージは別途、IOSフラッシュ上に保存するように実装し、モデム上は毎回ログをクリアすることにしました。モデム上は常に空っぽで、受信するとID 0が作られるようにしました。これで、モデム上のログ溢れは考えなくてよくなります。

確認用にsyslog出力をしていますが、送信してきた電話番号（mobileNum）と、SMSメッセージ（smsMsg）を取り出しました。

```
action_syslog msg "Received From : $mobileNum"
action_syslog msg "Received Message : $smsMsg"
```

今回は実装していませんが、セキュリティ用途として、登録澄み電話番号からのリクエストだけを処理するといった使い方も良いかと思いました。

### 5. Append SMS log file

EEMからIOS bootflash上にsms.logを作成し、cellular 0/2/0 lte sms view summary の結果を追記していきます。インデックス番号は、毎回クリアしているので、常に0になりますが、タイムスタンプが含まれるので良いでしょう。

こんな感じで保存されていきます。

```
C1111LTE#sh bootflash: | i sms.log
660      13220 Aug 05 2018 23:40:55.0000000000 +00:00 /bootflash/sms.log
C1111LTE#
C1111LTE#more bootflash:/sms.log

ID   FROM                  YY/MM/DD HR:MN:SC  SIZE  CONTENT
0    0909007****           18-08-03 11:46:04  3     Ios                         
C1111LTE#

ID   FROM                  YY/MM/DD HR:MN:SC  SIZE  CONTENT
0    0909007****           18-08-03 11:52:23  3     Cpu          
```

### 6. Actions

受信したメッセージ（smsMsg）に応じて、アクションを書いていきます。見たまんまですが、Switchを使います。

```
switch -regexp $smsMsg {...
```

以下のような例をデモ用に書いてみました。Vlan変更などは、確認用のテストとその結果も送信したいところですね。あと、SMSは文字数が限られるので、やり取りは最小限として、別途メールで送信または、ファイルサーバまたはローカルフラッシュにログを保存するといった方法も良いかと思いました。

- 電話番号が送られてきたら、その電話番号に対してHelloメッセージを送信
- Commandが来たら、対応コマンドを送信
- 対応コマンドは、Cpu、Ios、Route、Serialなど
- RttCisco、RttAmazonは、IOSのIP-SLA機能を使ってURLまでのRTTを測定し、その結果を送信
- Vlanと番号を送信すると、設定済みアクセスポート（gi 0/1/0 - 3）のVlanを変更

### 7. Clear messages on Modem

最後にモデム上のログをコマンド（cellular 0/2/0 lte sms delete all）でクリアして終わります。このコマンドは、プロンプトで確認が入るので、cli_execを連打するわけにもいきません。多くの場合、（file prompt quiet ） -> コマンド連打 -> （no file prompt quiet ）でいけるのですが、今回はこれが使えなかったので、パターンに呼応するタイプのコマンド実行を使います。これは、（cli_write）と（cli_read_pattern）を組み合わせて書けます。

```tcl

if [catch {cli_write $cli1(fd) "cellular 0/2/0 lte sms delete all"} result] {
    error $result $errorInfo
}
if [catch {cli_read_pattern $cli1(fd) "Are you sure you want to delete all SMS?"} result] {
    error $result $errorInfo
}
if [catch {cli_write $cli1(fd) "\n"} result] {
    error $result $errorInfo
}
```

# まとめ
意外と、簡単に色々できそうですね！
わりと力技のパターンを実行していますが、もうちょっと汎用化できれば、面白いかもしれません。