ヤマハルーターのトラフィックを可視化する(Luaスクリプト+PHP+Grafana+ラズベリーパイ)

ネットワーク

ヤマハルーターのLuaスクリプトとラズパイに構築したPHPMySQLGrafanaを組み合わせて、ヤマハルーターのトラフィックを集計してグラフ化してみました。

Grafanaのスクリーンショット
ヤマハルーターの通信量の変化やVPNが占める割合などをグラフで直感的に把握することができます!
スポンサーリンク

しくみ

ヤマハルーターでLuaスクリプトを定期実行させ、トラフィック情報を収集し、ラズパイへ送信するようにします。

システム構成図 ヤマハルーターでLuaスクリプトを動作させ、ラズパイにPOSTでデータを送信する

ラズパイにはあらかじめ送信先となるAPIを用意しておき、送信されたデータを解析して、MySQLなどのデータベースにトラフィック情報を格納します。格納したデータはGrafanaなどの可視化ツールによりグラフ化し、ブラウザからかんたんに確認できるようにします。

Grafanaはデータ可視化ツールと呼ばれるもので、手軽にデータベースの内容をグラフ化して表示することができるアプリケーションです。ラズパイにインストールすることで、ブラウザからデータベースの内容を視覚的に参照することができます。Grafanaの概要・使い方については、こちらで紹介しています。

今回はAPIの作成にPHPを使用しましたが、MySQLにデータを登録できるのであれば、どの言語でも対応できます。ヤマハルーターには専用のLuaスクリプトAPIが用意されていますが、多くの場合はピンポイントで数値を得ることはできません。Luaスクリプトからコマンドを実行し、出力を文字列解析する必要が出てきます。Luaスクリプトでも解析は不可能ではありませんが、できることが限られているため、今回はルーターからはコマンドの実行結果をそのまま送信し、API側で正規表現による文字列解析を行うことにしています。

なお、今回は以下の項目を集計対象とします。

  • LAN1の受信・送信(合計・IPv4のみ・IPv6のみ)
  • LAN2の受信・送信(合計・IPv4のみ・IPv6のみ)
  • PP1の受信・送信(PPPoE)

ちなみに、GitHubにはtunnelの集計プログラムも掲載しています。VPNのトラフィックも集計することができますので、興味があれば参照してみてください。

GitHub - takabus/rtx-traffic-logger: get traffic data from Yamaha RTX router
get traffic data from Yamaha RTX router. Contribute to takabus/rtx-traffic-logger development by creating an account on ...
スポンサーリンク

ヤマハルーターのLuaスクリプトの基本

ヤマハルーターでのLuaスクリプトの扱いについて、基本事項をまとめておきます。

コンソールでLuaスクリプトを動かしてみる

ヤマハルーターでコンソールからLuaスクリプトを実行するには、lua -eコマンドを使用します。

lua -e 'print(123)'

スクリプトの実行には管理ユーザーとしてルーターにログインしておく必要があります。

また、コンソールから直接実行する場合、スクリプトは改行せず、1行にしておく必要があります。コピペして流し込む前に改行をなくしておく必要があるので、注意してください。

WebサイトやAPIへPOSTするには?

WebサイトやAPIへのPOSTには、ヤマハルーター独自APIであるrt.httprequestを使用します。

ヤマハルーター専用 API

例えば、http://test.lc/rtxlogger/api.phpにPOSTする場合は、以下のようなコードとなります。

-- POSTデータ作成
req_t = {
    url = "http://test.lc/rtxlogger/api.php",//URL
    method = "POST",
    content_type = "application/x-www-form-urlencoded;charset=sjis"
}
req_t.post_text =
    string.format(
    "key1=%s&key2=%s",
    "hoge",
    "fuga"
)

注意点としては、

  • 文字コードがShiftJISになること
  • エンコードは自分で行う必要があること

などがあげられます。

まず、送信される際の文字コードはUTF-8ではなく、ShiftJISになります。日本語を含むデータを送信する際に文字化けの原因となりますので、content_typeに文字コードがShiftJISであることを明記しておくことをおすすめします。上記サンプルにも盛り込んであります。

また、POSTするボディは自分でエンコードを行う必要があります。サンプルのようにstring.formatを使用して、content_typeにあったエンコードを行ってください。上記サンプルではcontent_typeをapplication/x-www-form-urlencodedとしていますので、「key=value」形式のデータを&(アンパサンド)でつなげています。エンコードした後の形式がわからない場合は、Postmanなどのツールを使用してみるといいかもしれません。

コマンドの実行

ヤマハルーターのコマンドを実行するには、rt.commandを使用します。

rtn, env = rt.command("show environment")

第1引数には実行結果(成功したらtrue)、第2引数には標準出力(コマンドの出力)が格納されます。管理ユーザーとして実行されますので、十分内容を確認してから実行してください。

rt.commandでコマンドの実行結果を取得し、rt.httprequestでラズパイに送信する感じになりますね。

Luaスクリプトの作成

それではさっそくLuaスクリプトを作成していきましょう。なお、コード全体はGitHubを参照してください。

GitHub - takabus/rtx-traffic-logger: get traffic data from Yamaha RTX router
get traffic data from Yamaha RTX router. Contribute to takabus/rtx-traffic-logger development by creating an account on ...

-- YAMAHAルーターで実行するスクリプト

-- コマンド
rtn, lan1 = rt.command("show status lan1")
rtn, lan2 = rt.command("show status lan2")
rtn, pp1 = rt.command("show status pp 1")

-- POSTデータ作成
req_t = {
    url = "http://test.lc/rtxlogger/api.php",
    method = "POST",
    content_type = "application/x-www-form-urlencoded;charset=sjis"
}
req_t.post_text =
    string.format(
    "lan1=%s&lan2=%s&pp1=%s",
    lan1,
    lan2,
    pp1,
)

-- サーバーにPOST
rt.httprequest(req_t)

LAN1、LAN2、PP1のステータスを取得し、ラズパイにPOSTしています。

コンソールからテストする際は、lua -eにつづいて上のスクリプトを記述します。改行は除去する必要があります。

lua -e 'rtn, lan1 = rt.command("show status lan1")rtn, lan2 = rt.command("show status lan2")rtn, pp1 = rt.command("show status pp 1")req_t = {    url = "http://test.lc/rtxlogger/api.php",    method = "POST",    content_type = "application/x-www-form-urlencoded;charset=sjis"}req_t.post_text =    string.format(    "lan1=%s&lan2=%s&pp1=%s",    lan1,    lan2,    pp1,)rt.httprequest(req_t)'

ラズパイにAPIを作成する

次にルーターからPOSTされたデータを受けるAPIをラズパイに作成します。

<?php

require_once __DIR__ . '/vendor/autoload.php';

// 
// Eloquentの初期化
// 

class_alias(Illuminate\Database\Capsule\Manager::class, 'DB');
$database = new DB();
$config = [
    'driver'    => 'mysql',              // データベースの種類
    'host'      => 'localhost',          // ホスト名
    'database'  => '',           // データベース名
    'username'  => '',               // ユーザー名
    'password'  => '',           // パスワード
    // 'charset'   => 'utf8mb4',            // 文字セット(任意)
    // 'collation' => 'utf8mb4_general_ci', // コレーション(任意)
];
$database->addConnection($config);
$database->setAsGlobal();
$database->bootEloquent();


// 
// POSTされたときの処理
// 
if ($_SERVER["REQUEST_METHOD"] == "POST") {

    preg_match("/送信パケット:                   (\d+)/u", mb_convert_encoding($_POST["lan1"], "UTF-8", "sjis"), $d["lan1_total_send"]);
    preg_match("/受信パケット:                   (\d+)/u", mb_convert_encoding($_POST["lan1"], "UTF-8", "sjis"), $d["lan1_total_receive"]);
    preg_match("/IPv4\(全体\/ファストパス\):      (\d+) パケット/u", mb_convert_encoding($_POST["lan1"], "UTF-8", "sjis"), $d["lan1_v4_send"]);
    preg_match("/IPv6\(全体\/ファストパス\):      (\d+) パケット/u", mb_convert_encoding($_POST["lan1"], "UTF-8", "sjis"), $d["lan1_v6_send"]);
    preg_match("/IPv4:                         (\d+) パケット/u", mb_convert_encoding($_POST["lan1"], "UTF-8", "sjis"), $d["lan1_v4_receive"]);
    preg_match("/IPv6:                         (\d+) パケット/u", mb_convert_encoding($_POST["lan1"], "UTF-8", "sjis"), $d["lan1_v6_receive"]);

    preg_match("/送信パケット:                   (\d+)/u", mb_convert_encoding($_POST["lan2"], "UTF-8", "sjis"), $d["lan2_total_send"]);
    preg_match("/受信パケット:                   (\d+)/u", mb_convert_encoding($_POST["lan2"], "UTF-8", "sjis"), $d["lan2_total_receive"]);
    preg_match("/IPv4\(全体\/ファストパス\):      (\d+) パケット/u", mb_convert_encoding($_POST["lan2"], "UTF-8", "sjis"), $d["lan2_v4_send"]);
    preg_match("/IPv6\(全体\/ファストパス\):      (\d+) パケット/u", mb_convert_encoding($_POST["lan2"], "UTF-8", "sjis"), $d["lan2_v6_send"]);
    preg_match("/IPv4:                         (\d+) パケット/u", mb_convert_encoding($_POST["lan2"], "UTF-8", "sjis"), $d["lan2_v4_receive"]);
    preg_match("/IPv6:                         (\d+) パケット/u", mb_convert_encoding($_POST["lan2"], "UTF-8", "sjis"), $d["lan2_v6_receive"]);

    preg_match("/受信: (\d+) パケット/u", mb_convert_encoding($_POST["pp1"], "UTF-8", "sjis"), $d["pp1_receive"]);
    preg_match("/送信: (\d+) パケット/u", mb_convert_encoding($_POST["pp1"], "UTF-8", "sjis"), $d["pp1_send"]);

    // 現在時刻を取得
    $dt = new DateTime();
    $dt = $dt->modify('-9 hours'); //GrafanaはUTCで登録する必要あり

    // DBに入れるデータを作成する
    $data = array_map(function ($item) {
        if (isset($item[1])) {
            return $item[1];
        } else {
            return null;
        }
    }, $d);
    $data["update_at"] = $dt;
    $data["host"]=$_SERVER["REMOTE_ADDR"];


    // DBにインサートする
    DB::table("traffics")->insert($data);
}

POSTされたデータを取得し、取得されたデータからパケットに関する必要な数値を抽出しています。ここは正規表現で抽出するしかありませんので、少し大変です。ダイレクトに値を取得できるAPIがルーターに用意されていればよかったのですが、現状では用意されていませんので、がんばるしかないようです。文字コードがShiftJISであるため、pgrep_matchで抽出する前に、一応UTF-8に変換しています。

抽出した通信量データはひとつの配列にまとめ、illuminate/Databaseを使用して、データベースに格納しています。illuminate/DatabaseはSQLフリーでデータベースを扱えるライブラリで、Laravelのクエリビルダとまったく同じことができます。PDOやSQL文に関する知識がなくても、MySQLやSQLiteにデータを保存することができますので、とても便利です。インストールもcomposerからコマンド一発でできてしまいます。ぜひ活用してみてください。

GitHub - illuminate/database: [READ ONLY] Subtree split of the Illuminate Database component (see laravel/framework)
Subtree split of the Illuminate Database component (see laravel/framework) - GitHub - illuminate/database: Subtree split...

なお、データベースを用意するのが大変という方のために、データをテキストファイルに書き出すコードも掲載しておきます。上のコードに以下の関数を追加して、logging($d)で呼び出せば、データベースがなくても動作を確認できますよ。

【PHP】POST・GETされたデータとヘッダーをロギングする方法
デバッグするときに、POST・GETで送信したデータやリクエストヘッダーを確認したいときがあります。こういったデータをテキストファイルに書き出すPHPコードを掲載しておきます。かんたんにクライアントからのリクエストを把握できるようになるので...
function logging($val)
{
    // ログ保存ファイル名
    define("TXTFILE", "rtxlogger.log");
    // ファイルを追記モードでオープン
    $fh = fopen(TXTFILE, "a+");
    // GETされたデータをすべて取得して、配列として取得
    $str = print_r($val, true);
    // ファイルに追記する
    fputs($fh,  $str);
    // ファイルを閉じる
    fclose($fh);
}

もちろんですが、ディレクトリの書き込み許可をお忘れなく。

MySQLにデータベースを作成する

収集したデータを保存するためのデータベースを作成します。データベースの作成方法については、ここでは省略しますが、カラムの作成には次のSQLをご利用ください。

ALTER TABLE `traffics` ADD `host` VARCHAR(15) NULL AFTER `id`;
ALTER TABLE `traffics` ADD `lan1_total_send` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan1_total_receive` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan1_v4_send` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan1_v6_send` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan1_v4_receive` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan1_v6_receive` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan2_total_send` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan2_total_receive` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan2_v4_send` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan2_v6_send` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan2_v4_receive` BIGINT NULL;
ALTER TABLE `traffics` ADD `lan2_v6_receive` BIGINT NULL;
ALTER TABLE `traffics` ADD `pp1_receive` BIGINT NULL;
ALTER TABLE `traffics` ADD `pp1_send` BIGINT NULL;
ALTER TABLE `traffics` ADD `update_at` DATETIME NULL;

テストしてみる

Luaスクリプト、APIの作成からデータベースの容易まですべて終わったら、きちんとデータを収集できるかテストしてみましょう。ルーターのコンソールでスクリプトを実行してみます。

lua -e 'rtn, lan1 = rt.command("show status lan1")rtn, lan2 = rt.command("show status lan2")rtn, pp1 = rt.command("show status pp 1")req_t = {    url = "http://test.lc/rtxlogger/api.php",    method = "POST",    content_type = "application/x-www-form-urlencoded;charset=sjis"}req_t.post_text =    string.format(    "lan1=%s&lan2=%s&pp1=%s",    lan1,    lan2,    pp1,)rt.httprequest(req_t)'

データベースにデータが蓄積されていれば、成功です!

Luaスクリプトの定期実行

毎日AM1:00に作成したLuaスクリプトを実行させ、データを収集するようにします。

schedule at ID 01:00 * lua -e 'スクリプトをここに貼り付け'

例えばこんな感じになります。

schedule at 2 01:00 * lua -e 'rtn, lan1 = rt.command("show status lan1")rtn, lan2 = rt.command("show status lan2")rtn, pp1 = rt.command("show status pp 1")req_t = {    url = "http://test.lc/rtxlogger/api.php",    method = "POST",    content_type = "application/x-www-form-urlencoded;charset=sjis"}req_t.post_text =    string.format(    "lan1=%s&lan2=%s&pp1=%s",    lan1,    lan2,    pp1,)rt.httprequest(req_t)'

毎時に行いたいなら、以下のようになります。

schedule at 2 *:00 * lua -e 'rtn, lan1 = rt.command("show status lan1")rtn, l
an2 = rt.command("show status lan2")rtn, pp1 = rt.command("show status pp 1")re
q_t = {    url = "http://test.lc/rtxlogger/api.php",    method = "POST",    con
tent_type = "application/x-www-form-urlencoded;charset=sjis"}req_t.post_text =
   string.format(    "lan1=%s&lan2=%s&pp1=%s",    lan1,    lan2,    pp1,)rt.htt
prequest(req_t)'

IDは重複していると、既存の設定を上書きしてしまいますので注意してください。多くの場合、スケジュールのID1にはNTPによる自動時刻修正が登録されているため、上記のサンプルではあえてIDを2としておきました。

誤って消してしまった人のためにNTPのスケジュールコマンドも掲載しておきます~

schedule at 1 */Sun 01:00 * ntpdate ntp.nict.jp

自分もうっかりやってしまいましたので(^^;)

Grafanaでグラフ化してみる

データの蓄積も確認できましたので、Grafanaでグラフを表示してみます。Grafanaの基本的な使い方については、以下の記事をご覧ください。

データ可視化ツール Grafana入門
データ可視化ツールGrafanaを使ってみよう!便利な使い方をまとめています。

新しいダッシュボードを作成し、クエリビルダで書き出したいデータを設定します。

あわせてサイドメニューでUnit(単位)をBytesにします。

これでグラフが表示されました。

ただし、ヤマハルーターはパケットで通信量を表記しているため、得られた数値をbyteに変換する必要があります。そこで、クエリエディタに切り替えて、データベースから数値を取得するときに128倍(∵1パケット128byte)するようにクエリを編集します。

きちんと表示できました!完成です!

通信種類ごとの円グラフも描いてみました。

青はIPoE赤はIPSecVPN、黄色はPPPoEの通信量(いずれも下り)を示します。

私の場合、拠点間VPNとリモートVPNのみPPPoEに流し、その他すべてIPoEに流すようにしています。最近はFireTVでYouTubeを垂れ流しにしていることが多かったので、ほとんどがIPoEのトラフィックかなと思っていましたが、意外とVPN通信が多いことがわかりました。VPN接続先にある自宅サーバーに大量の写真をバックアップしているため、気づかぬうちにトラフィックが増えていたようです。

まとめ

RTXのLuaスクリプト機能を使い、ラズパイでトラフィックを監視、Grafanaでグラフ化するという今回の企画。実際に1週間ほど運用してみた結果がこちらです。

ちゃんと通信量が記録されていることがわかります。この1週間は不在にしていたため、あまり大きなトラフィック増加はありませんでしたが、ふだんはもっとトラフィックが増えているものと思われます。

固定回線であれば自己満足にすぎませんが、RTX810をはじめ、一部のヤマハルーターはUSB通信端末にも対応しています。移動拠点の場合は通信量を分析することで、最適なプラン設定や無駄な通信を洗い出すのに役立ちそうです。

ちなみに、Grafanaは通知も充実しています。一定のトラフィックを超えたら、LINENotifyで通知するといったこともGrafanaの機能を使えば、かんたんに実現できます。組み合わせてみると面白いかもしれませんね。

まだ1週間しかも不在時ですので、もう少しデータがほしいところです。データが蓄積されてきて、なにか発見があれば、追記していきたいと思います。

コメント

タイトルとURLをコピーしました