ごあいさつ

ご訪問、ありがとうございます。
私は茨城県取手市取手駅東口徒歩0分の場所にある取手総合法律事務所で弁護士をしております。

顧問先:大手損害保険会社、伊藤忠商事・住友化学関連会社

法律相談のお問い合わせは
0297−72−8090
までお願いいたします。
事務所概要
〒302-0004 茨城県取手市取手2丁目2番3号 TRDビル2階
取手総合法律事務所
TEL 0297(72)8090 FAX 050(3745)0085

主な受任事件

  • 交通事故のみとなっています。

なお、交通事故加害側のご相談は受けておりませんので、ご注意ください。

仕事を行う地域は、下記のようになっております。

  • 茨城県県南(取手・守谷・龍ヶ崎(竜ヶ崎)・土浦・つくば・常総・下妻)
  • 茨城県央(水戸)
  • 千葉北部(我孫子・柏・松戸)
  • 千葉県央(千葉・船橋・市川)
  • 東京都内

平成28年度:茨城県弁護士会土浦支部支部長
平成30年度:茨城県弁護士会副会長および関東弁護士会連合会理事
現職:関東弁護士会連合会 消費者委員会委員、取手市役所 政治倫理審査会委員、龍ケ崎市役所 入札監視委員会委員、茨城県弁護士会 消費者委員会副委員長・事務局運営室室員・弁護士会照会調査室室員
詳細な経歴は,以下のリンク先をご確認下さい。
www.facebook.com

法律事務所の留守番電話を自前サーバー内のプログラムに置き換えた

作ったもの

営業時間外にかかってきた電話を、事業者の留守番電話ではなく自前サーバー内のプログラムで受けるように変更しました。応答メッセージを流して用件を録音し、内容を書き起こして、翌営業日の朝に事務所へまとめて通知します。

実装はClaude Codeに任せ、私は方針を決めたり、実機で電話をかけて動作を確かめたりしました。

なぜ作ろうと思ったか

これまでの留守番電話は、電話サービスの事業者のサーバーに録音を保管する仕組みでした。

アナウンスが固定で「取手総合法律事務所」という名前を流せないため、依頼者の方は電話をかけた先が正しいかどうか判断できません。

また、留守番電話が入っているかを毎朝確認する必要があり、入っていない日でも確認しなければそれが分かりません。

確認の手間を減らすため、録音があるときだけ通知するプログラムと再生用のプログラムを一度作りました。ただ、やり方としては遠回りで、アナウンスの問題も残っていました。

当事務所では、業務の一部を事務所内で自作したシステムで処理しています。電話も複数台の内線で代表番号を使う仕組みなので、留守番電話も自前で作り、事務所のサーバーで処理できるのではないかと考えました。

電話の音声を事務所サーバーに引き込む

当事務所の電話はクラウドPBXのサービスで、スマートフォンのアプリで発着信しています。このアプリが標準的なSIPクライアントなら、同じ認証情報で自分のプログラムを「もう一台の内線電話」として登録できます。契約は10内線で、使っているのは7台なので、追加料金なしで1本を回せます。

登録までには3つの問題を解決しました。

まず、契約書類にあったサーバーアドレスはゲートウェイ機器用で、端末が登録する宛先は別でした。

次に、宛先を直しても応答がありませんでした。事業者のサーバーがベンダー純正以外のUser-Agentからの要求を無視していたためで、アプリと同じ名前を名乗らせるとすぐに応答が返りました。「無応答」は「届いていない」とは限らず、「届いているが返事をしてもらえない」こともあります。

最後に、ユーザー名には内線番号だけでなく、テナントコードと連結した文字列が必要でした。

これで登録が通り、別の内線から呼び出すと着信も届きました。

Asteriskのような既製のPBXソフトも検討しましたが、コンテナの再構築が必要になります。用途は1内線で着信を受けて処理するだけなので、SIPと音声パケット(RTP)の処理はPythonで自前で書きました。

アナウンスを固定の音声にした理由

当初は、時間外の電話にAIが会話で応答する方式を考えていました。

ただし試したところ、アナウンス中にかけてきた方が発話するとAI側が発話を中断するなど、留守番電話としては問題のある挙動が見られました。また大きな問題として、AIが内容まで踏み込んで回答するようなシステムだと、電話によるプロンプトインジェクションの問題が発生します。そのため、留守番電話は固定のメッセージを流して録音するだけで十分だと判断し、取手総合法律事務所が入った固定の音声を流す形にしました。

アナウンスは音声合成で一度だけ作り、ファイルとして保存しています。「取手」を誤読されないよう最初は読み仮名で書かせたところ、イントネーションがおかしくなりました。漢字のみ、読み方の指示付き、ひらがな、カタカナの4パターンを聞き比べ、指示を付けない漢字のみのものを採用しました。合成した音声は、人が聞いて確かめてから使っています。文面は約15秒に縮め、発信音も追加しました。

実機テストで出た不具合

いったん完成したプログラムを試験稼働させて実機でテストすると、いくつか不具合が出ました。

1つ目はアナウンスのノイズです。

音声合成の24kHzの出力を電話用の8kHzに落とす際、ローパスフィルタを掛けずに間引いていたため、高い音が折り返して雑音になっていました。フィルタを実装して解消しました。

2つ目は、こちらの応答(200 OK)が相手に届かない問題です。

SIPのソケットをconnected UDPで開いていたため、登録先と別のポートから届く着信に応答できていませんでした。Claudeはテストを書く段階でこの制約に気づいていましたが、ソケットの開き方を直さず、制約のほうに実装を合わせていました。

3つ目は、アナウンスの冒頭が切れる問題です。

相手側の準備が整うのを待たずに再生を始めていたので、相手から最初の音声パケットが届いてから再生するように直しました。

4つ目は、発信音の直後に通話が切れる問題です。

6秒無音が続いたら録音を終える条件にしていたところ、アナウンス中の15秒も無音として数えていました。

ここで私から、「そもそも6秒無音で切断する仕様は適切なのか。一般的に、留守電は留守電側で切ることはないのではないか」と指摘しました。留守番電話は本来、相手が切るまで録音し、上限時間で打ち切るものです。調べ直すと、相手が電話を切っても録音が止まらない欠陥も見つかりました。6秒で切れていたために目立っていなかっただけでした。

5つ目は、無音では切らないように直した直後、通話のあとに電話がつながらなくなる問題です。

着信の処理が終わるまで受信処理全体を止める書き方になっていて、その間は登録の更新も切断の通知も処理できず、内線の登録が切れていました。

この症状は、最初の通話テストのログにも出ていました。通話が終われば回復していたので、Claudeは「通話中だけの現象で、実害は小さい」と報告して先に進めていました。当時は6秒の無音タイムアウトが通話を終わらせるたびに回復していただけで、その保護を外した途端、回復しなくなりました。影響範囲をきちんと見積もっていれば、「内線1本の登録が切れる」ことが「事務所の電話が使えなくなる」ことに直結すると分かったはずです。

すべて直したあとの実機テストでは、登録の更新失敗はなく、相手が切った時点で録音が確定し、内容も聞き取れました。

なお、自前のサーバーはPCMU以外のコーデックを処理できませんが、モバイル回線から発信しても事業者側でPCMUに変換して届くことを確認でき、追加の実装は不要でした。

テストについて考えたこと

296件のテストが全部通っていても、実機では不具合が出ました。その中には、外部機器の振る舞いや実際の使われ方についての前提の誤りが含まれていました。connected UDPが接続先以外のパケットを捨てること、事業者のサーバーが登録先と別のポートから着信を送ってくること、アナウンス中も相手からパケットが届き続けることなどです。

書いたコードの中だけを見ているテストでは、これらは検出できません。テストが確かめていたのは「Claudeの理解が実装と一致していること」であって、「その理解が現実と一致していること」ではありませんでした。電話のように人が直接使うものでは、実地のテストを繰り返して確かめることが必要でしょう。

別のセッションによるレビュー

実機で動くことを確認したあと、機能、テスト品質、セキュリティ、性能、運用の5つの観点でレビューを行いました。実装したのと同じセッションで見直させると自己弁護が入るので、別のセッションでレビュー役を立ち上げ、コードだけを読ませています。新規にコードを生成したときや大規模な機能を追加したときは、このレビューを実行するようにしており、意外とここで問題が見つかります。

今回見つかった問題には、次のようなものがありました。

・前の通話の切断通知が遅れて届くと、進行中の通話が切断を検出できなくなり、録音が上限の5分まで続く
・音声パケットを送る20ミリ秒ごとに名前解決が走り、1回に15.6ミリ秒かかっていた。その間は着信も切断も処理できない
・失敗時に通知する設定が、ログの置き場所がないと本体ごと実行されない
・事務所の電話を2回止めたのに、登録切れを私に知らせる仕組みがなかった
・4つ目の不具合の再発防止テストは、テスト用の部品が待ち時間を無視していたため、関数が呼ばれたかどうかしか確かめていなかった

登録切れについては、更新が続けて失敗したら通知し、復帰したらそれも通知する監視を追加しました。

特定の内線番号を留守電として使うための設定

このシステムは、内線番号319を使用しています。
日中は、外線番号はグループID1の内線に接続するように設定しており、内線番号319にはグループ番号9を割り当てています。こうすると、外線からはグループ番号9の内線319には接続がされません。

そして設定した時刻になったら、外線は全て内線319に転送されるように設定しています。これで外線は留守番電話システムに転送され、外部からは営業時間外は留守番電話になっているように見えます。

普通の光電話でもできるか

当事務所の電話はクラウドPBXですが、外線の回線そのものはNTTのひかり電話です。ひかり電話も、内部ではSIPで動くIP電話です。ただし、NTTの網とやり取りするための認証情報はHGWの中にあり、利用者には開示されていません。そのため、今回のように事業者のサーバーへ直接登録することはできません。

代わりに使えるのがHGWの内線機能です。HGWにはIP電話機を内線として登録する機能があります。留守番電話のプログラムをそこへ内線として登録すれば、外線の着信を受けられるはずです。当事務所のゲートウェイ機器がしていることと同じです。

当事務所では試していないので、次の点は確かめる必要があります。

・時間帯で着信先を切り替える機能は、私の知る限りHGWにはありません。プログラムを常に他の電話と一緒に鳴らしておき、時間外だけ応答させるといった工夫が要ります。
・NTTの留守番電話サービスを契約していると、そちらが先に応答します。
・ビジネスフォンの主装置で外線を受けている事務所では、主装置が他社製のSIP端末を内線として受け付けるかどうか次第です。

現在の状況とまとめ

時間外の留守番応答は、実装と実機検証を終え、すでに稼働しています。

ここまで作れたのは、モデルもSDKもすでに揃っていたからです。
一方で、プロトコルの仕様、機器ごとの癖、ネットワークの都合といった実世界とつなぐ部分は、モデルが賢くなっても自動的には解決しません。実機で動かし、不具合を出し、直すしかありません。

QNAP NAS(QTS 5.2.10)でUbuntu 26.04のコンテナをビルドする

前回の記事「QNAP NAS(QTS 5.2.10)でUbuntu 24.04のコンテナをビルドする」では、QNAPのカーネル(5.10.60-qnap)がsyscall番号451〜455に独自のsyscallを割り当てているため、glibc 2.39以上とsystemd 255以上がfchmodat2(452)でfallbackできずに落ちる問題と、その回避策を書きました。

www.toridelaw.com

前回は「26.04も同じ構造だが未検証」としていたので、Ubuntu 26.04(resolute)で実際にビルドしました。24.04の回避策はそのまま使えましたが、24.04では起きなかった原因でビルドが2回止まり、通った後もCanonのプリンタだけ紙が出ない問題が残っていました。その対処をメモしておきます。

環境

QTS 5.2.10、Container Station 3.1.2.1742、Docker Engine 27.1.2-qnap8、カーネル5.10.60-qnap、ホストのシェルはbash 3.2.57です。移行元はubuntu:22.04で、24.04用に作ったDockerfileを起点にしました。

24.04から26.04で、glibcは2.39から2.43、systemdは255から259、CUPSは2.4.7から2.4.16、cups-filtersは1.28から2.0.1に上がります。Pythonはmainリポジトリに3.14しかなく、coreutilsはbase imageではuutils(Rust実装)になっています。

ビルド前の確認

glibc 2.43のfchmodat.cは2.39と著作権表示の年以外は同じで、systemd 259もfallbackの条件はENOSYSとEPERMのままでした。そのため、seccompプロファイルとsysusersのユーザー事前作成は引き続き使えるはずです。

base imageの中身は、Docker Hubのタグをpullできなくても、Canonicalが公開しているOCI rootfs(partner-images.canonical.com/oci/resolute/current/)をtarで開けば確認できます。26.04にもubuntu:x:1000:1000が入っていて、gid 999と998は空いていました。

aptの依存解決は、resoluteのindexを空のdpkg statusに当ててapt-get -s installを実行すれば、dockerなしでシミュレーションできます。548パッケージが解決し、t64付きのパッケージ名は24.04と同じでした。Python 3.12はdeadsnakes PPAにresolute用が公開されていたので、まずはPPAでビルドを通すことにしました。

1回目:dbusのpostinstがsysusersで落ちる

Setting up dbus-system-bus-common (1.16.2-2ubuntu4) ...
Failed to copy permissions from /etc/group to /etc/.#group24081a56ddeecad4: No such file or directory

前回systemdで見たのと同じエラーです。26.04のdbus-system-bus-common 1.16.2は、messagebusユーザーをsystemd-sysusersで作る方式に変わっていました。24.04の1.14.10はadduserを使うので問題が出なかったわけです。前のリリースで問題のなかったパッケージが、次のリリースでsysusersを使う方式に切り替わることがあります。

同じ処理を通るパッケージをビルドのたびに1つずつ見つけていくと往復が増えるので、UbuntuのContents-amd64.gzでusr/lib/sysusers.d/*.confを持つパッケージを抜き出し、インストールする548件と突き合わせました。

for s in resolute resolute-updates resolute-security; do
  curl -fsSL -o "contents_$s.gz" "http://archive.ubuntu.com/ubuntu/dists/$s/Contents-amd64.gz"
done
zcat contents_resolute*.gz | grep -E '^usr/lib/sysusers\.d/[^[:space:]]+\.conf:space:' \
  | awk '{print $NF}' | tr ',' '\n' | sed 's#.*/##' | sort -u > sysusers_pkgs.txt
comm -12 sysusers_pkgs.txt install_set.txt      # install_set.txt = apt-get -s install の Inst 行

対処済みのsystemdとcron-daemon-commonのほかに、dbus-system-bus-common(messagebus)、polkitd(polkitd)、openssh-server(sshd。主グループはnogroup)が見つかりました。それぞれのsysusers.dの定義(ホームディレクトリ、シェル、グループ)に合わせて、aptの前に置く事前作成のレイヤを次のようにしました。

RUN groupadd -g 999 systemd-journal && \
    groupadd -g 998 systemd-network && \
    useradd -u 998 -g 998 -r -M -s /usr/sbin/nologin systemd-network && \
    groupadd -r crontab && \
    groupadd -r messagebus && useradd -r -g messagebus -M -d /nonexistent -s /usr/sbin/nologin messagebus && \
    groupadd -r polkitd && useradd -r -g polkitd -M -d / -s /usr/sbin/nologin polkitd && \
    useradd -r -g nogroup -M -d /run/sshd -s /usr/sbin/nologin sshd

uidとgidはsysusersも動的に選ぶので、-rに任せて構いません。|| true は付けず、衝突した場合はビルドが止まって理由がログに残るようにしています。

2回目:/run/sshdが既にある

mkdir: /var/run/sshd: File exists

22.04と24.04ではなかった/run/sshdが、26.04ではビルドの途中で既にできていました。どこで作られたのかは特定できませんでしたが、mkdir -p にすれば通ります。

3回目:通過

全25レイヤが通り、seccompプロファイルを付けたスモークテストでも、rsync -aなどがmodeを保持し、syscall(452)が38(ENOSYS)を返すことを確認しました。base imageはuutilsですが、aptの依存でgnu-coreutilsが入り、最終イメージのcpはGNU版に戻っていました。どちらが入るかはインストールするパッケージ次第なので、cp --versionで確認しておくと迷いません。

Canonだけ紙が出なかった

試験用のイメージでcupsdを起動し、本番と同じ設定で3台に1ページずつ送りました。Brother HL-L6400DWは紙が出て、Brother MFC-L6900DWのPC-FAXも受信完了まで確認できました。Canon LBP851CはCUPS上はcompletedでしたが、紙は出ていませんでした。

原因は、Canonのinstall.shが依存パッケージを古い名前で探し、見つからなくてもexit 0で終わることでした。ビルドは止まらず、26.04にないlibxml2.so.2をCanonのライブラリが必要としたまま残っていました。印刷時にフィルタがライブラリの読み込みに失敗して0バイトを出力し、バックエンドはプリンタに接続しないまま終わり、ジョブはcompletedになります。error_logには次の行が出ていましたが、当時はerr = 0という字面から無害と判断していました。

E [Job 2] src = libcanon_pdlwrapper.c, line = 514, err = 0

この流れは、検証用のcupsdで再現して確かめています。Dockerfileでは次の3点を直しました。いずれも失敗をビルドの時点で止めるための変更です。

・Canonのdebを、install.sh経由ではなく apt-get install ./cnrdrvcups-lipslx_*.deb で入れる。依存関係を解決できなければ、aptが0以外で終了して止まる
・Canonが想定しているlibxml2 2.9.14系のdeb(pluckyのlibxml2_2.12.7+dfsg+really2.9.14)を同梱し、同じapt-get installで並べてインストールする。so.16へのシンボリックリンクでも読み込みは通るが、2.9と2.15のABIの違いが残るので採用しない
・BrotherとCanonの各パッケージの全ファイルと、/usr/lib/cups/filter、/usr/lib/cups/backendのELFをlddで調べ、未解決が1件でもあればビルドを失敗させる。対象をdpkgのファイル一覧から取るので、/usr/libに置かれたライブラリも漏れない

再ビルド後はCanonから紙が出ています。

CUPSのcompletedは、バックエンドにデータを渡し終えたというところまでの意味で、0バイトのジョブでもcompletedになります。印刷できたかどうかは、送ったバイト数(LogLevel debugのWrote N bytes)か実際の紙で判断します。

ホストのbashは3.2

ホストで実行したテスト用スクリプトが構文エラーで止まりました。QTSのbashは3.2.57で、コマンド置換の中にヒアドキュメントを置くと中身まで構文解析してしまいます。ヒアドキュメントを変数に入れるだけなら、readで読み込めば解析されません。

IFS= read -r -d '' INNER <<'INNER_EOF' || true
...
INNER_EOF
docker run --rm ... --entrypoint /bin/bash "$IMAGE" -c "$INNER"

コンテナ内のbash 5.1で bash -n を通しても、ホストで動く保証にはなりません。;&、;;&、|&、連想配列、${var,,} なども3.2にはないので、ホストで動かす.shではこれらを使わないでおくのが無難です。

Pythonは3.14に上げる

deadsnakesの3.12で通りましたが、本番のPythonを第三者のPPAに頼る形になり、3.12のサポートは2028年10月に終わる予定です。lockファイルの146パッケージを調べたところ、3.14で使えないのは使っていなかったmojimojiだけでした。システムのPythonに直接入れる案は、PEP 668でpipが拒否されるうえ、aptで入る同名のパッケージと衝突するので採りません。3.14に上げてvenvを維持しました。

22.04からのDockerfileの変更点

1. FROM ubuntu:26.04 に変更
2. libmagic1をlibmagic1t64に変更(24.04と同じ)
3. sysusersが作るユーザーの事前作成。24.04の4行にmessagebus、polkitd、sshdを追加
4. gcloudの展開をGNU tarから python3 -m tarfile に変更(24.04と同じ)
5. userdel -r ubuntu を追加(24.04と同じ。base imageのuid 1000)
6. mkdir /var/run/sshd を mkdir -p に変更(26.04のみ)
7. Canonのドライバの導入を、install.sh経由から apt-get install ./cnrdrvcups-lipslx_*.deb に変更(移行後に追加)
8. pluckyのlibxml2 2.9.14系のdebを同梱し、7と同じapt-get installで並べてインストール(移行後に追加)
9. BrotherとCanonの各パッケージの全ファイルと、/usr/lib/cups/filter、/usr/lib/cups/backendのELFをlddで調べ、未解決が1件でもあればビルドを止める検査を追加(移行後に追加)

実行時は、docker run --security-opt seccomp= に24.04と同じプロファイルを指定しています。

QNAP NAS(QTS 5.2.10)でUbuntu 24.04のコンテナをビルドする

QNAP NAS(QTS 5.2.10)でUbuntu 24.04のコンテナをビルドするとsystemdとrsyncが落ちる問題と回避策

事務所の業務用スクリプトは、QNAP NASのContainer Station上で、自前でビルドしたUbuntu 22.04ベースのコンテナで動かしています。

このコンテナを24.04(noble)に上げようとしたところ、docker buildがsystemdのpostinstで止まりました。ビルドを通しても、今度は実行時にrsync -aが落ちます。

原因を調べていくと、DockerfileでもaptでもなくQNAPのカーネルにありました。QNAPのカーネルは、upstreamのsyscall番号451〜455に独自のsyscallを割り当てていたのです。

切り分けの経緯と、最終的に採った回避策(ビルド側の2点と、実行時のseccompプロファイル)をメモしておきます。

環境

QTS:5.2.10.3577
Container Station:3.1.2.1742
Docker Engine:27.1.2-qnap8(containerd 1.7.20、runc 1.1.13)
カーネル:5.10.60-qnap
移行元と移行先:ubuntu:22.04 から ubuntu:24.04

コンテナはsupervisordで常駐デーモンをまとめて動かす構成で、systemdは起動していません。それでもcron、cups、libreofficeの依存関係でsystemdパッケージはインストールされます。

症状

ビルド時にsystemdのpostinstがexit 1で終わる

fchmod() of /etc/credstore failed: No such file or directory
Creating group 'systemd-journal' with GID 999.
Creating user 'systemd-network' (systemd Network Management) with UID 998 and GID 998.
Failed to copy permissions from /etc/group to /etc/.#group3602cef3edff7bbf: No such file or directory
dpkg: error processing package systemd (--configure):
 installed systemd package post-installation script subprocess returned error exit status 1

docker run --rm ubuntu:24.04 で apt-get install systemd を実行するだけで再現します。22.04では通ります。

ビルド時にgcloud SDKのtar展開で「Cannot change mode」が出る

tar: google-cloud-sdk/platform/bundledpythonunix/bin/python: Cannot change mode to rwxrwxrwx: Bad address

失敗するのはシンボリックリンク(1052本)とディレクトリだけで、通常のファイルは展開できています。

実行時にrsync -aとPythonのos.chmod(follow_symlinks=False)が落ちる

rsync: [generator] failed to set permissions on "/t2/d": Bad address (14)
rsync error: some files/attrs were not transferred (see previous errors) (code 23)

OSError: [Errno 14] Bad address: '/s/d/f'

cp -aとinstall -mは問題なく動きます。22.04ではすべて通ります。

最初に疑ったもの

最初に3つの仮説を立てましたが、いずれも実際に測って否定しました。

1つ目はseccompとAppArmorです。--security-opt seccomp=unconfined とし、AppArmorもunconfinedにしても同じように失敗しました。後で説明するとおり、unconfinedにしても結果は変わらず、既定プロファイルの防御が外れるだけです。

2つ目は、古いDockerでglibc 2.38以降が全く動かなくなることで知られるclone3のEPERM問題です。コンテナ内で測るとclone3はENOSYSを返し、glibcはcloneへのfallbackができていました。Docker 27の既定プロファイルはclone3をerrnoRet: 38でENOSYSにする設計なので、この問題が起きる条件がそろいません。

3つ目はaptの依存解決とi386のindexです。必要なパッケージはすべてnobleのindexに同じ名前で存在していました。違いはlibmagic1からlibmagic1t64への改名だけで、これもProvidesで解決されます。i386のindexも公開されています。失敗していたのは依存解決の段階ではなく、dpkgの設定段階でした。

原因はsyscall 451〜455の番号の衝突

コンテナ内からsyscall(2)で番号を直接呼ぶと、次の結果になりました。Dockerの既定seccompプロファイルの下で測った値です。左から、番号、upstream(x86_64)での名前、upstreamで追加された版、既定seccomp下での応答の順です。

445〜449 landlock_*、memfd_secret、process_mrelease、futex_waitv(5.13〜5.16) ENOSYS(正常。5.10には無い)
450 set_mempolicy_home_node(5.17) EPERM(seccompの既定拒否。カーネルの応答は未測定)
451 cachestat(6.5) EFAULT
452 fchmodat2(6.6) EFAULT(AT_EMPTY_PATH形ではENOENT)
453 map_shadow_stack(6.6) EBADF
454 futex_wake(6.7) EBADF
455 futex_wait(6.7) EINVAL
456〜 futex_requeue以降(6.7〜) ENOSYS(正常)

450のEPERMはカーネルではなくseccompの応答です。moby v27.1.2の既定プロファイルでは、set_mempolicy_home_nodeはCAP_SYS_NICEを持つ場合にだけ許可されます。既定のdocker runにはこのcapabilityが無いので、既定の拒否(defaultErrnoRet: 1)でEPERMが返ります。450についてカーネル自身がどう応答するかは測っていません。

451〜455の5つは既定プロファイルで無条件に許可されているので、上の値はカーネルの応答です。

5.10のカーネルなら、450以降はすべてENOSYSを返すはずです。QNAPは5.10に独自syscallを追加する際、当時upstreamでまだ使われていなかった番号を使ったと見られ、少なくとも451〜455がそれに当たります。カーネルが新しい番号を知っているかのように応答するので、呼び出し側は未実装だと判断できません。

問題になるのは452番のfchmodat2です。

systemd 255(src/basic/fs-util.c のfchmod_opath())は、O_PATHで開いたfdのmodeを変えるとき、まず fchmodat2(fd, "", m, AT_EMPTY_PATH) を呼びます。/proc/self/fd/N 経由のchmodへfallbackするのは、errnoがENOSYSかEPERMのときだけです。このカーネルではENOENTが返るので、systemdは本当のエラーとして扱います。その結果、sysusersが/etc/groupを書けず、postinstが落ちます。

glibc 2.39(sysdeps/unix/sysv/linux/fchmodat.c)も、fchmodat(…, AT_SYMLINK_NOFOLLOW) ではまずfchmodat2を呼び、ENOSYSのときだけO_PATHと/procを使う方法へfallbackします。lchmod()はこの経路を呼ぶだけの薄いラッパです。

この関数を使っているのが、rsyncの-p(-aに含まれます)です。rsyncはlchmodを呼びます。GNU tar 1.35も、シンボリックリンクと、展開の最後にまとめて行うディレクトリのmode設定でfchmodat(AT_SYMLINK_NOFOLLOW)を使います。Pythonのos.chmod(follow_symlinks=False)も同じ関数です。

22.04のglibc 2.35とsystemd 249はfchmodat2を呼ばないので、問題が出ませんでした。つまり、この問題は24.04固有ではなくカーネル固有です。glibc 2.39以上とsystemd 255以上を含む26.04にも同じ経路があります(26.04は未検証です)。

手元で確かめる方法

docker run --rm ubuntu:24.04 bash -c 'apt-get update -qq && apt-get install -y -qq --no-install-recommends python3 >/dev/null 2>&1; python3 -c "
import ctypes
l = ctypes.CDLL(None, use_errno=True)
for n in range(445, 458):
    l.syscall(n, -100, b\"\", 0, 0); print(n, ctypes.get_errno())

既定のseccompプロファイルの下では、素の5.10でも450は1(EPERM)になり、それ以外の445以降は38(ENOSYS)になります。QTS 5.2.10では、451〜455に14、2、9、9、22が並びます。

452は空のパスを渡しているので2(ENOENT)です。実在するパスを渡すと14(EFAULT)になり、いずれにしても38にはなりません。この記事の環境での実測結果は次のとおりです。

445 38 446 38 447 38 448 38 449 38 450 1 451 14 452 2 453 9 454 9 455 22 456 38 457 38

回避策

実行時とビルド時で手段を分けたのは、docker build(BuildKit)ではRUNの段階でseccompプロファイルを差し替えられないためです。--security=insecure を使うとunconfinedになりますが、452はもともと既定プロファイルの下でもカーネルまで届いているので、結果は変わりません。外れるのは既定プロファイルの防御だけです。

実行時はseccompプロファイルで450〜455をENOSYSに見せる

Dockerの既定プロファイル(moby v27.1.2の profiles/seccomp/default.json)をもとに、6つのsyscall名をallowリストから外し、SCMP_ACT_ERRNOとerrnoRet: 38のルールを1つ追加します。既定プロファイル自身もclone3を同じ形式でENOSYSにしているので、Docker側でも使われている書き方です。

VER=27.1.2   # docker version --format '{{.Server.Version}}' の先頭に合わせる
T=$(mktemp) && curl -fsSL -o "$T" "https://raw.githubusercontent.com/moby/moby/v${VER}/profiles/seccomp/default.json" && \
python3 - "$T" << 'PY'
import json, sys
NAMES = ["set_mempolicy_home_node", "cachestat", "fchmodat2", "map_shadow_stack", "futex_wake", "futex_wait"]
p = json.load(open(sys.argv[1]))
for r in p["syscalls"]:
    r["names"] = [n for n in r["names"] if n not in NAMES]
p["syscalls"].append({"names": NAMES, "action": "SCMP_ACT_ERRNO", "errnoRet": 38,
    "comment": "QNAP kernel 5.10.60-qnap assigns vendor syscalls to 450-455; report ENOSYS as an upstream 5.10 kernel would"})
with open("seccomp-qnap-kernel510.json", "w") as f:
    json.dump(p, f, indent="\t"); f.write("\n")
PY

docker run --rm --security-opt seccomp=$PWD/seccomp-qnap-kernel510.json ubuntu:24.04 bash -c '...'

これでrsync -aも、Pythonのfollow_symlinks=Falseも通るようになりました。実測では4項目ともrc=0で、modeも保持されていました。

451〜455については、upstreamの5.10が本来返す値に戻すだけなので、将来QNAPがカーネルを更新して本物の451〜455を持つようになっても害はありません。ENOSYSが返れば、呼び出し側がfallbackを選ぶだけです。

6つの中には450(set_mempolicy_home_node)も入れています。450はもともと既定プロファイルの下でもカーネルに届いていないので、このプロファイルで変わるのはerrnoの値(1から38)だけです。残しても害はありません。

allowを全開にしてdenylistだけを書く小さなプロファイルにすれば、Dockerのバージョンに合わせて作り直す必要はなくなります。ただ、その場合は既定プロファイルが塞いでいるmount、kexec_load、bpfなどへの防御を捨てることになります。そのため、既定プロファイルをコピーして6つの名前だけを変える方法にしました。Container Stationの更新でDockerのバージョンが変わったら、同じバージョンの既定プロファイルから作り直します。

ビルド時の対策(1)sysusersが作るユーザーを先に作っておく

systemd-sysusersは、作るべきユーザーやグループがすでに存在していれば/etc/groupを書き換えません。そのため、失敗する経路を通らずに済みます。

nobleのsystemdパッケージはsystemd-journal(グループ)とsystemd-network(ユーザーとグループ)を作り、cron-daemon-commonはcrontab(グループ)を作ります。そこで、aptの前に次のレイヤを1つ追加します。

RUN groupadd -g 999 systemd-journal && \
    groupadd -g 998 systemd-network && \
    useradd -u 998 -g 998 -r -M -s /usr/sbin/nologin systemd-network && \
    groupadd -r crontab

|| true は付けていません。衝突した場合は0以外で止まり、ビルドログに理由が残るからです。

なお、systemd-tmpfiles --create は || true 付きで実行され、失敗しても止まりません。そのため、tmpfiles.dが指定するmodeのうちmkdirの既定と違うもの(/etc/credstoreの700など)は、適用されないまま残ります。cronはpostinstが自分でchmod 1730するので問題ありません。

ビルド時の対策(2)tarの展開をPython標準のtarfileに替える

GNU tar 1.35は、シンボリックリンクと、最後にまとめて行うディレクトリのmode設定でfchmodat(AT_SYMLINK_NOFOLLOW)を呼びます。標準ライブラリのtarfileはシンボリックリンクをchmodせず、ファイルとディレクトリにはflag 0のchmodを使うので、仕組み上この経路を通りません。

RUN curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz && \
    python3.12 -m tarfile --filter=fully_trusted -e google-cloud-cli-linux-x86_64.tar.gz / && \
    rm google-cloud-cli-linux-x86_64.tar.gz

展開結果は、GNU tarで展開したものと型、mode、所有者、シンボリックリンク先がすべて一致しました(37,190エントリを22.04で比較)。

--filter=fully_trusted は、GNU tarと同じく何も加工せずに展開する指定で、Python 3.12の既定と同じ動作です。警告を消すために明示しています。

noble固有の注意点(base imageのubuntuユーザー)

これはカーネルとは関係ありませんが、24.04のbase imageには ubuntu:x:1000:1000 が最初から入っています。NASのファイル所有権に合わせてuid 1000でユーザーを作る構成では、useraddが「UID 1000 is not unique」で失敗します。先に userdel -r ubuntu しておきます。

結果

上記の対策を入れたdocker buildは、すべてのレイヤを通過しました(約5分)。

--security-opt seccomp= を付けたスモークテストでは、rsync -aでmodeが保持されること、follow_symlinks=Falseが成功すること、syscall(452)が38を返すことを確認しています。BrotherとCanonのプリンタドライバ(i386の.debと、LIPSLXのinstaller)も、24.04上で導入まで通りました。

まとめ

QTS 5.2.10のカーネル(5.10.60-qnap)は、syscall 451〜455に独自のsyscallを載せていて、upstreamのfchmodat2(452)がそこに当たります。glibc 2.39以上とsystemd 255以上はfchmodat2を先に呼び、想定外のerrnoが返るとfallbackしません。22.04が無事で24.04が落ちるのはそのためで、26.04も同じ構造です(未検証)。

実行時は、docker runのseccompプロファイルで450〜455をENOSYSに見せれば直ります。既定プロファイルをコピーして6つの名前を変えるだけで済みます。

ビルド時は、BuildKitがプロファイルを差し替えられないので、sysusersのユーザーを事前に作ることと、tarをtarfileに替えることでしのいでいます。

seccomp=unconfinedは解決策になりません。452は既定プロファイルの下でもカーネルまで届いているので、unconfinedにしても結果は変わらず、既定プロファイルの防御が外れるだけです。

Google DriveとNASを双方向同期する方法とチューニング手法

弁護士事務所のNASとGoogle Driveをrcloneで双方向同期させた話

rcloneのチューニング、それも bisync と Google Drive の組み合わせについて、日本語で実測値を載せている記事がほとんど見当たりません。私の事務所のファイルは、書面などの小さいファイルが毎日大量に増えていく一方で、証拠のスキャンデータになると1ファイルが数十MB以上になったりします。この構成でNASとGoogle Driveを双方向同期させるにあたって、--transfers や --checkers、--drive-pacer-min-sleep を実際に動かしながら詰めていったので、記録しておきます。

発端は、HSB3のリアルタイム同期が使えなくなったこと

もともとはQNAP NASのAppCenterで配布されているHSB3(Hybrid Backup Sync)のGoogle Drive連携で同期していました。導入当初はリアルタイム同期、つまりファイルを保存したら即座にDriveへ上がる動作をしていたのですが、ファイル数が一定数(10万?)を超えたあたりで、まともに作動しなくなりました。

仕方がないので、一定時間ごとにフォルダをスキャンして差分を拾う方式に切り替えました。ところがこちらも、スキャンのタイミングが来ても取りこぼしが多数発生します。しかも厄介なことに、HSB3の画面上は正常に完了したことになっています。「同期しました」と表示されているのに、Drive側を見ると実際には上がっていないファイルがある。

双方向で運用するにも問題がありすぎです。そこで、もっと高機能そうなInsync(試用したのは insync-headless 3.2.7)を検討することにしました。

Insyncでは、ローカルのフォルダを消された

最終的にInsyncを採用しなかった理由は単純で、私がやりたい「フォルダ単位の同期」が構造的にできず、しかもローカルのフォルダを削除してしまったからです。

Insyncの同期モデルは、設定した base(同期の起点となるディレクトリ)をGoogle Driveのミラーとして管理する、というものです。私は特定の業務フォルダだけを同期したかったので、baseをディスク全体(30以上の実フォルダ、約35万ファイル、2.2TBが入っている)に設定して、選択同期で一部のフォルダだけONにしました。

ところがInsyncは、base全体をDriveルートの完全なミラーとみなします。そのため、選択しなかったフォルダを「同期対象外=ローカルに存在すべきでないもの」と解釈して、ローカルから削除しはじめました。unsyncing というステータス表示が出ているのに気づいて、すぐに止めました。

削除されたファイルのほとんどはQNAPのゴミ箱から元のパスに全件戻すことができました。

この件で分かったのは、Insyncはそもそもフォルダ単位の独立した同期には向いていないという構造上の問題です。調べたところ、insyncのフォーラムでも同様の話題をしているスレッドがありましたが、Insync側では対応は現在のところしないようです。

わたしの要求を満たせそうなのが、 rclone bisync でした。rcloneならフォルダごとに独立した双方向ペアを定義できて、それぞれのペアは自分のフォルダの範囲しか触りません。Insyncのような「ディスク全体のミラー+対象外は削除」という構造自体が存在しないわけです。これに --max-delete(大量削除でabort)、--check-access(同期マーカーが消えていたら停止)、--conflict-resolve none(競合は両方残す)を足して、状態が変わったときだけ通知が飛ぶようにして、業務で使っている23フォルダ同期を全部HSB3からrcloneに移しました。

事務所のファイルの偏り

法律事務所のファイルは性質が二極化します。Word、PDFの書面、FAX、郵送物のスキャンといった小さいファイルが大量に積み上がる一方で、証拠書類の高解像度スキャンは1ファイルが大容量のPDFになります。この「小さいファイルが多数、たまに大きいファイル」という分布が、チューニングの前提になっています。

同期方式は rclone bisync の双方向で、フォルダ単位の独立ペアです。rcloneのバージョンは v1.74.3、SHA256を検証して固定で入れています。認証は共有の client_id ではなく、自前のGCPプロジェクトで取った専用の OAuth client_id を使っています。アカウントはGoogle Workspace Businessです。

同期しているフォルダは業務カテゴリ別に約23個。いちばん大きいフォルダで約17,800ファイル、182GBあり、そのうち8MiBを超える大きいファイルは約2,150個、率にすると12%ほどでした。ファイル数で見れば9割が8MiB未満の小さいファイル、容量で見れば少数の大きいファイルが大半を占める、という偏り方をしています。

実行はcronで3つに分けています。よく使うフォルダは3分ごと、更新の少ないフォルダは1日3回、書庫系は1日1回です。

ボトルネックは回線ではなくて、Drive APIを何回呼び出したか

「小さいファイルが多いから速くしたい」という話をするときに、まず何がボトルネックなのかを切り分ける必要があります。bisyncとDriveの組み合わせで効いているのは帯域ではなく、Drive APIの呼び出し回数です。

現在のGoogleの公式ドキュメントは quota units というモデルを採っていて、プロジェクト全体で毎分1,000,000ユニット、ユーザーとプロジェクトの組み合わせで毎分325,000ユニットという上限があります。そのうえで、files.get のような読み取りが1回あたり5ユニット、files.update のような書き込みが1回あたり50ユニットです(2026年6月時点の公式ドキュメント)。

つまり書き込みは読み取りの10倍のコストがかかります。「書き込みのほうが遥かに厳しい」というのは感覚の話ではなく、公式に明記されているわけです。さらにGoogleは別途、持続的な書き込みは毎秒3リクエストを超えないように、というバックエンド側のガイドラインも出していて、こちらは引き上げ不可とされています。上限を超えると 403: User rate limit exceeded が返り、バックエンド側の追加チェックで 429: Rate limit exceeded が返ることもあります。

なお、これらの上限にGoogle Workspaceのエディションによる差があるとは、公式には書かれていません。Businessだから書き込みが速くなるということはなく、全エディション共通です。攻めるなら自前のGCPプロジェクトで専用の client_id を用意してプロジェクト枠を専有すること(これは設定した方がいいです。デフォルトだとclient_idが共有枠で、こちらの問題でなくても制限がされる可能性がある)であって、Workspaceのプランを上げることではありません。

毎回の実行の大半は「変更なし」、つまり読み取りと差分の照合が主になります。ここを速くするのが本題であって、実際の書き込みのほうはGoogle側のコスト構造で頭打ちです。そうすると --transfers を上げても意味がなく、効いてくるのは --checkers と --drive-pacer-min-sleep のほうです。

実際に設定したパラメータ

現在使っているコマンドラインは以下のとおりです。

rclone bisync "gdrive:フォルダ" "/path/to/フォルダ" \
  --transfers 8 \
  --checkers 64 \
  --drive-pacer-min-sleep 1ms \
  --fast-list \
  --check-access --max-delete 50 \
  --conflict-resolve none \
  --resilient --recover --max-lock 2m \
  --drive-skip-gdocs --filters-file ./filters.txt

まず --drive-pacer-min-sleep です。既定は100msで、これを1msにしました。rcloneのDriveバックエンドは、API呼び出しごとに最小の間隔を空けるようになっていて、100msというのは1秒あたり10回程度、事実上の天井です。1msにすると超過した分は403が返ってきますが、--resilient を付けておけばrcloneの自動バックオフが調整役として働いてくれます。

ただしこれは専用の client_id が前提です。共有の client_id のままだと、プロジェクト枠が低いのですぐ403だらけになります。

次に --checkers です。既定は8で、これを64にしました。pacerで間隔を詰めても、APIを待つ並列のワーカーが足りなければ使い切れません。スループット×レイテンシなので、読み取りが実務上毎秒200回くらい、DriveのRTTが0.3秒くらいだとすると、60並列前後という数字が出てきました。それで64にしています。

rcloneのフォーラムでは、Workspace Enterpriseのユーザーが --checkers=120、--drive-pacer-min-sleep=0ms で同期時間を5.5時間から6〜9分に短縮したという報告があります。checkersを倍にすると50%くらい速くなり、120あたりでレートリミットに当たる、とのことでした。私は本番の業務データを扱っているので、保守的に64から始めています。

forum.rclone.org

--transfers は既定の4から8にはしていますが、それ以上は上げていません。書き込みはGoogle側で律速されるので、転送の並列度を上げても403が増えるだけだからです。--fast-list は再帰的なリストを ListR で一括取得するもので、毎回の実行におけるリストのやり取りを減らせます。

一日負荷をかけてログを見てみた

書き込みのほうは、あるフォルダで1日に約2,091ファイルがコピーされていて、ピークは毎分約230件、毎秒にすると4件くらいのペースが10分ほど続いていました。

肝心のスロットリングですが、rateLimitExceeded、403、429といった本当のレート制限は、23フォルダ全部・複数日にわたって実質ゼロでした。pacerのバックオフもゼロです。[ERROR] も [NEEDS-RESYNC] も .conflict の生成もゼロで、全ての実行が Bisync successful で終わっていました。

公式の「毎秒3回」を超えていたのに平気だった理由

毎秒4件のコピーが続いても403が一切出なかったのは、最初は意外でした。公式の「持続的な書き込みは毎秒3回まで」というガイドだけ見ると、毎秒4件は超過しているように思えます。

ただ、これをユニットで計算し直すと話が変わります。毎秒4回の書き込みは 4×50 で毎秒200ユニット、毎分12,000ユニットです。ユーザー枠の毎分325,000、プロジェクト枠の毎分1,000,000に対して、桁が違うくらい余裕があります。

「毎秒3回」というのは瞬間的なバーストに対する目安で、cronで3分ごとに分散される実際の運用では quota units の枠のほうが先に効いてきた、ということなのでしょう。もっとも、これは私の環境と使い方でそうだったというだけで、Googleが両方の制限をどう組み合わせて評価しているかまで分かったわけではありません。公式に書かれた数字だけ見て身構えるのではなく、実際のログの403発生率を見て決めるのが結局は確実だ(つまり最終的には実測しかない)、というくらいの教訓だと思っています。

やってみて分かったこと

bisyncとDriveの組み合わせで効いているのは帯域ではなくAPIのやり取りの回数です。まず読み取りと書き込みのどちらが効くのかを切り分けるところから始めるべきだと思います。

読み書きのコストは公式に非対称で、書き込みは読み取りの10倍かかります。小さいファイルを速くしたいときに効くのは読み取り側、つまり checkers と pacer であって、書き込みのほうはGoogle側で律速されるので設定を変えてもほとんど関係ありません。

pacerとcheckersはセットで動かします。片方だけ調整しても効きません。

もっとも簡単な対処法は、自前のGCPプロジェクトで専用のOAuth client_id を取ることです。Workspaceのエディションを上げてもDrive APIの書き込み上限は変わりません。

そして安全側の設定は性能チューニングとは切り離して維持します。--check-access、--max-delete、--conflict-resolve none は、速度をどう詰めるにせよ外しません。Insyncで15,600ファイル消えたときのことを考えると、ここは崩す気になれません。

小さいファイルが多くて専用の client_id がある環境なら、--checkers 64 --transfers 8 --drive-pacer-min-sleep 1ms --fast-list あたりから始めて、logs/*.log の rateLimitExceeded の発生率を見ながら、出なければpacerをさらに詰めて0msに、出るようなら10msに緩める、という進め方でいいのではないかなと思います。

AMD Ryzen/Radeon 610MとPaperlike253(E-inkモニター)の相性問題

しばらく使っていたPCの調子がどうにも悪く(これはこれで別の原因があったようですが)、パソコンを買い換えました。

これまではlenovoのPCを使用していましたが、MINIS FORUMのMS-A2にしました。


 

3年前にeBayで購入したU2 NVMe SSDを接続したら、SSDの厚みが15mmでカバーが閉まらなかったのでバラック運用です。

ビデオカードを追加でインストールすると、ビデオカードのドライバーをインストールするまで異様にWindowsの動作が重くなる以外は、セットアップはおおむね問題ありませんでした。

ただDasung Paperlike253(E-inkモニター)を接続したところ、はじめて見る現象が発生。

本来、モニターの左端に表示される映像が右側に表示されてしまっています。
そして、画面表示に明らかにちらつきが見られました。

しかもこの現象、Windowsスタート前の、パソコン起動時のメーカーロゴ表示時点で発生しています。つまりWidowsドライバーの問題ではなく、ハードウェア的な問題である可能性が極めて高いということです。

MS-A2のUSB Type-CコネクタからDisplayPort変換アダプターを使用して出力していたので、問題切り分けをしてみました。

  1. USB Type-Cコネクタのうち、HDMIポート側のコネクタでPaperlike253(E-inkモニター)に接続した場合に発生する。もう1つのUSB Type-Cコネクタでは発生しない。
  2. USB Type-Cコネクタのうち、HDMIポート側のコネクタ接続では、変換コネクター、ケーブルを交換しても発生する。
  3. USB Type-Cコネクタのうち、HDMIポート側のコネクタ接続で、Paperlike253(E-inkモニター)と接続した場合のみ発生する。HDMIポート側のUSB Type-Cコネクタ接続でも、一般的な液晶モニターと接続した場合は問題なく接続できて映像の乱れなどは発生しない。

結論的には、Radeon 610Mのハードウェア的な実装の問題で、Paperlike253(e-inkモニター)に接続した場合に問題が出るのではないかと思われます。

E-inkモニターの入力/出力が独特で、Paperlike253はリフレッシュレートが40Hzになっていて、通常のモニターとはかなり異なっています。
本来左側にあるべきタスクバーやスタートボタンが右側に回り込んで表示される現象は、「水平同期(H-Sync)の不整合」または「タイミング・パラメータの誤認」が原因のようです。
E-inkモニター(Dasung製品等)は、一般的な液晶モニターと異なり独自のコントローラーを介して描画を行います。GPU(Radeon 610M)が送出する信号のタイミング(水平帰線期間など)と、モニター側のコントローラーが期待するタイミングが微細にずれることで、走査の開始位置が誤認され、画像が「回り込む(Wrap-around)」現象が発生します。

ハードウェアの問題なので、回避策は、USB Type-Cコネクタのうち、HDMIポート側のコネクタをE-inkモニターでは使用しないことしか方法はないように思います。

かなり限定的な環境でしか発生しない問題ですが、Ryzenの内蔵GPUを使用する場合、モニターとの同期機能はどのGPUでも同じ可能性が高く(つまり同じ問題が発生しうる)、意外に再現性があるかもしれません。
もし同様に困っている方がいれば、ご参考までに。

速度と効率の重要性:弁護士業務におけるネットワーク最適化

当事務所では、膨大な裁判資料の電子化や、動画・画像といった証拠データの取り扱いが増加する中、業務効率を最大化するために所内ネットワークの「10GbE(10ギガビットイーサネット)化」が完了しています。

「弁護士業務にそこまでの速度が必要か?」と問われることもありますが、数百ページに及ぶPDFの閲覧や、数ギガバイト単位のバックアップを一瞬で終えることは、思考を中断させないために極めて重要です。

今回は、事務所内の環境で実施した、異なる接続形態およびネットワークチップ(NIC)による転送速度のベンチマーク結果をご紹介します。

測定には定番の「CrystalDiskMark」を使用し、所内の10GbE NASをネットワークドライブとしてマウントした状態で計測しました。

1. USB接続アダプター(Realtek RTL8159チップ採用)

まずは、ノートPCや拡張性のない端末で重宝するUSB接続タイプの10GbEアダプターです。

  • Read: 974.16 MB/s

  • Write: 577.39 MB/s

USB接続でありながら、読み込みで約974MB/s(約7.8Gbps)という素晴らしい数値を叩き出しました。USBのオーバーヘッドがあるためPCIe接続には及びませんが、手軽にここまでの速度が出るのは技術の進歩を感じます。これならノートPCでもデスクトップ並みの快適さで資料検索が可能です。

10GbE USB-C Ethernet Adapter USB3.2 Type C 10 Gigabit Ethernet Adapter Realtek/RTL8159 Support for Windows 11
Price Now: JPY 11549
🔗Click & Buy: https://s.click.aliexpress.com/e/_c3WDlhjz

2. Intel製NIC(E610-XT2)

続いて、サーバーやワークステーショングレードで信頼性の高いIntel製チップを採用した接続環境です。

  • Read: 1186.09 MB/s

  • Write: 588.77 MB/s

さすがはIntelと言うべきでしょうか。読み込み速度は1186MB/sと、10GbEの理論値(帯域幅)をほぼ使い切るパフォーマンスです。ただ問題は搭載PCが異様に不安定で、ちょっとファームとドライバーがこなれていない感じがします。値段も高いし、あまりおすすめしません。

3. Realtek RTL8127 PCIeカード(10GbE)

最後に、コストパフォーマンスに優れるRealtekチップを搭載したPCIe接続のカードです。

  • Read: 1046.04 MB/s

  • Write: 581.38 MB/s

こちらも読み込みで1000MB/s(1GB/s)の大台を超えてきました。USB接続と比較しても、やはりPCIバスに直結するPCIeカードの方が帯域を安定して確保できている印象です。Random4kQ1T1がもっとも早く、法律事務所のような細かいドキュメントファイルを頻繁に開く状況では、一番レスポンスがいいかもしれません。