この記事は、NetOpsCoding Advent Calendar 2016の2016年12月17日の記事です。
「Amazon Dash Button」を入手したので、ルータ系で何かできないかと考えた結果、ルータをクラッシュさせる「Router Crash Button」を作ってみました。ボタンを押すと、ルータがクラッシュします。
Amazon Dash Buttonで、ルータをCrashさせてみた。 pic.twitter.com/iZR7vj6ToR
— kooshin (@kooshin) 2016年12月8日
仕組み
ボタンを押してからルータがクラッシュまで、下記の通りで実行します。
- 人がボタンを押す
- スクリプトがボタンが押されたことを検知(ARPパケット検知)
- スクリプトがルータにTelnetする
- スクリプトがルータの隠しコマンド(test crash )を発行し、Bus Errorを強制的に発生
- ルータがクラッシュする
- ルータが再起動する
- また、人がボタンを押す。。。。
- 2に戻る
Amazon Dash Button
Amazon Dash Buttonは、インターネットと通信するために、IPアドレスがDHCPから割り当てられます。IPアドレスが割り当てられる機器のため、ボタンごとに、MACアドレスが異なります。このMACアドレスを利用して、ボタンを識別します。今回は「消臭力」のAmazon Dash Buttonを入手しました。設定方法は参考文献を参照してください。
ボタンが押されたことの検知(ARPパケット検知)
Amazon Dash Buttonが押された場合、ARPパケットがブロードキャストされます。このARPパケットをパケットキャプチャすることで、ボタンが押されたことを検知します。
Amazon Dash Buttonは、電源OFFの状態で、ボタンが押されると電源ONし、DHCPでIPアドレスの割当が行われた後、IPアドレスの重複確認のために、ARP Requestパケットが送信されます。このARP Requestパケットの送信元MACアドレスを確認し、Amazon Dash ButtonのMACアドレスと比較して、合致していればボタンが押されたと判断します。
環境
無線LAN環境で事前にAmazon Dash Buttonの設定をします。設定方法は参考文献を参照してください。
- Ubuntu Linux 16.04
- libpcap0.8 - パケットキャプチャ用
- Ruby 2.3.0
- expect4r 0.0.11 - Telnetライブラリ
- pcaprub 0.12.4 - パケットキャプチャライブラリ
- pio 0.30.0 - パケットパースライブラリ
コード
コードはgithubにありますので参照してください。
https://github.com/kooshin/router-crash-button
スクリプトの配置は下記のとおりです。
.
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── crash_button.rb - Ruby実行ファイル(root権限で)
└── lib
└── dash_button.rb - Amazon Dash Button用のライブラリ
crash_button.rb
Amazon Dash Buttonが押されたらルータをクラッシュさせるスクリプトです。
下記の通り、パラメータを設定します。デモコードのため、すべてスクリプトに埋め込んであります。
DASH_BUTTON_MAC_ADDR = '88:71:e5:63:aa:d5' # ボタンのMACアドレス
NETDEV = 'enp0s25' # パケットキャプチャするインタフェース
ROUTER_HOST = '192.168.88.5' # クラッシュさせるルータのIPアドレス
ROUTER_USER = 'cisco' # Telnet時のユーザ名
ROUTER_PASSWORD = 'cisco' # Telnet時のパスワード
ROUTER_ENABLE = 'cisco' # enableパスワード
自作DashButtonクラス(lib/dash_button.rb)のインスタンスを作成します。引数はパケットキャプチャするインタフェースと、検知するAmazon Dash ButtonのMACアドレスです。MACアドレスは、Wireshark等でパケットキャプチャすることでわかります。
dash_button = DashButton.new(NETDEV, DASH_BUTTON_MAC_ADDR)
DashButtonクラスのmonitorメソッドにブロックを渡します。Amazon Dash Buttonを検知するとブロックが実行されます。
今回は、ルータをクラッシュさせるコードを実行します。expect4rライブラリを使ってTelnetしています。詳細はRubyのexpect4rでCiscoルータにTelnet/SSHしてコマンド実行するを参照してください。
ルータにログイン後、ios.exp_printでtest crashコマンドを送信します。「Enable crash router selection marked with (crash router)」するために「C」を送信し、「(crash router) Bus Error, due to invalid address access」を引き起こすために、1を送信します。
dash_button.monitor do
begin
puts "#{DateTime.now} Crash Button pressed!! Force crash the Router"
ios = Expect4r::Ios.new_telnet(
host: ROUTER_HOST,
user: ROUTER_USER,
pwd: ROUTER_PASSWORD,
enable_password: ROUTER_ENABLE
)
ios.login
ios.exp_print("test crash\rC\r1\r")
rescue Expect4r::ExpTimeoutError, Errno::EIO => e
puts "#{DateTime.now} Failed: #{e}"
end
end
lib/dash_button.rb
pcaprubライブラリとpioライブラリを使って、Amazon Dash ButtonのARP Requestパケットを検出します。
PCAPRUBY::Pcap.open_liveでパケットキャプチャを開始します。この際、インタフェースの指定と、BPFフィルタでARPパケットのみパケットキャプチャするよう指定します。
def pcap
return @pcap if @pcap
@pcap = PCAPRUB::Pcap.open_live(
netdev,
SNAPLENGTH,
PROMISCOUS_MODE,
TIMEOUT
)
@pcap.setfilter(BPF_FILTER)
end
monitorメソッド内のpcap.each_packetでパケットを受信するたびに、ボタンが押されたかを判断します。パケットをPio::Parser.readでパースします。パースしたパケットを、dash_button_pressed?メソッドで、ボタンが押されたかどうかを判断します。ボタンが押されたと判断した場合、monitorメソッドの呼び出し元のブロックが実行されます。
def monitor
pcap.each_packet do |raw_packet|
begin
packet = Pio::Parser.read(raw_packet.data)
yield if dash_button_pressed?(packet)
rescue Pio::ParseError
next
end
end
end
dash_button_pressed?メソッド内では、ARP Requestパケットであるか、指定のMACアドレスであるかを判断します。
def dash_button_pressed?(packet)
arp_packet?(packet) && dash_button_mac_addr?(packet)
end
def arp_packet?(packet)
Pio::Arp::Request == packet.class
end
def dash_button_mac_addr?(packet)
dash_button_mac_addr == packet.source_mac
end
実行
パケットキャプチャのために、libpcap-devを予めインストールし、コードをcloneして、bundleコマンドでRubyのライブラリをインストールします。
$ sudo apt install libpcap-dev
$ git clone https://github.com/kooshin/router-crash-button
$ bundle
パケットキャプチャするため、スクリプトの実行にはroot権限が必要です。sudoで実行すると良いでしょう。説明は省略していますが、Rubyはrbenvを用いてインストールしています。rbenvの場合、別途rbenv-sudoプラグインが必要です。
$ sudo ruby crash_button.rb
or
$ rbenv sudo ruby crash_button.rb
Waiting for Crash Button press...
実行結果
上記のTwitterの動画を参照してください。