ブロードキャストストームの抑制 : FWX120 コマンド設定

本設定例では、ルーターのL2MSマスター機能・Luaスクリプト機能と、L2スイッチのL2MSスレーブ機能を使用しています。L2MSを使って、ルーターから、スイッチが持つ機能の設定を行います。

ルーターの対応機種は、FWX120です。

L2スイッチの対応機種は、SWX2200-8GSWX2200-24GSWX2200-8PoEです。

L2MSマスター(ルーター)とL2MSスレーブ(L2スイッチ)の対応リビジョンは、技術資料「L2MS」でご確認ください。

図 Luaスクリプト構成図1
図 Luaスクリプト構成図2

SWX2200のポートを監視し、ブロードキャストストームの発生を検知した際、受信量の最も多いSWX2200のポートのブロードキャストパケットに関する受信帯域を絞り、ブロードキャストストームを抑制します。
また、本設定例はマルチキャストパケットについても同様の処理を行い、マルチキャストストームを抑制することができます。

光回線に接続するためには、別途ONUが必要です。

本設定例は、Luaスクリプト例とブロードキャストパケットおよびマルチキャストパケットの抑制を行うためのモジュールファイルを使用します。このモジュールファイルをルーターにコピーする必要があります。

設定手順

本設定例は、以下の流れで設定します。

  1. ルーターにConfigを設定
  2. 構成を構築
  3. ルーターにモジュールファイルをコピー
  4. Luaスクリプトを実行

対応機種のうち、設定例を掲載している機種は、以下のとおりです。

機種 掲載内容 備考
ルーター FWX120 コマンド設定例
Luaスクリプト例
モジュールファイル
L2MSマスター機能、
Luaスクリプト機能
L2スイッチ SWX2200-8G SWX2200-24G L2MSスレーブ機能

モジュールファイルを利用した設定の導入手順

FWX120に本設定例のConfigを設定する

  1. 設定例のリンクから設定ファイルをダウンロードし、FWX120に設定を追加してください。
pp auth myname (ISPに接続するID) (ISPに接続するパスワード)
dns server (ISPより指定されたDNSサーバーのIPアドレス)
[解説]
上記のように設定例で赤字で記述されている設定値は、適切な値に変更してください。
FWX120にはTFTPを使用してPCからルーターに設定ファイルをコピーすることができます。
この方法については取扱説明書をご参照ください。

構成を構築する

  1. 本設定例ページのトップにある通常時の構成図と同様の構成を構築します。

モジュールファイルをルーターにコピーする

  1. モジュールファイルをPCにダウンロードします。
    [解説]
    モジュールファイル名はLuaスクリプトの動作にも関係がありますので、本設定例では変更せずそのままご使用ください。
  2. PCにUSBメモリを接続して、ダウンロードしたモジュールファイルをUSBメモリにコピーします。
  3. USBメモリをPCから取り外し、ルーターに接続します。ルーターのUSBランプが点灯します。
  4. ルーターコンソールを開きます。
    [解説]
    ルーターコンソールはシリアルケーブルやtelnetで接続して使用します。
    使用方法については取扱説明書をご参照ください。
  5. 管理者権限でログインし、コマンドでルーターにモジュールファイルをコピーします。
    copyコマンドを使用します。
    USBメモリのルートディレクトリーからルーターのルートディレクトリーにモジュールファイルをコピーする場合:
    [ルーターコンソール]
    # copy usb1:/lua_rtx1200_storm-limit.lib /lua_rtx1200_storm-limit.lib
    #
    [解説]
    copyコマンドの詳細は こちらをご参照ください。
    ファイルをルーターのルートディレクトリー以外にも格納することができます。
    その場合は、ディレクトリーを作成してから、そのディレクトリーにコピーします。
    そちらの方法については 参考資料をご参照ください。
    技術資料 「RTFS」-「コマンド一覧」-「ディレクトリの作成」
    モジュールファイルをディレクトリーにコピーした場合、環境変数LUA_PATHでそのディレクトリーを指定する必要があります。そちらの方法は 参考資料をご参照ください。
    技術資料 「Lua スクリプト機能」 - 「詳細」にある「5.ルーターの環境変数 」をご参照ください。
    モジュールファイルの名前を変更した場合、Luaスクリプト内「require関数」の引数の名前も変更する必要があります。
    [補足]
    本設定例のモジュールファイルを、copyコマンドではなくTFTPを使用してRTFSへコピーする場合は、バイナリモードでコピーしてください。

Luaスクリプトの導入手順

[解説]
Luaスクリプトのダウンロードから実行までの手順はLuaスクリプト導入手順マニュアルをご覧ください。

FWX120の設定例

環境変数の設定 set LUA_PATH="./\?.lib;" #注釈1
LANインターフェースの設定
(LAN1ポートを使用)
ip lan1 address 192.168.0.1/24
WANインターフェースの設定
(LAN2ポートを使用)
pp select 1
pp always-on on
pppoe use lan2
pp auth accept pap chap
pp auth myname (ISPに接続するID) (ISPに接続するパスワード)
ppp lcp mru on 1454
ppp ipcp ipaddress on
ppp ipcp msext on
ip pp inbound filter list 1001 1002 1003 1004 1005 1006 1007 1099
ip pp nat descriptor 1
ip pp mtu 1454
pp enable 1
ip route default gateway pp 1
NATの設定 nat descriptor type 1 masquerade
DHCPの設定 dhcp service server
dhcp server rfc2131 compliant except remain-silent
dhcp scope 1 192.168.0.2-192.168.0.100/24
DNSの設定 dns server (ISPより指定されたDNSサーバーのIPアドレス)
dns private address spoof on
L2MSの設定 switch control use lan1 on
フィルターの設定 ip inbound filter 1001 reject-nolog * * tcp,udp * 135
ip inbound filter 1002 reject-nolog * * tcp,udp 135 *
ip inbound filter 1003 reject-nolog * * tcp,udp * netbios_ns-netbios_ssn
ip inbound filter 1004 reject-nolog * * tcp,udp netbios_ns-netbios_ssn *
ip inbound filter 1005 reject-nolog * * tcp,udp * 445
ip inbound filter 1006 reject-nolog * * tcp,udp 445 *
ip inbound filter 1007 reject-nolog 192.168.0.0/24 * * * *
ip inbound filter 1099 pass-nolog * * * * *
ip policy interface group 101 name=Private local lan1
ip policy address group 101 name=Private 192.168.0.0/24
ip policy address group 102 name=Any *
ip policy service group 101 name="Open Services"
ip policy service group 102 name=General dns
ip policy service group 103 name=Mail pop3 smtp
ip policy filter 1100 reject-log lan1 * * * *
ip policy filter 1110 pass-log * * * * 161
ip policy filter 1122 static-pass-log * lan1 * * *
ip policy filter 1123 static-pass-log * local * * *
ip policy filter 1124 static-pass-log * * 192.168.0.0/24 * http
ip policy filter 1150 pass-nolog * pp1 * * *
ip policy filter 1500 reject-log pp* * * * *
ip policy filter 1520 pass-log * lan1 * * 101
ip policy filter 1700 pass-log local * * * *
ip policy filter 1710 static-pass-log * lan1 * * *
ip policy filter 3000 reject-log * * * * *
ip policy filter set 101 name="Internet Access" 1100 [1110 1123 [1124] 1122 1150] 1500 [1520] 1700 [1710] 3000
ip policy filter set enable 101
Luaスクリプトのスケジュール設定 schedule at 1 startup * lua (Luaスクリプトファイル名)

[注釈の説明]

注釈1:
モジュールファイルをロードするために使用するパスを設定します。
Lua ではモジュール名の置換記号として "?" を使いますが、ルーターコンソールで '?' を入力するとヘルプが表示されてしまうため、'?' を入力する前にエスケープシーケンスの '\' を入力しています。

require関数の引数と環境変数LUA PATHの設定値についての詳細は こちら

Luaスクリプト例

設定値 -- 転送レート上限
broadcast_rate = 5
multicast_rate = 5
-- 監視対象のスイッチをカンマで区切って列挙する
-- MACアドレスかルーターからスイッチまでの接続ポートの経路情報で指定
-- ("00:a0:de:xx:xx:xx" または "lan1:2-3")
sw_list = {
    "lan1:1"
}
-- パケットストームの監視間隔(1 - 864000 秒)
idle_time = 60
-- パケットストーム連続検出閾値 (1 - 864000)
-- 推奨値は 3
continue_time = 3
-- ブロードキャストストーム検出時のメール送信設定
-- (送る: true / 送らない: false)
mail = false
-- メールの設定
mail_tbl = {
    smtp_address = "(SMTP サーバーのアドレス)",
    from = "(送信元メールアドレス)",
    to = "(宛先メールアドレス)"
}
-- スイッチの状態、メール送信失敗等の情報を出力する SYSLOG のレベル (info, debug, notice)
log_level = "info"
グローバル変数の設定 -- 転送制限が連続した回数
limit_cnt = {0, 0, 0, 0, 0, 0, 0, 0}
-- 転送制限設定とカウンター設定の状態
limit_cfgd = {nil, nil, nil, nil, nil, nil, nil, nil}
-- 転送制限が始まったときのカウンター値
cnt_save = {nil, nil, nil, nil, nil, nil, nil, nil}
ロードするモジュールの指定 require("lua_rtx1200_storm-limit")
スイッチの機種を取得する関数

function switch_model_read(sw)
    local rtn, str
    local cmd = "switch control function get model-name " .. sw

    rtn, str = rt.command(cmd)
    if (not rtn) or (not str) then

        str = string.format("failed to get model name : %s\r\n", sw)
    end
    return rtn, str
end

スイッチの名前を取得する関数

function switch_sys_name_read(sw)
    local rtn, str
    local cmd = "switch control function get system-name " .. sw

    rtn, str = rt.command(cmd)
    if (not rtn) or (not str) then
        str = string.format("failed to get switch system name : %s\r\n", sw)
    end
    return rtn, str
end

スイッチのカウンタータイプを設定する関数

function switch_set_counter(port, cnt_num)
    local cmd = string.format("switch control function set "..
                     "counter-frame-rx-type %d %d "..
                     "broadcast-and-multicast-packets", port, cnt_num)

    return rt.command(cmd)
end

スイッチのカウンターを読みだす関数

function switch_read_frame_counter(sw, rx_tx, port, cnt_num)
    local rtn, val
    local cmd = string.format("switch control function get "..
                     "status-counter-frame-%s %d %d %s",
                     rx_tx, port, cnt_num, sw)

    rtn, val = rt.command(cmd)
    if (not rtn) or (not val) then
        return nil
    end
    return rtn, tonumber(val)
end

受信カウンタータイプをbroadcast-and-multicast-packetsに設定する関数

function set_counter_watch_pkts(sw)
    local rtn, str, port_num, cnt_id
    local cmd = "switch select " .. sw

    rtn = rt.command(cmd)
    if (not rtn) then
        return nil
    end

    rtn, str = switch_model_read(sw)
    if (not rtn) or (not str) then
        return nil
    end

    if (string.find(str, "SWX2200-8G", 1, true)) then
        port_num = 8
        cnt_id = 3
    elseif (string.find(str, "SWX2200-24G", 1, true)) then
        port_num = 24
        cnt_id = 5
    else
        return nil
    end

    for i = 1, port_num do
        rtn = switch_set_counter(i, cnt_id, "broadcast-and-multicast-packets")
        if (not rtn) then
            return nil
        end
    end

    cmd = "switch select none"
    rtn = rt.command(cmd)
    return rtn
end

受信した抑制対象フレーム数を取得する関数

function read_counter_watch_pkts(sw)
    local rtn, str, port_num, cnt_id
    local val = {}

    rtn, str = switch_model_read(sw)

    if (not rtn) or (not str) then
        return nil
    end

    if (string.find(str, "SWX2200-8G", 1, true)) then
        port_num = 8
        cnt_id = 3
    elseif (string.find(str, "SWX2200-24G", 1, true)) then
        port_num = 24
        cnt_id = 5
    else
        return nil
    end

    for i = 1, port_num do
        rtn, val[i] = switch_read_frame_counter(sw, "rx", i, cnt_id)
        if (not rtn) or (not val[i]) then
            return nil
        end
    end
    return rtn, val
end

現在の日時を取得する関数

function time_stamp()
    local t

    t = os.date("*t")
    return string.format("%d/%02d/%02d %02d:%02d:%02d",
        t.year, t.month, t.day, t.hour, t.min, t.sec)
end

カウンターの増加量が最も大きいポートを取得する関数

function get_port_cntinc_max(curr, prev)
    local maxport
    local maxinc = 0

    if (curr == nil) or (prev == nil) then
        return nil
    end
    if (#curr ~= #prev) then
        return nil
    end
    for i, c in ipairs(curr) do
        inc = curr[i] - prev[i]
        if (inc > maxinc) then
            maxinc = inc
            maxport = i
        end
    end
    return maxport
end

パケットストームの状態検出を行う関数

function get_storm_status(idx, sw)
    local rtn, str, storm, limit

    -- SWX2200の転送制限フラグを確認する
    rtn = swx2200.is_storm_stt(sw)
    if (rtn == true) then
        limit = true
        limit_cnt[idx] = limit_cnt[idx] + 1
        -- SWX2200の転送制限フラグをクリアする
        rtn = swx2200.clear_storm_stt(sw)
        if (limit_cnt[idx] >= continue_time) then
            storm = true
        end
    elseif (rtn == false) then
        limit = false
        limit_cnt[idx] = 0
    else
        if (limit_cnt[idx] > 0) then
            limit = true
        else
            limit = false
        end
        str = string.format("failed to check packet storm status : "..
                    "'%s' (Luaスクリプトファイル名)", sw)
        rt.syslog(log_level, str)
    end
    return storm, limit
end

スイッチごとに状態監視を行う関数

function watch_switch(idx, sw)
    local rtn, str, cnt_curr, msg, m_port, storm, limit

    if (not limit_cfgd[idx]) then
        -- SWX2200のパケットストーム制限・検出を有効にする
        limit_cfgd[idx] = swx2200.set_storm_limit(sw, broadcast_rate,
                                   multicast_rate)
        rtn = set_counter_watch_pkts(sw)
        limit_cfgd[idx] = limit_cfgd[idx] and rtn
        if (not limit_cfgd[idx]) then
            str = string.format("failed to set packet storm limit : "..
                        "'%s' (Luaスクリプトファイル名)", sw)
            rt.syslog(log_level, str)
        end
    end

    if (limit_cfgd[idx]) then
        storm, limit = get_storm_status(idx, sw)
    end

    if (limit) then
        rtn, cnt_curr = read_counter_watch_pkts(sw)
        if (rtn) then
            if (cnt_save[idx] == nil) then
                cnt_save[idx] = {}
                for p, c in ipairs(cnt_curr) do
                    cnt_save[idx][p] = c
                end
            end
            m_port = get_port_cntinc_max(cnt_curr, cnt_save[idx])
        end
        str = string.format("broadcast-and-multicast-packets forwarding "..
                    "is limited : %s (Luaスクリプトファイル名)",
                     sw)
        rt.syslog(log_level, str)
    else
        cnt_save[idx] = nil
    end

    if (storm) then
        rtn, str = switch_sys_name_read(sw)
        str = string.split(str, "\r\n")
        msg = string.format("* %s (%s)\r\n", str, sw)
        rtn, str = switch_model_read(sw)
        msg = msg .. string.format(" 機種名 : %s", str)
        str = string.format("packet storm detected : %s "..
                    "(Luaスクリプトファイル名)", sw)
        rt.syslog(log_level, str)
        if (m_port ~= nil) then
            msg = msg .. string.format(" (ポート %d で大量の"..
                        "パケットを受信しました)\r\n",
                         m_port)
            str = string.format("large amounts of broadcast-and-"..
                        "multicast-packets received : "..
                        "%s port %d (Luaスクリプトファイル名)",
                         sw, m_port)
            rt.syslog(log_level, str)
        end
        msg = msg .. "\r\n"
    end
    return storm, limit, msg
end

メインルーチン

local rtn, rtn2, str, storm, limit, msg
if (_RT_LUA_VERSION_NUM < 101) then
    str = "Lua script function version 1.01 or higher is required, terminated. "..
        "(Luaスクリプトファイル名)"
    rt.syslog(log_level, str)
    return
end

while (true) do
    storm = false
    limit = false
    msg = ""
    for i, sw in ipairs(sw_list) do
        rtn, rtn2, str = watch_switch(i, sw)
        if (rtn) and (str) then
            storm = true
            msg = msg .. str
        end
        if (rtn2) then
            limit = true
        end
    end

    if (storm) and (mail) then
        mail_tbl.text = "以下のスイッチでパケットストームを検出しました"..
                "\r\n\r\n" .. msg
        mail_tbl.subject = string.format("packet storm notify (%s)",
                            time_stamp())
        rtn = rt.mail(mail_tbl)
        if (rtn) then
            str = "send packet storm notify mail. " ..
                "(Luaスクリプトファイル名)"
            rt.syslog(log_level, str)
        else
            str = "failed to send mail. (Luaスクリプトファイル名)"
            rt.syslog(log_level, str)
        end
    end

    if (limit) and (not storm) then
        rt.sleep(1)
    else
        rt.sleep(idle_time)
    end
end

【ご注意】
本設定例は、設定の参考例を示したもので、動作を保証するものではございません。
ご利用いただく際には、十分に評価・検証を実施してください。