pmacctを活用したリアルタイムパケットモニタリングとBGP Null Routingの自動化

本文に含まれるIPアドレス、AS番号、BGP Community値は公開用の例示値に変更しています。
実際の運用環境では、ISPと合意済みのPrefix、AS番号、Communityポリシーに基づいて適用する必要があります。

pmacctを活用したリアルタイムパケットモニタリングとBGP Null Routingの自動化

ネットワークを運用していると、単純にトラフィック使用量を見るだけでは不十分なケースが多くあります。
特にDDoS攻撃への対応では、bps基準の帯域使用量だけでなく、pps(Packet Per Second)基準のパケット処理量も必ず確認する必要があります。

この記事では、CentOSサーバーとCisco Catalyst 6506-E環境で運用していたパケットモニタリングおよび自動遮断の構成を整理します。

構成の要点は以下のとおりです。

  • Cisco Catalyst 6506-Eでトラフィックをミラーリング
  • CentOSサーバーでpmacctを利用してIn/Outパケットを収集
  • Pythonデーモンでpmacctの集計データを読み取り、MySQLへ保存
  • Highchartsを利用してWebページ上にリアルタイムグラフを表示
  • プロトコル別のPPS閾値を超過した場合に自動遮断
  • BGP Communityを利用してISP側でNull Routingを実施
  • 遮断されたホストは2時間後に自動解除

全体構成の概要

全体構成は以下のとおりです。

pmacctベースのリアルタイムパケット監視構成

この構成は単なるモニタリングではなく、
検知 → 判断 → 遮断 → 解除 までを自動化することを目的としていました。


運用環境

運用環境は以下のとおりです。

区分内容
OSCentOS
ルーター / スイッチCisco Catalyst 6506-E
パケット収集pmacct
データ処理Python
統計保存MySQL
グラフ表示Highcharts
遮断方式BGP CommunityベースのISP Null Routing
自動解除遮断から2時間後に自動解除

なぜPPS基準のモニタリングが重要だったのか

DDoS攻撃と聞くと、多くの場合は帯域を埋め尽くす大容量攻撃を想像します。
しかし実際の運用環境では、帯域使用量よりもPPSのほうが致命的になるケースが多くありました。

特に2010年代は、L7攻撃よりもL3/L4ベースの攻撃が多く、
その中でも短いパケットを大量に発生させ、ネットワーク機器の処理負荷を高める攻撃が脅威でした。

代表的には以下のような攻撃です。

  • UDP Flood
  • ICMP Flood
  • TCP SYN Flood
  • Fragment Flood
  • Small Packet Flood

このような攻撃はパケットサイズが小さいため、bps基準ではそれほど大きく見えない場合があります。
しかし、秒間パケット数が急増すると、ルーター、ファイアウォール、ロードバランサーの処理負荷は急激に上がります。

つまり、帯域グラフだけを見ていると、以下のような状況を見落とす可能性があります。

トラフィック使用量: 300Mbps
パケット数: 800,000pps

300Mbpsだけを見ると回線には余裕があるように見えるかもしれません。
しかし、800,000ppsはネットワーク機器に大きな負荷を与える可能性があります。

そのため、このシステムではbpsよりもppsを中心に閾値を設定していました。


Cisco Catalyst 6506-Eのミラーリング構成

パケット収集サーバーは、実際のサービス経路には直接挿入しませんでした。
代わりに、Cisco Catalyst 6506-Eでトラフィックをミラーリングし、CentOSサーバーへ転送しました。

この方式には以下のようなメリットがあります。

  • サービストラフィックの経路に影響を与えない
  • モニタリングサーバーの障害がサービス障害に直結しない
  • 収集サーバーの交換やメンテナンスがしやすい
  • 運用中でも分析ポリシーを変更しやすい

設定状態の例は以下のとおりです。

show monitor session all

Session 1
---------
Type                   : Remote Source Session
Source Ports           :
    Both               : Te1/1
Dest RSPAN VLAN        : 10

Egress SPAN Replication State:
Operational mode       : Centralized
Configured mode        : Centralized (default)

上記の意味は以下のとおりです。

項目説明
Source Portsミラーリング対象ポート
Both送信・受信の両方向トラフィックを複製
Dest RSPAN VLANミラーリングされたトラフィックを転送するRSPAN VLAN
Centralized集中型のSPAN複製モード

つまり、Te1/1を通過するトラフィックを複製してRSPAN VLANへ転送し、
CentOSのモニタリングサーバーがそのトラフィックを受信してpmacctで分析する構成です。


pmacctを使用した理由

パケット分析には、tcpdump、nfdump、ntopng、sFlow、NetFlowなど複数の方法があります。
その中でpmacctを使用した理由は以下のとおりです。

  • pcapベースで直接パケットを収集できる
  • 必要なフィールド単位で柔軟に集計できる
  • memory pluginを利用して高速に集計できる
  • In/Outトラフィックを別々の設定として管理できる
  • 宛先IP、宛先ポート、プロトコル単位の統計を作成しやすい

このシステムで必要だったのは、パケットの原文を長期保存することではありません。
どのホストに対して、どのプロトコルのパケットが、どれくらい入ってきているのかをリアルタイムに把握することでした。

そのため、pmacctのaggregate機能が非常に適していました。


pmacct設定例

以下はIn Traffic収集用の設定例です。
IPアドレス帯は実際の運用値ではなく、公開用の例示値に変更しています。

# /usr/local/pmacct/etc/total_in.conf

pcap_interface: ens1f0
daemonize: true

!plugin_buffer_size : 8192
!plugin_pipe_size : 8192000

pmacctd_force_frag_handling: true
networks_file: /usr/local/pmacct/etc/networks.def

plugins: memory[in]

aggregate[in]: dst_host, dst_port, proto

aggregate_filter[in]: dst net 198.51.100.0/24 or \
                      dst net 198.51.101.0/24 or \
                      dst net 203.0.113.0/24 or \
                      dst net 203.0.114.0/24

!imt_path[in]: /tmp/total_in.pipe
!imt_buckets: 65537
!imt_mem_pools_size: 1024000

Out Trafficもほぼ同じ方式で構成しました。
違いは、収集方向とフィルタ条件を運用環境に合わせて調整する程度です。


主要設定の説明

pcap_interface

pcap_interface: ens1f0

パケットを収集するネットワークインターフェースです。
Cisco機器でミラーリングされたトラフィックが、このインターフェースへ入るように構成しました。

daemonize

daemonize: true

pmacctをバックグラウンドデーモンとして実行します。
運用環境では継続的にパケットを収集する必要があるため、デーモン実行が必要でした。

pmacctd_force_frag_handling

pmacctd_force_frag_handling: true

Fragmentパケット処理を強制するオプションです。
DDoS攻撃ではfragmentパケットが混在するケースもあるため、パケット集計の正確性を考慮して有効化しました。

plugins

plugins: memory[in]

収集した統計をmemory pluginに保存します。
リアルタイム分析が目的だったため、ファイル保存よりもメモリベースの集計が適していました。

aggregate

aggregate[in]: dst_host, dst_port, proto

この設定が最も重要です。

収集したパケットを以下の基準で集計します。

フィールド意味
dst_host宛先IP
dst_port宛先ポート
protoプロトコル

つまり、以下のような統計を取得できます。

198.51.100.10 / UDP / 53  / 120,000pps
198.51.100.20 / TCP / 80  / 30,000pps
198.51.100.30 / ICMP / 0  / 15,000pps

このように見ることで、特定ホストがどのプロトコルで攻撃を受けているのかを素早く把握できます。

aggregate_filter

aggregate_filter[in]: dst net 198.51.100.0/24 or \
                      dst net 198.51.101.0/24 or \
                      dst net 203.0.113.0/24 or \
                      dst net 203.0.114.0/24

すべてのトラフィックを収集すると不要なノイズが多くなる可能性があります。
そのため、保護対象のネットワーク帯域だけをフィルタリングしました。

実際の運用では、管理対象の顧客サービス帯域、ホスティング帯域、セキュリティモニタリング対象帯域などを基準に設定しました。


Pythonデーモンによる統計収集

pmacctで集計したデータは、Pythonスクリプトで定期的に読み取りました。
このPythonスクリプトは単発実行ではなく、常にバックグラウンドで動作するデーモンとして運用しました。

Pythonデーモンの役割は以下のとおりです。

  • pmacct集計データの読み取り
  • ホスト別PPSの計算
  • プロトコル別PPSの計算
  • MySQLへの統計保存
  • 閾値超過判定
  • 遮断対象の選定
  • 遮断履歴の管理
  • 遮断から2時間経過した対象の自動解除

構造を簡略化すると以下のようになります。

pmacctデータ処理パイプライン

pmacct


Python Daemon

  ├─ Parse aggregate data
  ├─ Calculate PPS
  ├─ Save to MySQL
  ├─ Check threshold
  └─ Trigger blocking script


Cisco Router / BGP Blackhole

MySQLテーブル構造例

収集データはMySQLに保存しました。
以下は理解しやすくするためのスキーマ例です。

CREATE TABLE traffic_stats (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    direction ENUM('in', 'out') NOT NULL,
    host VARCHAR(45) NOT NULL,
    port INT UNSIGNED NOT NULL DEFAULT 0,
    proto VARCHAR(16) NOT NULL,
    packets BIGINT UNSIGNED NOT NULL DEFAULT 0,
    pps BIGINT UNSIGNED NOT NULL DEFAULT 0,
    collected_at DATETIME NOT NULL,
    PRIMARY KEY (id),
    KEY idx_host_time (host, collected_at),
    KEY idx_proto_time (proto, collected_at),
    KEY idx_direction_time (direction, collected_at)
);

遮断履歴は別テーブルで管理できます。

CREATE TABLE blackhole_events (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    target_ip VARCHAR(45) NOT NULL,
    proto VARCHAR(16) NOT NULL,
    detected_pps BIGINT UNSIGNED NOT NULL,
    status ENUM('active', 'released') NOT NULL DEFAULT 'active',
    blocked_at DATETIME NOT NULL,
    release_at DATETIME NOT NULL,
    released_at DATETIME NULL,
    PRIMARY KEY (id),
    KEY idx_target_status (target_ip, status),
    KEY idx_release_at (release_at)
);

この構成により、以下のような集計クエリを実行できます。

SELECT host, proto, SUM(pps) AS total_pps
FROM traffic_stats
WHERE collected_at >= NOW() - INTERVAL 5 MINUTE
GROUP BY host, proto
ORDER BY total_pps DESC
LIMIT 20;

Highchartsによるリアルタイムグラフ

MySQLに保存された統計は、Webページ上でHighchartsを使って表示しました。

運用画面では主に以下の項目を確認していました。

  • ホスト別PPS推移
  • プロトコル別PPS推移
  • ポート別のトラフィック集中状況
  • In Traffic / Out Trafficの比較
  • 自動遮断イベントの発生履歴

画面構成の例は以下のようなイメージです。

+----------------------------------------------------+
| Host PPS Ranking                                  |
+-----------------+------------+----------+----------+
| Target IP        | Protocol   | Port     | PPS      |
+-----------------+------------+----------+----------+
| 198.51.100.10    | UDP        | 53       | 120,000  |
| 198.51.100.20    | TCP        | 80       | 35,000   |
| 198.51.100.30    | ICMP       | 0        | 20,000   |
+----------------------------------------------------+

+----------------------------------------------------+
| Real-time PPS Graph                                |
|                                                    |
|      ▲                                             |
| PPS  │                         /\                  |
|      │                        /  \                 |
|      │        /\             /    \                |
|      │_______/  \___________/      \_______        |
|                                                    |
+----------------------------------------------------+

Highchartsを使うと、Ajaxで一定間隔ごとにデータを取得し、リアルタイムグラフのように表示できます。

簡単な例は以下のとおりです。

Highcharts.chart('pps-chart', {
  chart: {
    type: 'spline'
  },
  title: {
    text: 'Real-time PPS by Host'
  },
  xAxis: {
    type: 'datetime'
  },
  yAxis: {
    title: {
      text: 'Packets per Second'
    }
  },
  series: [{
    name: '198.51.100.10 UDP/53',
    data: []
  }]
});

実際の運用では、TOP5ホストとプロトコルを同時に確認できるように構成しました。


PPS閾値ベースの自動遮断

このシステムの重要な点は、モニタリングで終わらず、自動遮断までつなげていることです。

基本的な流れは以下のとおりです。

1. pmacctがパケットを集計
2. Pythonデーモンが集計データを取得
3. ホスト別 / プロトコル別のPPSを計算
4. 事前に定義した閾値と比較
5. 閾値を超過した場合、遮断対象として登録
6. ルーターにコマンドを自動投入
7. BGP Communityを利用してISP Null Routingを誘導
8. 2時間後に自動解除

プロトコル別の閾値は個別に設定しました。

例は以下のとおりです。

THRESHOLDS = {
    "TCP": 50000,
    "UDP": 80000,
    "ICMP": 20000,
}

上記の値は説明用の例です。
実際の環境では、回線容量、機器性能、サービス特性、通常時のトラフィックパターンを基準に調整する必要があります。


なぜプロトコル別に閾値を分けたのか

同じPPSであっても、プロトコルによって意味が異なります。

たとえばUDPは、正常なサービスでも瞬間的に高いPPSが発生することがあります。
一方でTCP SYNパケットは、比較的低いPPSでもセッション処理負荷を引き起こす場合があります。

ICMPも障害解析や監視に使われますが、異常に増加すると機器に負荷を与える可能性があります。

そのため、単一の閾値を使うよりも、以下のように分けたほうが現実的です。

プロトコル判断基準
TCPSYN増加、特定ポートへの集中
UDP特定ポートへの集中、DNS/NTP/SSDP系の有無
ICMPEcho Requestの急増
Fragment異常なfragmentの増加

自動遮断スクリプトの構成

自動遮断は、Pythonでpexpectを利用し、ルーターへ直接コマンドを投入する方式で実装しました。

つまり、スクリプトがルーターへ接続し、プロンプトを待ち、
プロンプトが表示されたら次のコマンドを送信する方式です。

例は以下のとおりです。

import pexpect

TIMEOUT = 5

router_ip = "192.0.2.1"
username = "router-user"
password = "router-password"

t = pexpect.spawn(f"ssh {username}@{router_ip}", timeout=TIMEOUT)

t.expect(['Username:', pexpect.EOF, pexpect.TIMEOUT], TIMEOUT)
t.sendline(username)

t.expect(['Password:', pexpect.EOF, pexpect.TIMEOUT], TIMEOUT)
t.sendline(password)

t.expect(['#', '>', pexpect.EOF, pexpect.TIMEOUT], TIMEOUT)
t.sendline('terminal length 0')
t.expect('#', TIMEOUT)

実際の運用では、このような方式でログインした後、
遮断対象IPに対するNull Routeの追加や削除を行いました。


ローカルNull RouteとISP Null Routingの違い

攻撃対象IPが198.51.100.10であると仮定します。

ルーターで以下のように設定すると、そのIP宛のトラフィックはローカルルーターで破棄されます。

ip route 198.51.100.10 255.255.255.255 Null0

しかし、この方式には限界があります。

トラフィックはすでにISPを経由して、自社回線まで到達した後に破棄されるためです。

[Internet]


[ISP]


[Our Circuit]  ← すでに帯域を使用


[Our Router]

    └─ Null0で破棄

つまり、ローカルNull Routeは機器内部の保護には役立ちますが、
回線帯域を保護するという点では限界があります。

そのため、攻撃トラフィックが自社回線へ到達する前に止めるため、
BGP CommunityベースのISP Null Routingを使用しました。


BGP CommunityベースのISP Null Routing

BGP Communityは、BGP経路に付与するタグです。
ISPと事前に合意したCommunity値を付けて特定Prefixを広告すると、ISP側でそのPrefixに対して特定のポリシーを適用できます。

そのポリシーの一つがBlackhole、またはNull Routingです。

たとえば、ISPと以下のようなポリシーが合意されているとします。

Community 65000:666 = Blackhole

この場合、顧客ルーターから特定の/32 PrefixをこのCommunityと一緒にISPへ広告します。

198.51.100.10/32 + Community 65000:666

ISPはこの経路を受信すると、その宛先へ向かうトラフィックをISP網内で破棄します。

結果として、攻撃トラフィックは自社回線まで降りてきません。

BGP CommunityベースのISP Null Routingフロー

[Attacker]


[ISP Network]

    ├─ 198.51.100.10/32 + Blackhole Communityを受信

    └─ ISP内部でトラフィックを破棄


[Our Circuit]

    └─ 攻撃トラフィックの流入を削減

BGP Community適用例

以下は概念説明のためのCisco設定例です。

実際の環境では、必ずISPが提供するBlackhole Community値を使用する必要があります。

1. 遮断対象IPに対するNull Route作成

ip route 198.51.100.10 255.255.255.255 Null0 tag 666

ここでtag 666は、ルーター内部でroute-mapがこの経路を識別するための値です。

2. Static RouteをBGPへ再配布

router bgp 65000
 redistribute static route-map RTBH-EXPORT

static routeをBGPへ広告しますが、すべてのstatic routeを無条件に広告すると危険です。
必ずroute-mapで遮断用routeだけを選別する必要があります。

3. route-mapでtagをマッチし、Communityを付与

route-map RTBH-EXPORT permit 10
 match tag 666
 set community 65000:666 additive

この設定の意味は以下のとおりです。

コマンド意味
match tag 666tag 666が付いたstatic routeだけをマッチ
set community 65000:666 additiveISP Blackhole Communityを追加
additive既存Communityがある場合は維持したまま追加

4. BGP NeighborにCommunity送信を許可

router bgp 65000
 neighbor 203.0.113.1 remote-as 64500
 neighbor 203.0.113.1 send-community

send-community設定がない場合、CommunityがISPへ送信されない可能性があります。
そのため、BGP Communityベースのポリシーを使う場合は必ず確認が必要です。


BGP Null Routingの動作順序

実際の動作フローは以下のとおりです。

1. pmacctが特定ホストのUDP PPS急増を検知
2. Pythonデーモンが閾値超過と判断
3. 遮断対象IPを198.51.100.10として決定
4. PythonスクリプトがCiscoルーターへ接続
5. ルーターに/32 Null Routeを追加

   ip route 198.51.100.10 255.255.255.255 Null0 tag 666

6. BGP redistribute staticポリシーにより/32経路がBGPで広告される
7. route-mapでtag 666をマッチ
8. ISP Blackhole Community 65000:666を付与
9. ISPが該当PrefixをBlackhole処理
10. 攻撃トラフィックがISP網内で破棄される
11. 2時間後にPythonデーモンがNull Routeを削除
12. BGP Withdrawが発生
13. ISP側のBlackhole処理が解除される

自動解除ロジック

遮断は2時間後に自動解除されるように構成しました。

自動解除が必要な理由は以下のとおりです。

  • 攻撃が終了したにもかかわらず遮断が継続する状況を防ぐ
  • 手動解除漏れを防ぐ
  • 運用担当者の負担を減らす
  • 正常サービスの復旧時間を短縮する

自動解除の流れは以下のとおりです。

1. 遮断イベントを保存
2. release_at = blocked_at + 2 hours を設定
3. Pythonデーモンが定期的にrelease_atを確認
4. 時間が経過した項目のNull Routeを削除
5. BGP Withdrawが発生
6. ISP Blackholeが解除される
7. DBの状態をreleasedへ変更

ただし、攻撃が継続している場合は、解除後に再度検知されて再遮断される可能性があります。
この構造により、自動遮断と自動解除を繰り返し動作させることができました。


運用時の注意点

BGP CommunityベースのNull Routingは強力な方法ですが、誤って使用するとサービス障害につながる可能性があります。

1. ISPとの事前合意が必要

Blackhole Community値はISPごとに異なります。
そのため、以下の項目を必ず確認する必要があります。

  • Blackhole Community値
  • /32 Prefix広告の許可有無
  • 顧客保有Prefix範囲の制限
  • Community送信条件
  • BGPセッションポリシー
  • max-prefixポリシー
  • route filteringポリシー

2. send-community設定の確認

ルーター側でCommunityを設定しても、neighborに送信されなければ意味がありません。

neighbor 203.0.113.1 send-community

この設定が抜けていると、ISPはBlackhole Communityを受信できません。

3. Prefix範囲の制限

一般的に、ISPは顧客が保有または委任されたアドレス帯域内のBlackhole Prefixのみを許可します。

たとえば顧客帯域が以下の場合、

198.51.100.0/24

遮断広告は通常、その帯域内の/32に限定されます。

198.51.100.10/32
198.51.100.20/32

外部IPを任意に広告してはいけません。

4. 誤検知の防止

自動遮断システムでは、誤検知が最も危険です。

正常なトラフィックを攻撃と誤判定すると、
自分でサービスIPをBlackholeしてしまう障害が発生する可能性があります。

そのため、以下のような条件を入れることが望ましいです。

  • ホワイトリスト
  • 連続サンプル基準
  • 最小継続時間基準
  • 特定PPS以上 + 特定パケットパターンの同時成立
  • 手動承認モード
  • 重要サービスIPの自動遮断除外
  • 遮断前後のログ保存

5. 大きすぎるPrefixを遮断しない

Blackholeは、一般的に単一ホスト単位である/32基準で使用するのが安全です。

たとえば以下のような遮断は影響範囲が大きくなります。

198.51.100.0/24

サービス全体のアドレス帯が遮断される可能性があるため、自動化では必ず/32単位に制限するのが安全です。


この構成のメリット

この構成を運用していて、特に有用だった点は以下のとおりです。

1. 攻撃対象ホストを素早く確認できる

インターフェース全体のトラフィックだけを見ても、攻撃対象は見えません。
しかし、dst_hostdst_portproto単位で集計すれば、どのホストが攻撃を受けているのかをすぐに確認できます。

2. PPS基準で機器負荷型の攻撃を検知できる

Short packetベースの攻撃は、bps基準では小さく見える場合があります。
PPS基準のモニタリングにより、このような攻撃をより早く検知できました。

3. モニタリングと対応がつながる

グラフを見るだけではなく、閾値を超過した場合に自動遮断までつなげました。
そのため、夜間や担当者がすぐに対応できない状況でも、一次防御が可能でした。

4. ISP区間で遮断し、回線帯域を保護できる

ローカルルーターだけでNull Routeを行うと、トラフィックはすでに回線を使用した後に破棄されます。
一方、BGP CommunityベースのISP Null Routingでは、トラフィックが自社回線に到達する前に遮断できます。

この点が最大のメリットでした。


限界点

もちろん、この方式にも限界があります。

1. pcapベース収集の性能限界

トラフィック量が非常に大きくなると、pcapベースの収集ではパケットドロップが発生する可能性があります。
大規模環境では、NetFlow、sFlow、DDoS専用機器、Scrubbing Centerとの併用も検討する必要があります。

2. 遮断単位が粗い

Null Routingは対象IP全体を破棄する方式です。
そのため、特定サービスポートだけを遮断するような細かい制御とは異なります。

3. 自動化の安定性が重要

ルーターへ直接コマンドを投入する構造のため、
スクリプトエラー、ログイン失敗、プロンプト変更などへの対策が必要です。

4. L7攻撃対策には向いていない

この構成はL3/L4パケットベースの攻撃対応には有効ですが、
HTTP FloodのようなL7攻撃には、別途WAF、CDN、Bot Management、Rate Limitingなどのポリシーが必要です。


まとめ

このシステムは、単なるパケットモニタリングツールではなく、
リアルタイム検知と自動対応を組み合わせた運用自動化構成でした。

流れをまとめると以下のようになります。

Cisco SPAN/RSPAN

pmacct パケット集計

Pythonデーモン分析

MySQL保存

Highcharts可視化

PPS閾値超過判定

ルーター自動制御

BGP CommunityベースのISP Null Routing

2時間後に自動解除

現在はCloudflare Magic Transit、Scrubbing Center、FlowSpec、DDoS専用機器など、より進んだ選択肢が増えています。
しかし、このような構成を自分で運用してみると、ネットワークレベルでDDoS攻撃がどのように見え、どのように遮断されるのかを明確に理解できます。

特に重要なのは、モニタリングそのものではなく、
モニタリング結果を実際の対応につなげる構造です。

パケットを見て、異常を判断し、機器を制御し、ISPと連携して上位区間で遮断する流れは、
ネットワーク運用自動化において非常に重要な経験でした。