ミニPCをネットワークタップ化しYouTubeの通信を可視化する

## 概要
LANポートが2つあるPCを使用してネットワークタップを構築し、ntopng、InfluxDB、Grafanaを組み合わせた監視システムの構築手順を紹介します。試しにYouTubeの通信量から利用時間を可視化してみました。

ダッシュボード上段のように今日の閲覧時間とバイト数（MB/min）を確認できます。

![minipctap-grafana-ntopng.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/ad6b6237-4a89-4aca-b554-fac891bfdcdb.png)

なお、本環境は一人でネットワークを利用しているため、プライバシーに関する問題はありません。

### ネットワークタップとは
ネットワークタップとは、ネットワーク機器間に接続して通過するパケットを監視する装置です。 ネットワークタップ以外にもポートミラーリング機能を持ったスイッチやルータを使うこともできますが、今回はどちらも所有していなかったためPCを使用しました。

### ntopngとは
[ntopng](https://www.ntop.org/products/traffic-analysis/ntop/)は、ntop社がメンテナンスしているトラフィック収集、可視化、分析ができるソフトウェアです。インストールすることでパケットキャプチャした上でWebUIから通信状況を確認できます。 今回はWebUIを使わずntopngからInfluxDBに格納した上でInfluxDBをデータソースにGrafanaから確認しています。
ライセンスにコミュニティ版もあり無料で利用可能です。

ntopngにはnDPIというライブラリが含まれており、これによってアプリケーションレベルでの通信内容の分析もできます。今回はこちらの機能でYouTubeの閲覧時間を計測してみました（ちなみにこんなことをしなくてもYouTubeのアプリを使えば視聴動画を元にした閲覧時間を確認できます）。

### PCについて
PCはCHUWI LarkBox Xを使っています。
- イーサネットインターフェースが2つ付いています
- Wi-Fiも利用可能なので、GrafanaのHTTPサーバはWi-Fi経由で接続します
- Ubuntu 24.04.2 LTSをインストールしています

### 構成図
ONUとWi-Fiルーターの間にタップとしてPCを設置し、キャプチャした内容を無線LAN経由でPCから確認します。 この構成にすることで、Wi-Fiルータに接続しているすべての機器の通信をキャプチャできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/4beb732f-1f4a-46ec-8e45-c2e027b06617.png)

## 構築
Wi-Fiのネットワーク設定～SSH接続までは省略します。
### ネットワーク設定

Netplanの設定ファイルを作成します（Wi-Fiの設定は別ファイルで管理しています）。
ルータとONU間に設置するため、IPv6を有効にする必要があります。
```bash
sudo vi  /etc/netplan/60-ethernet-bridge.yaml
```
設定内容は以下のとおりです。IPアドレスを無効にしてブリッジとして接続します。
インターフェース名は ip a で確認しておきます。
```
network:
  version: 2
  renderer: networkd
  ethernets:
    enp1s0:
      dhcp4: false
      dhcp6: false
    enp2s0:
      dhcp4: false
      dhcp6: false
  bridges:
    br0:
      interfaces: [enp1s0, enp2s0]
      dhcp4: false
      dhcp6: false
      accept-ra: true
      parameters:
        forward-delay: 0
        stp: false
```
Netplanを適用します。
```bash
sudo netplan apply
```
### カーネルパラメータの調整
カーネル設定を変更します。
```bash
sudo vi /etc/sysctl.conf
```
以下の設定を追加します。1Gbpsのため128MBに設定しました。
```
net.core.rmem_default = 4194304 # 4MB
net.core.rmem_max = 134217728   # 128MB
net.core.wmem_default = 4194304 # 4MB
net.core.wmem_max = 134217728   # 128MB
net.core.netdev_max_backlog = 10000 
```
設定を反映します。
```
sudo sysctl -p
```
### ntopngのインストール
以下のページを参考にします。
https://packages.ntop.org/apt-stable/

```bash
# リポジトリの追加
sudo apt-get install software-properties-common wget
sudo add-apt-repository universe
wget https://packages.ntop.org/apt-stable/24.04/all/apt-ntop-stable.deb
sudo apt install ./apt-ntop-stable.deb

# インストール
sudo apt-get clean all
sudo apt-get update
sudo apt-get install pfring-dkms nprobe ntopng n2disk cento
```
起動と起動設定を追加します（すでに両方有効でした）。
```bash
sudo systemctl start ntopng
sudo systemctl enable ntopng

sudo systemctl status ntopng
sudo systemctl is-enabled ntopng
```

### InfluxDBのインストール
以下のページを参考にします。2025年5月現在で安定版のv2を利用します。
https://docs.influxdata.com/influxdb/v2/install/

```bash
# リポジトリの追加
curl --silent --location -O \
https://repos.influxdata.com/influxdata-archive.key
echo "943666881a1b8d9b849b74caebf02d3465d6beb716510d86a39f6c8e8dac7515  influxdata-archive.key" \
| sha256sum --check - && cat influxdata-archive.key \
| gpg --dearmor \
| sudo tee /etc/apt/trusted.gpg.d/influxdata-archive.gpg > /dev/null \
&& echo 'deb [signed-by=/etc/apt/trusted.gpg.d/influxdata-archive.gpg] https://repos.influxdata.com/debian stable main' \
| sudo tee /etc/apt/sources.list.d/influxdata.list
sudo apt-get update

# インストール
sudo apt-get install influxdb2
```
起動と起動設定を追加します。
```bash
sudo systemctl start influxdb
sudo systemctl enable influxdb

sudo systemctl status influxdb
sudo systemctl is-enabled influxdb
```
ブラウザで[IPアドレス:8086]でアクセスし、ユーザーをセットアップします。

### ntopngとInfluxDBを接続する
以下のページを参考にします。
https://www.ntop.org/ntop/ntopng-influxdb-and-grafana-a-step-by-step-guide-to-create-dashboards/
https://www.ntop.org/ntop/influxdb-v2-support-in-ntopng-is-now-partially-available/

ntopngはInfluxDB v2には完全には対応していませんが、v1互換のAPIを使えば連携できます。
InfluxDBのバケットとAPIトークンを作成後、以下のコマンドでv1互換のユーザーを作成します。
BUCKET_IDはバケットの一覧から取得できます（名前ではありません）。
InfluxDBは[IPアドレス:8086]でアクセスできます。

```bash
influx v1 auth create --read-bucket BUCKET_ID --write-bucket BUCKET_ID --username USERNAME_FOR_NTOPNG --password USERNAME_FOR_NTOPNG --org YOUR_ORGANIZATION --token REST_API_TOKEN_PROVIDED_BY_INFLUX
```

ntopngのWebUIからInfluxDBへの接続をします。

- 日本語設定ではサイドバーの「設定」→「設定」→「時系列」からアクセスできました
- 時系列ドライバーを「InfluxDB 1.x/2.x」に設定します
- InfluxDB URLは http://localhost:8086 のままにしました
- InfluxDBデータベースをバケット名のntopngにしました
- InfluxDB認証を有効にし、上記コマンドで作成したユーザー名とパスワードを設定しました
- 時系列の解像度を1mにしました
- タイムシリーズ/統計データの保持は60日にしました

![minipctap-ntopnp-influxdb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359375/24765124-fc28-412c-8255-a04ea526163b.png)

### Grafanaのインストール
以下のページを参考にします。
https://grafana.com/docs/grafana/latest/setup-grafana/installation/debian/

```bash
# リポジトリの追加(software-properties-commonとwgetはすでにインストール済み)
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt-get update

# インストール
sudo apt-get install grafana

```
設定ファイルを修正します。
```bash
sudo vi /etc/grafana/grafana.ini
```
ポートがntopngと被るので変更します。ユーザー名とパスワードも設定しておきます。
```
http_port = 4000

admin_user = admin
admin_password = XXXXX
```
起動と起動設定を追加します。
```
sudo systemctl start grafana-server
sudo systemctl enable grafana-server

sudo systemctl status grafana-server
sudo systemctl is-enabled grafana-server
```
InfluxDBと接続します。
- ConnectionsからData sourcesでInfluxDBを追加します
- Query languageをFluxに設定します
- HTTPのURLを http://localhost:8086 に設定します
- 認証情報でOrganization、APIトークン、デフォルトバケットを指定しました

## 動作確認
ここまででntopng、InfluxDB、Grafanaの設定ができたので、タップとしてLANケーブルを挿してGrafanaから確認してみます。

YouTubeの通信量を確認するクエリです。iface:ndpiからYouTubeの通信のみフィルタし分毎に集計しています。
```
from(bucket: "ntopng")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "iface:ndpi")
  |> filter(fn: (r) => r["_field"] == "bytes")
  |> filter(fn: (r) => r["ifid"] == "2")
  |> filter(fn: (r) => r["protocol"] == "YouTube")
  |> aggregateWindow(every: 1m, fn: sum, createEmpty: false)
  |> derivative(unit: 1m, nonNegative: true)
  |> map(fn: (r) => ({ r with _value : r._value / 1000000.0  }))
```

今日のYouTube利用時間を確認するクエリです。
ここでは1分ごとの通信量が700KB以上であるときに利用中であるとして判定するようにしました。

```
import "date"
import "timezone"

option location = timezone.location(name: "Asia/Tokyo")

// 日本時間の今日の開始時刻を取得
today_start = date.truncate(t: now(), unit: 1d, location: timezone.location(name: "Asia/Tokyo"))

from(bucket: "ntopng")
  |> range(start: today_start)
  |> filter(fn: (r) => r["_measurement"] == "iface:ndpi")
  |> filter(fn: (r) => r["_field"] == "bytes")
  |> filter(fn: (r) => r["ifid"] == "2")
  |> filter(fn: (r) => r["protocol"] == "YouTube")
  |> aggregateWindow(every: 1m, fn: sum, createEmpty: false)
  |> derivative(unit: 1m, nonNegative: true)
  |> map(fn: (r) => ({ r with 
    is_active: if r._value / 1000.0 > 700.0 then 1.0 else 0.0
  }))
  |> filter(fn: (r) => r.is_active == 1.0)
  |> count()
```

これらクエリにより画面上部の画像のようなダッシュボードが作成できました。

## おわり
通信量を元に時間を測定しているので実際の閲覧時間とどの程度違いがあるかは今後確認します。
せっかくGrafanaのダッシュボードで表示するようにしたので、一定時間以上視聴すると通知するなども試してみたいと思います。