bashのワンライナーで複数台のホストに対し並列疎通確認を行う

はじめに

ホストの疎通確認ではお約束のpingコマンドですが、対象ホストが複数台いるとちょっとめんどくさいですよね。 複数ホストに対して並列pingを行うコマンドもOSSでいくつかありますが、そういったツールを入れるのもそれはそれでめんどくさい。

今回は、そんな環境でも容易に並列pingが可能なワンライナーを書いていきます。 (せっかくなのでICMPだけではなく、同じようにTCP/UDPでの疎通確認を行うワンライナーについても作っていきます。)

実行ホストに必要なコマンドは以下です。

  • ping
  • xargs(gnu)
  • gawk(awk)
  • grep
  • curl(tcp/udpでの疎通確認を行う場合)

少し注意が必要なのは、timestampを付与するためにawkを使う際、strftimeを使うためgawkである必要があります。 mawkだとstrftimeがないためエラーになっちゃうので注意。

並列でのpingコマンド実行

結論から出してしまうと、並列でpingコマンドを実行する場合は以下のようなワンライナーで実現可能です。 GNU xargsでは-Pというオプションがあり、これを利用することで容易に並列実行するプロセス数を指定することが出来ます。

echo 192.168.0.{1..100} | fmt -1 | xargs -P 100 -I@ bash -c "ping @ | awk '{print strftime(\"%F %T \")\"(@)\"\"\t\" \$0}{fflush() }'" | grep -e'ttl=' -e'timeout' -e'Unreachable'

それぞれのコマンドの意味をコメントとして記述すると、以下のようになります。

# bashのブレース展開を使って、192.168.0.1〜192.168.0.100 までを表示
echo 192.168.0.{1..100} | \
    # スペース区切りとして出力されている192.168.0.1〜192.168.0.100を1列に変換
    fmt -1 | \
    # xargsで`bash -c "..."`内の処理を実行する. オプションの意味は
    #   -P 100 ... プロセスの並列実行数を指定(今回の場合は100)
    #   -I @ ... 実行するコマンドに記述されている`@`を、標準入力で受け付けた値(今回の場合`192.168.0.X`)に置き換える
    xargs -P 100 -I @ \
        bash -c "ping @ | \
                 awk '{print strftime(\"%F %T \")\"(@)\"\"\t\" \$0}{fflush() }'" | \
    # 並列実行したpingの出力から、`ttl=`か`timeout`、`Unreachable`がある行のみを抽出する
    # (疎通する行のみを表示する場合は`ttl=`のみを指定)
    grep -e'ttl=' -e'timeout' -e'Unreachable'

xargsで実行しているbashの処理が少しわかりにくいと思うので、動作させるためのエスケープを消して抜粋、説明を追記したコードを記載します。

# `@`(今回の場合はxargsで値を置き換えるため、`192.168.0.X`)に対し、icmpでの疎通確認を実行
ping @ | \
    # awkを使って、標準入力で受け付けた値(今回の場合、pingの結果)の行頭に↓のような出力を追加する
    # `YYYY-mm-dd HH:MM:SS (@) ${tab}`
    awk '{print strftime("%F %T ")"(@)""\t" $0}{fflush() }'

最初の echo ... | fmt -1 の箇所は、疎通対象となるホストの一覧を書いたファイルをcatで開くように変更することも出来ます。

tcp/udpへの疎通確認を行う場合(curlを使用する場合)

icmpでの疎通確認をする方法についてはpingコマンドを使えばいいですが、tcp/udpでの疎通確認を行う場合は別の方法で対応する必要があります。

こういった場合に使いやすいのがcurlコマンドです。 curlコマンドではhttpのほか、telnetやftpなどイロイロなプロトコルをuriで指定することが出来ます。

これを利用すると、以下のようにワンライナーを書くことが出来ます。 以下の例では、22番ポートに対する疎通確認を行っています。

echo 192.168.0.{1..100}:22 | fmt -1 | xargs -P 100 -I@ bash -c "while :; do (echo | curl -m 2 -s telnet://@ 2>&1 >/dev/null && echo ok || echo ng) | awk '{print strftime(\"%F %T \")\"(@)\"\"\t\" \$0}{fflush() }';sleep 1;done"

基本的な作りはpingを並列実行する処理と変わらないですが、xargsで実行させているコマンドが違いますね。 こちらもエスケープを消して抜粋、説明を追記してみましょう。

# whileでloop処理
while :;
do
    # curlでtelnetによる通信を行わせる処理.
    # curlの手前にechoを入れておくことで、標準入力を行わせている.
    # curlのオプション、引数の内容は以下
    #   -m 2 ... タイムアウト(2秒)
    #   -s ... サイレントモードでの実行
    #   telnet://@ ... telnetでの通信を行うようuri指定(今回の場合はxargsで値を置き換えるため、`192.168.0.X:22`が入る)
    (echo | curl -m 2 -s telnet://@ 2>&1 >/dev/null \
     && echo ok || echo ng) | \
        awk '{print strftime(\"%F %T \")\"(@)\"\"\t\" \$0}{fflush() }';
    sleep 1;
done

tcp/udpへの疎通確認を行う場合(bashの機能を使用する場合)

tcp/udpでの疎通確認を行う場合では、curlを使わずにbashの組み込み機能を利用する方法もあります。 bashでは/dev/{tcp,udp}/{hostname}/{port}というpathに対してリダイレクトをすることで通信を行うことが可能なため、これを利用する方法になります。

echo > /dev/tcp/192.168.0.40/22

# 正常終了であれば0、異常終了であれば1となる
echo $?

この機能を利用したワンライナーだと、以下のようになります。

echo 192.168.0.{1..100}/22 | fmt -1 | xargs -P 100 -I@ bash -c "while :; do (echo > /dev/tcp/@ 2>&1 >/dev/null && echo ok || echo ng) | awk '{print strftime(\"%F %T \")\"(@)\"\": \" \$0}{fflush() }';sleep 1;done"

ちなみに少し注意したいのは、この方法だとタイムアウトにかかる時間がカーネルパラメータ等に依存する(echo >/dev/{tcp,udp}/{hostname}/{port}の頭にtimeoutを付与しても効かない)ため、柔軟に所要時間を定義出来ません。 また、/dev/nullへのリダイレクトを行っていますがこれの対処範囲外でエラーメッセージが表示されるため、curlのときに比べて少し扱いにくい点も注意が必要です。

curlないけど、簡単にポートチェックするかぁ〜 というときに使えるかな?というレベルのコードなので、あんまり使う機会も無いかもしれませんね。

さいごに

書き捨てのコードですが、このような形でかんたんに並列疎通確認を行うことができます。 少し注意したいのは、今回記載しているコードはあくまでも書き捨てであることです。 (xargsで並列プロセスを指定して実行する方式は都度プロセスを生むため、あまりパフォーマンス的にはよくないやり方だったりします)

実行するホストのパフォーマンスを考えて、かつ継続して同じ作業をする場合であれば、Goとかでツールを作るほうがいいかなと思います。

© 2016 - 2022 DARK MATTER / Built with Hugo / Theme Stack designed by Jimmy