Synology DS923+にAQC113C搭載の互換10GbEカードを導入してみた
今回の記事はChatGPTに生成してもらって私が味付けして書いてます(挨拶)
技術系記事で公開の意義はあると思うけど、正直理解してない部分もかなりあるというやつです。文体もできる限り直してますが、ご容赦ください。
DS923+はSynologyのNASで、ハイアマチュアというかプロシューマーモデルになります。SOHO(死語?)向けだと思います。
私はSynologyの監視カメラと連携して自宅前の画像を24時間態勢で保存したり、写真データのバックアップ先として活用しています。
DS923+ですが、10Gbps用の拡張カード(E10G22-T1-Mini)がオプションで売られています。見ての通り結構高額で流通数も少なめ、かつ他の方の記事を参照してもらえばわかりますが、互換カードが出ていて、そちらのほうがチップのモデルが新しくて発熱量が小さいそうです。送料込みで13000円くらいでした。今回生まれて初めてアリエク使いましたが問題なく届きました。
ただ、届いた外箱はE10G22-T1-Miniのパッケージで、開封済み内袋の中に互換品が入っている、という状態で届いて、古き良き中国の気配がしました。
今回はPCとNASを10GbEで直結して使うことにします。家庭内LANは1GbE接続です。
PC側はこのカードを買っていれました。LANケーブルはDS923+付属のもの(CAT5Eらしい)を使いましたが、問題なさそうです。
ちなみにNAS側のカードの取り付けでまず引っかかりました。拡張カードを入れるスロットのネジはP2サイズのドライバーじゃないと外せません。あとはそのまま差し込むだけ。簡単。
↓こっからチャッピー
結果から書くと、私の環境ではプラグアンドプレイではありませんでした。
まぁそれは想定内で、検索すると出てくるスクリプトを回せば動きそうだ、という当てはあったのですが、スクリプトの中身が不安だったのでチャッピーに聞いてみるとセキュリティリスクがゼロではないとのご回答、というわけでチャッピーに聞いた通りの手順で回していくと認識しました。具体的には以下の通り。
DSMに標準搭載されているatlantic_v2ドライバへPCI IDを手動登録すると、10Gbps Full Duplexで通信できました。
ただし、atlantic_v2へバインドしたままDSMを再起動すると、次回起動後にAQC113CがPCIeデバイス一覧から消える。
ドライバが認識しないのではない。lspciにも出てこなくなる。
条件を変えて確認したところ、再起動前にatlantic_v2から明示的にunbindすれば、この現象を回避できた。
注意
以下はSynologyの正式サポート外の構成であり、root権限の操作を含む。DSM更新で動かなくなる可能性がある。バックアップを確保し、管理用の1GbE接続を残した状態で作業すること。
目次?
使用環境
NAS側
- Synology DS923+
- DSM 7系
- Linuxカーネル:4.4.302+
- DSMビルド:#90080
- 互換10GbEカード:A77P-10Gbe
- コントローラー:AQC113C
- PCI ID:
1d6a:14c0
PC側
- Windows
- Realtek RTL8127搭載10GbE NIC
- PCとNASをRJ45で直結
- 既存の1GbE接続は通常ネットワークおよび管理用として維持
作業環境
SSHクライアント(puttyとか、WindowsPowerShellでもなんでも良い)を用いて(1GbEネットワークで)DS923+にSSH接続してください。ID,passは普段DSMにログインするものでできます。
もともとのユーザーの権限次第ですが、sudo -iでroot権限取れちゃうはずです。DS923+側で操作するコマンドはすべてrootでやってます。
DSM側でSSH接続接続を許可しておくことを忘れないように。
直結用ネットワーク
DS923+ LAN 3:10.10.10.1/30
Windows 10GbE:10.10.10.2/30
デフォルトゲートウェイ:双方とも空欄
MTU:1500
カードは見えるが、ネットワークインターフェースが出ない
カードを装着してDS923+を起動すると、PCIeデバイスとしては認識された。
lspci -nn | grep -i '1d6a:14c0'
出力:
01:00.0 Class [0200]: Device [1d6a:14c0] (rev 03)
一方、ドライバはバインドされていなかった。
lspci -k -s 01:00.0
出力には、次の行がない。
Kernel driver in use:
sysfsにもdriverリンクが存在しなかった。
ls -l /sys/bus/pci/devices/0000:01:00.0/driver
DSM側にはatlanticとatlantic_v2の両モジュールがロードされていた。
lsmod | grep atlantic
そこで、DSM標準のatlantic_v2へAQC113CのPCI IDを追加した。
echo "1d6a 14c0" > /sys/bus/pci/drivers/atlantic_v2/new_id
new_idは、ロード済みのPCIドライバへVendor IDとDevice IDを追加し、該当する未バインドデバイスをprobeさせるLinuxの標準的なsysfsインターフェースである。
直後、カードはatlantic_v2へバインドされた。
lspci -k -s 01:00.0
Kernel driver in use: atlantic_v2
さらにeth2が生成された。
ip link show eth2
リンク速度も正常だった。
ethtool eth2
主要部分:
Speed: 10000Mb/s
Duplex: Full
Link detected: yes
LAN 3へ固定IPを設定する
ドライバをバインドすると、DSMのネットワーク画面にもLAN 3として表示された。
途中、手動で設定した10.10.10.1が消え、169.254.x.xのリンクローカルアドレスへ戻ることがあった。これは物理リンクの異常ではなく、DSMがLAN 3をDHCPまたはリンクローカル設定として扱っていたためだった。
DSMの以下の画面から固定IPへ変更した。
コントロールパネル
→ ネットワーク
→ ネットワークインターフェース
→ LAN 3
→ 編集
NAS側:
IPアドレス:10.10.10.1
サブネットマスク:255.255.255.252
デフォルトゲートウェイ:空欄
DNS:空欄
MTU:1500
Windows側:
IPアドレス:10.10.10.2
サブネットマスク:255.255.255.252
デフォルトゲートウェイ:空欄
DNS:空欄
Windowsからのpingは正常に通った。
ping 10.10.10.1
Reply from 10.10.10.1: bytes=32 time<1ms TTL=64
SMBが本当に10GbE側を通っているか確認する
SMB共有にはNAS名や1GbE側IPではなく、10GbE側IPを指定して接続した。
\\10.10.10.1\共有フォルダ名
Windows上で使用中のSMB経路を確認する。
Get-NetTCPConnection -State Established -RemotePort 445 |
Select-Object LocalAddress, RemoteAddress
今回の出力:
LocalAddress RemoteAddress
------------ -------------
10.10.10.2 10.10.10.1
SMBは間違いなく10GbE側を通っていた。
転送速度はHDDで頭打ちになった
10Gbpsでリンクしたからといって、HDD上の共有フォルダが1GB/sで転送できるわけではない。
今回の実測値は概ね次のとおりだった。
- PCからNAS、50GB単一ファイル:約100~125MB/s
- NASからPC、50GB単一ファイル:約150MB/s
- RAW画像を多数転送:約60MB/s
DSMのリソースモニターでは、転送先ストレージプールのHDD使用率が80~100%に張り付いていた。一方、10GbE側の通信量には余裕があり、メモリ(16GBの互換カード追加で20GBに増設済み)も不足していなかった。
監視カメラの録画先は別ストレージプールに分離している。録画を停止しても傾向は変わらず、今回の主な律速は対象ストレージプールのHDDと判断した。
単一ファイルよりRAW画像群の方が遅いのは、ファイルごとに作成、属性更新、ディレクトリ更新などが発生するためと考えられる。
転送開始直後だけ一時的に速度が高くなったのは、Windows側とNAS側のファイルキャッシュの影響と考えられる。DSM上では20GBのメモリのうち約16GBがキャッシュ済みとして利用されていた。キャッシュが吸収できる間はHDDの持続書き込み速度を超えて見えるが、長時間転送では最終的にHDD速度へ収束する。
最大の罠――バインドしたまま再起動するとカードが消える
起動時にnew_idを書けば運用できると思ったが、ウォームリブートで問題が発生した。
atlantic_v2へバインドした状態でDSMを再起動すると、次回起動後のlspciからAQC113C自体が消えた。
lspci -nn | grep -i '1d6a:14c0'
何も返らない。
これは単なるドライバ未認識ではない。OSから見てPCIeデバイスそのものが存在しない状態なので、起動後にnew_idを書いても復旧しない。ACアダプターを抜いた完全なコールドブートが必要だった(※一応DSMから電源OFF→スイッチONでも認識はしてます。)。
条件を変えて切り分けた
| 再起動前の状態 | 再起動後 |
|---|---|
| AQC113Cをドライバへバインドしない | lspciに残る |
atlantic_v2へバインドしたまま再起動 |
lspciから消える |
再起動前にatlantic_v2からunbind |
lspciに残る |
したがって、カード自体が無条件にウォームリブートへ失敗するわけではない。
少なくとも今回の環境では、atlantic_v2へバインドした状態でシステムを終了することが発生条件だった。
内部原因までは特定できていない。
- DSM付属
atlantic_v2のshutdown処理 - AQC113Cのファームウェア
- PCIeの電源状態またはリセット処理
- DS923+側のドライバ停止順序
これらのいずれか、または組み合わせと考えている。
再起動前にunbindすれば回避できた
手動では、次の手順で問題を回避できた。
まず、AQC113CのPCIアドレスをVendor IDとDevice IDから検索する。
BDF="$(
for dev in /sys/bus/pci/devices/*; do
[ "$(cat "$dev/vendor" 2>/dev/null)" = "0x1d6a" ] || continue
[ "$(cat "$dev/device" 2>/dev/null)" = "0x14c0" ] || continue
basename "$dev"
break
done
)"
echo "$BDF"
出力例:
0000:01:00.0
ネットワークインターフェースを停止し、ドライバから切り離す。
ip link set eth2 down
echo "$BDF" > /sys/bus/pci/drivers/atlantic_v2/unbind
この状態ではカード自体はlspciに残るが、Kernel driver in use: atlantic_v2は消える。
その後DSMを再起動すると、再起動後もAQC113CはPCIeデバイスとして列挙された。
起動時bindスクリプト
PCIアドレスは固定せず、Vendor IDとDevice IDから毎回検索する。
保存先例:
/volume1/test/_admin_scripts/aqc113-bind.sh
#!/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
DRIVER="/sys/bus/pci/drivers/atlantic_v2"
VENDOR_ID="0x1d6a"
DEVICE_ID="0x14c0"
NEW_ID="1d6a 14c0"
STATIC_IP="10.10.10.1/30"
find_device()
{
for dev in /sys/bus/pci/devices/*; do
[ -r "$dev/vendor" ] || continue
[ -r "$dev/device" ] || continue
[ "$(cat "$dev/vendor" 2>/dev/null)" = "$VENDOR_ID" ] || continue
[ "$(cat "$dev/device" 2>/dev/null)" = "$DEVICE_ID" ] || continue
echo "$dev"
return 0
done
return 1
}
echo "===== $(date '+%Y-%m-%d %H:%M:%S') ====="
echo "Starting AQC113 binding"
i=0
DEVICE_PATH=""
while [ "$i" -lt 60 ]; do
DEVICE_PATH="$(find_device)"
if [ -n "$DEVICE_PATH" ] && [ -e "$DRIVER/new_id" ] && [ -e "$DRIVER/bind" ]; then
break
fi
sleep 1
i=$((i + 1))
done
if [ -z "$DEVICE_PATH" ]; then
echo "ERROR: AQC113 1d6a:14c0 was not found"
exit 1
fi
if [ ! -e "$DRIVER/new_id" ]; then
echo "ERROR: atlantic_v2 new_id was not found"
exit 1
fi
BDF="${DEVICE_PATH##*/}"
echo "PCI address: $BDF"
if [ -L "$DEVICE_PATH/driver" ]; then
BOUND_DRIVER="$(basename "$(readlink "$DEVICE_PATH/driver")")"
if [ "$BOUND_DRIVER" != "atlantic_v2" ]; then
echo "ERROR: Unexpected driver: $BOUND_DRIVER"
exit 1
fi
else
echo "Registering PCI ID $NEW_ID"
printf '%s\n' "$NEW_ID" > "$DRIVER/new_id" 2>/dev/null || true
i=0
while [ ! -L "$DEVICE_PATH/driver" ] && [ "$i" -lt 15 ]; do
sleep 1
i=$((i + 1))
done
if [ ! -L "$DEVICE_PATH/driver" ]; then
echo "Trying explicit bind"
printf '%s\n' "$BDF" > "$DRIVER/bind"
fi
fi
if [ ! -L "$DEVICE_PATH/driver" ]; then
echo "ERROR: Driver binding failed"
exit 1
fi
BOUND_DRIVER="$(basename "$(readlink "$DEVICE_PATH/driver")")"
echo "Bound driver: $BOUND_DRIVER"
if [ "$BOUND_DRIVER" != "atlantic_v2" ]; then
echo "ERROR: Unexpected bound driver: $BOUND_DRIVER"
exit 1
fi
i=0
while [ ! -d "$DEVICE_PATH/net" ] && [ "$i" -lt 60 ]; do
sleep 1
i=$((i + 1))
done
NETDEV=""
for netpath in "$DEVICE_PATH"/net/*; do
[ -e "$netpath" ] || continue
NETDEV="${netpath##*/}"
break
done
if [ -z "$NETDEV" ]; then
echo "ERROR: Network interface was not created"
exit 1
fi
echo "Network interface: $NETDEV"
ip link set dev "$NETDEV" up
sleep 15
if ! ip -4 -o addr show dev "$NETDEV" |
grep -q '10\.10\.10\.1/30'; then
echo "Applying fallback address $STATIC_IP"
ip -4 addr flush dev "$NETDEV" scope global
ip addr add "$STATIC_IP" dev "$NETDEV"
fi
ip addr show dev "$NETDEV"
ethtool "$NETDEV" 2>/dev/null |
grep -E 'Speed:|Duplex:|Link detected:' || true
echo "AQC113 initialization completed"
exit 0
実行権限を付与する。
chmod 700 /volume2/test/_admin_scripts/aqc113-bind.sh
DSMの起動タスク
コントロールパネル
→ タスク スケジューラ
→ 作成
→ トリガーされたタスク
→ ユーザー定義のスクリプト
- タスク名:AQC113 Bind
- ユーザー:root
- イベント:ブートアップ
- 有効:オン
ユーザー定義スクリプト:
/bin/sleep 120
/bin/sh /volume1/test/_admin_scripts/aqc113-bind.sh >> /volume2/test/_admin_scripts/aqc113-bind.log 2>&1
起動直後はDSMのネットワーク処理と競合したため、120秒の待ち時間を入れている。(※バグ取りの過程で入れてるsleepなので、なくても良いと思う(未検証))
終了時unbindスクリプト
保存先例:
/volume2/test/_admin_scripts/aqc113-unbind.sh
#!/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
DRIVER="/sys/bus/pci/drivers/atlantic_v2"
VENDOR_ID="0x1d6a"
DEVICE_ID="0x14c0"
find_device()
{
for dev in /sys/bus/pci/devices/*; do
[ -r "$dev/vendor" ] || continue
[ -r "$dev/device" ] || continue
[ "$(cat "$dev/vendor" 2>/dev/null)" = "$VENDOR_ID" ] || continue
[ "$(cat "$dev/device" 2>/dev/null)" = "$DEVICE_ID" ] || continue
echo "$dev"
return 0
done
return 1
}
echo "===== $(date '+%Y-%m-%d %H:%M:%S') ====="
echo "Starting AQC113 unbind"
DEVICE_PATH="$(find_device)"
if [ -z "$DEVICE_PATH" ]; then
echo "NOTICE: AQC113 is not present"
exit 0
fi
BDF="${DEVICE_PATH##*/}"
echo "PCI address: $BDF"
if [ ! -L "$DEVICE_PATH/driver" ]; then
echo "AQC113 is already unbound"
exit 0
fi
BOUND_DRIVER="$(basename "$(readlink "$DEVICE_PATH/driver")")"
echo "Current driver: $BOUND_DRIVER"
if [ "$BOUND_DRIVER" != "atlantic_v2" ]; then
echo "ERROR: Refusing to unbind unexpected driver: $BOUND_DRIVER"
exit 1
fi
for netpath in "$DEVICE_PATH"/net/*; do
[ -e "$netpath" ] || continue
NETDEV="${netpath##*/}"
echo "Setting $NETDEV down"
ip link set dev "$NETDEV" down 2>/dev/null || true
done
sync
if ! printf '%s\n' "$BDF" > "$DRIVER/unbind"; then
echo "ERROR: Failed to unbind $BDF"
exit 1
fi
sleep 1
if [ -L "$DEVICE_PATH/driver" ]; then
echo "ERROR: Driver is still bound"
exit 1
fi
echo "Successfully unbound AQC113 at $BDF"
exit 0
実行権限を付与する。
chmod 700 /volume2/test/_admin_scripts/aqc113-unbind.sh
DSMのシャットダウンタスク
- タスク名:AQC113 Unbind
- ユーザー:root
- イベント:シャットダウン
- 有効:オン
ユーザー定義スクリプト:
/bin/sh /volume2/test/_admin_scripts/aqc113-unbind.sh >> /volume2/test/_admin_scripts/aqc113-unbind.log 2>&1
シャットダウンおよび再起動前にこの処理が実行され、atlantic_v2からカードを切り離す。
自動化の確認方法
ウォームリブート後、まず終了ログを確認する。
tail -50 /volume2/test/_admin_scripts/aqc113-unbind.log
期待する出力:
Successfully unbound AQC113 at 0000:01:00.0
続いて起動ログを確認する。
tail -50 /volume2/test/_admin_scripts/aqc113-bind.log
期待する出力:
Bound driver: atlantic_v2
Network interface: eth2
AQC113 initialization completed
最終状態を確認する。
lspci -nnk | grep -A4 -i '1d6a:14c0'
ip addr show eth2
ethtool eth2 | grep -E 'Speed:|Duplex:|Link detected:'
iperfでネットワーク単体を測定する
ファイルコピーではHDDが律速になるため、NIC、ドライバ、ケーブル、TCP処理だけを確認するにはiperfを使う。
ここでもいくつか罠があった。
iperf 2とiperf3には互換性がない
NAS側には次のiperfが入っていた。
iperf version 2.1.8 (12 August 2022) pthreads
当初、Windows側のiperf3と接続したところ、次のエラーになった。
warning: JSON data length overflow - 33554688 bytes JSON size is not allowed
iperf3: error - unable to receive parameters from client
iperf 2とiperf3は通信プロトコルが異なり、相互接続できない。両側をiperf 2へ統一した。
5001番ポートが使用中だった
iperf 2の既定ポート5001でサーバーを起動しようとすると、次のエラーが出た。
listen failed: Address already in use
そこで、測定には5002番ポートを使用した。
PCからNAS方向
NAS側をサーバーにする。
iperf -s -B 10.10.10.1 -p 5002 -i 1
Windows側をクライアントにする。
iperf.exe -c 10.10.10.1 -B 10.10.10.2 -p 5002 -t 30 -i 1 -P 4
NASからPC方向
iperf 2.1.8の--reverseを試したところ、NAS側で次のエラーが発生した。
*** buffer overflow detected ***: terminated
Aborted (core dumped)
このため、逆方向測定では--reverseを使わず、サーバーとクライアントを手動で入れ替えた。
Windows側をサーバーにする。
iperf.exe -s -B 10.10.10.2 -p 5002 -i 1
NAS側をクライアントにする。
iperf -c 10.10.10.2 -B 10.10.10.1 -p 5002 -t 30 -i 1 -P 4
Windows側をサーバーにする場合は、Windows Defenderファイアウォールでiperfの受信を許可する必要がある場合がある。
iperf実測値: (NAS→PC)0.00-30.03 sec 32.9 GBytes 9.41 Gbits/sec、(PC→NAS)0.00-30.02 sec 33.2 GBytes 9.49 Gbits/sec
外部ドライバ導入は見送った
AQC113C向けの外部atlantic.koをダウンロードし、起動時にロードするスクリプトも見つかった。
しかし、そのスクリプトはroot権限で外部サイトからBusyBox、シェルスクリプト、カーネルモジュールを取得し、一部のHTTPS接続では証明書検証を無効化していた。
目的とコードの大筋には整合性があったものの、実際に実行されるファイルを十分に監査できなかったため採用しなかった。
今回の環境では、DSM標準のatlantic_v2だけで10GbE通信まで確認できた。外部カーネルモジュールを導入するより、bindとunbindのライフサイクルを補う方を選んだ。
自作SPK化も断念した
起動時bindと終了時unbindをパッケージのライフサイクルに載せる方が構造としては自然である。
そこでroot権限で動作する自作SPKも作成したが、DSM 7では署名されていないroot権限パッケージのインストールが拒否された。
DSMのセキュリティ制約を迂回してまで導入するのは適切ではないため、DSMが標準で提供しているrootのユーザー定義タスクを使用した。
この運用の限界
DSMから正常に再起動またはシャットダウンした場合は、終了タスクでunbindできる。
一方、次のケースでは終了タスクが実行されない。
- 停電
- 電源コードを抜いた場合
- カーネルパニック
- 強制電源断
- DSMの終了処理自体が停止した場合
その場合、次回起動時にAQC113Cがlspciから消える可能性がある。復旧には、DS923+を停止し、ACアダプターを抜いて完全に電源を落とした後、再起動する必要がある。
したがって、1GbE管理経路は残しておくべきである。
現時点での評価
| 項目 | 結果 |
|---|---|
| PCIeデバイスとして認識 | する |
DSM標準atlantic_v2で通信 |
できる |
| 10Gbps Full Duplex | 確認 |
| ドライバ不要・挿すだけ | 不可 |
| バインドしたままウォームリブート | カードが消える |
| 事前にunbindしてウォームリブート | カードが残る |
| 停電・強制終了時 | 回避処理は動かない |
この互換カードは、純正品と同じ安定性とは言えない。
それでも、DSM標準ドライバだけで10GbEリンクが成立し、再起動時の発生条件と回避方法まで切り分けられた。
ファイル転送ではHDDが先に限界へ達した。RAW画像群では約60MB/sまで低下し、10GbE化だけで写真整理が劇的に速くなるわけではなさそうです。CrystalDiskMarkみたいなので検証したほうが良いのかもしれないですが。
純正カードとの価格差は、部品代だけの差ではない。
挿して終わること、再起動を意識しなくてよいこと、DSM更新時の保守対象を増やさないことにも価格が付いている。
今回、互換カードは動いた。
しかし、挿して終わりではなかった。
以上チャッピー
1年以上やろうかなぁ、まぁええか……って思いながらスルーしてたことができたので、夏の工作としては上等だったと思います。結果は伴わず。出費は2万円。
高速化について有識者のお話があったら情報ください。基本的には撮影したRAWファイル爆速転送したいとかそのくらいなので。
というわけで、今日はこの辺で。