PiPO X9s を買ってきた

持ち運びを想定していない、机に置く用のデスクトップタブレット端末である PiPO X9s (4+64GB 版)を買ってきた。

まず PiPO の端末の売り場はあちこちに色々あるが、どれがちゃんとしたところかよくわからなかったのでそこからというところだった。Baidu で「pipo」と検索すると、 http://www.pipo.cn/ が公式マーク的なものがついていたので、一旦ここから辿っていくことにした。(結果的に届いた商品のパッケージにも www.pipo.cn の記述があった)

今回はこのサイトの英語版の Contact Us ページに書いてある alibaba.com のリンクである https://pipotech.en.alibaba.com/ から購入した。なお Contact Us のところには公式のように見えるが公式ではないサイトが一つ注意喚起されている。

alibaba.com の会員登録にあたっては、 alibaba.com の上部の “Join Free” からだと会社名の入力が必須(しかもおそらく日本の不動産契約の書類に書くような個人が所属する会社としての記述というより、会社のアカウントとしての立ち位置だと思われる)になっていた。先に商品を選んで Contact Supplier を押し、そのタイミングで出てくるログイン画面から Join Alibaba.com を選択すると “I’m a non-business entity” というチェックボックスが利用可能になり、個人でのアカウントが登録可能になった。ここでチャットでやりとりし、在庫がなかったので在庫が復活するのを待って購入することができた。本体 179USD, 送料 50USD で合計 229USD で、香港から FedEx で送られてきた。(追記:後日FedEx から関税を支払うよう郵便で通知された。「その他税金(Consumption Tax/VAT)」が1000円、「関税その他税金についての特別取扱手数料(Duty Handling Fee)」が1000円で合計2000円だった。)

PiPO X9s と呼ばれる製品は過去に何度か X9s という名前のまま設計を変えているようだ。提供されている OS イメージやウェブ上の資料を見る限りだと少なくとも、

  • Windows 10 + Android デュアルブートモデル (OS イメージがある)
  • Windows 10 シングルブートモデル、電源電圧5V版(OS イメージがある)
  • Windows 10 シングルブートモデル、電源電圧12V版(新マザーボード版で、おそらく今回買ったものはこれ。 OS イメージ

が存在しそうに見える。その中でさらにバリエーションがありそうにも見える。

特に今回買った 12V 新マザーボード版は、ざっくり調べたところおそらくだが

  • 電源電圧(今回買ったもの:12V、旧版:5V (PIPO の X9s 紹介ページでも 2021/5/23 現在 5V ということになっている)。ドライバダウンロードも電圧で区別している)
  • タッチパネルコントローラー(今回買ったもの: Sileadtouch、旧版:Freaktab のやり取りを見るにおそらく Goodix)
  • メインボード(イメージダウンロードページのところに「注意此系统支持新主板,机器电源为12V!」と注意があるので、おそらく 12V 版はメインボードが変わっている)
  • 画面解像度(今回買ったもの:1920×1280、旧版:おそらく 1920×1200 (PIPO の X9s 紹介ページでも 2021/5/23 現在 1920×1200 ということになっている。Android 版のイメージのファイル名にも 1920×1200 を示唆する文字列がある)
  • UEFI (今回買ったものは NTFS パーティションからのブート不可、旧版と思われるイメージの説明書や、FreakTab でのやり取りを見るに、おそらく旧版は NTFS ブート可と思われる)

辺りが異なっていそうに見える。X9s という名前がついていても、見た目やできることは似ていても中身は旧版と別物と思った方が良さそう。従ってトラブルの際はウェブ検索で調べて X9s についての対処が書いてあっても、当てはまるかどうかわからない。

今回買ったものは負荷をかけてもファンの音はしていないようだ。

購入後、ドライバを

dism /online /driver-export /destination:C:\driver-bak

でバックアップして Windows 10 をクリーンインストールし、デバイスマネージャーからバックアップしたドライバを適用しようとしたが、いくつか挙動に問題があった。試行錯誤の結果、以下の感じでクリーンインストールした状態から、見た限り正常に動作するようになった。Sileadtouch はウェブ検索してみると中国製の格安タブレットに多く使われているようで、やや鬼門であるようには思われたが、結局のところはバックアップしたドライバか、 PIPO が公開しているイメージファイルで正常動作させられるようだ。

  • (そのままで当てられるドライバを先に当てたが、このタイミングでやらなくても良いと思われる)
  • “bcdedit -s testsigning on” で、未署名ドライバのインストールを許可
  • ウェブで調べた方法で詳細は覚えていないが、回復メニューから再起動を行い、ドライバの署名を強制しないモードで起動
  • デバイスマネージャーで不明なデバイスになっているデバイスを片っ端からドライバ更新を指定し、バックアップしたドライバのフォルダを指定してインストール
    • Sileadtouch について試行錯誤の結果、dism でバックアップしたものではなく別途公開されている OS イメージに含まれていたものにしたが、中身は同じと思われる
  • この時点で、なぜかタッチパネルが、短く押しても長押し判定されるようになっていた。もとのインストールイメージでも sileadtouch.fw というファイルがあるわけではなく、理由がなかなかわからなかった。
  • “bcdedit -s testsigning off” してテストモードを抜けて再起動すると、タッチパネルが誤って長押し判定されることがなく、正常動作した。理由はわからないが一旦これで進めることにした。
  • スタンバイや電源オフにしても液晶のバックライトが点灯したままになっていた。デバイスマネージャーで Microsoft Basic Display Adapter と表示されていたデバイスについて、 dism でバックアップしたドライバからの更新を試みると、数分経った後に Intel(R) HD Graphics として認識され、スタンバイなどの際はきちんと消灯するようになった。

Intel Atom x5-Z8350, RAM 4GB というスペックなので、 Windows 10 が動くとは言っても速くはないし、今時だとできることは限られている。この辺りはうまくカバーする使い方をする必要があるだろう。

また、液晶と実際に触れる面の間にまあまあの厚さのガラスがあるのか、斜めからタッチすると視差が出るようだ。形状・用途的には斜めからタッチすることも多いと思うが、慣れるしかない。

ちなみに Ubuntu については USB ブートで試してみた限りで、放置すると1分程度でスリープしたり電源が落ちたりする現象があったのと、タッチパネルが正常に使えなかった。ウェブの情報を見るに Silead のタッチパネルを Linux で動かしている人はいるようだが、おそらく Ubuntu そのままというわけにはいかないのと、1分程度でスリープする現象についてはまだ追っていないので、入れようとすると苦労しそうだ。

なお、技術適合証明やそれに類する記述は見当たらなかったし、そもそも日本市場を意識しているとも思えないので、おそらく内蔵の無線は日本では合法には使えないと考えて無効化した。内蔵の有線 LAN で困らない場合もあると思うが、今回は小さく安い USB WiFi を背面ポートに刺すことにした。特に邪魔にはならない(実運用上は、今回使わないアンテナや、 AC アダプタの端子の陰に埋もれる)と思われる。

WiFi アダプタを刺した様子

PIPO X9s 背面に ELECOM WDC-150SU2MBK を刺して上から見たところ

(追記)書き忘れていたので追記。PC として機能させられるだけのバッテリーは搭載しておらず、AC アダプタを抜くとすぐに電源が落ちる。輸送時には UN3091 のラベルが貼り付けられ、チャットではバッテリーについて確認した際にバッテリーなしではなく 40mAh と言っていたので、おそらくこれがボタン電池かそれに近いものを刺していると思われる(が、分解するためのねじ穴にアクセスするには接着されたゴム足を外す必要がありそうなので、分解しての確認はしていない)。少なくとも UN3091 はリチウムイオンバッテリーではないので、品質の低いリチウムイオン電池が問題を起こす心配はなさそう。

千葉県の福岡の桜並木に行ってきた

桜が満開になる季節になってきたので、千葉県にある福岡の桜並木(リンク:千葉県観光物産協会による紹介サイト)に一昨日ドライブに行ってきた。道沿いに断続的に桜が咲いていてなかなか良い感じだった。

「福岡の桜並木」では使った車のカーナビに登録されておらず、鹿野山神野寺への道として設定しても周辺の道としては見当たらなかったが、結局は近くのコンビニで車を止めている時に Google マップで「福岡の桜並木」検索したところ見つかった。鹿野山 福岡口(リンク先は Google マップ)の T 字路から県道163号を西に進めばよいようだ。上の方に分岐があって片方は時間帯指定で進入禁止、かつ進入禁止の道もそうでない道もすれ違いができない道に見えたので、そこで引き返してきた。

下りの動画(ダウンロード

入り口

福岡桜並木入り口

反対側から(帰り)

中ほど(下り)

福岡桜並木中ほど

(投稿してから思い出したので追記)しかしこの日は渋滞が激しかった。カーナビはとにかく首都高を避けようとしていたし、実際のところアクアラインでは以下のような表示が出ていたのでカーナビに従って、浮島から湾岸線に出ず、殿町から出てあとは下道で帰ってきた。
2021/3/26 渋滞(湾岸環八から辰巳まで渋滞15km70分)

このブログを(多分) The Privacy Sandbox の FLoC による演算から opt-out した

最近、 Google が The Privacy Sandbox なる試みを行っているようだ。現在の広告その他のエコシステムを保ったままプライバシーを改善することを狙っているようで、 The Privacy Sandbox 自体が仕組みの名前というよりは複数の仕組みを作ることを通してプライバシーを改善するプロジェクトとして存在しているようだ。とはいえ、やはりサードパーティー Cookie を廃止するための代替としての、ブラウザ側でユーザーの閲覧履歴の分析・学習を行って、嗜好を知りたいサイト側にはどのグループ(Cohort)に属しているかだけを通知する FLoC が一番取り沙汰されているように感じる。

調べてみた感じでは現在のところ、広告のための学習のためにプライベートな情報を使う機構を今からわざわざ追加するモチベーションがあるとは思えない Firefox や Safari をどうするつもりなのかといった情報は見つけられなかった。もし少しでも Chrome 以外のブラウザを排斥する意図があるのであれば、それは Google によるインターネットの支配であり、オープンなインターネットの終焉であり、同一の事業者の広告が貼ってある複数ページの閲覧履歴を束ねて追跡されることよりもずっと危険な問題であると思うが、情報がない以上何とも言えない。

また、一応は学習を担当するブラウザベンダと学習結果を実際に評価できる広告事業者が別々の役割として存在しているわけで、何をどうやって学習するのかもよくわかっていないが、そのあたりは追っていない。加工されているとはいえ FLoC システム全体の学習のために、閲覧履歴をもとにしたデータをブラウザベンダとやりとりするというのであれば、ブラウザベンダが持つ権限は今よりずっと大きなものになりそうだと思っている。

ただし、少なくとも現時点で、明示的にサイト側で対応しなければすべてのサイトを学習の対象にしようとしているようだった。www.chromium.org ドメインにある The Privacy Sandbox のページからリンクされている GitHub リポジトリで WICG/floc: FLoC というものがあり、そこに現時点では以下のように記されている:

A site should be able to declare that it does not want to be included in the user’s list of sites for cohort calculation. This can be accomplished via a new interest-cohort permissions policy. This policy will be default allow.

opt-in 形式にした方が良いというプルリクは上がっており、まだ close はされていないようだ: Opt in cohort training by dmarti · Pull Request #42 · WICG/floc

従来では一応、関連の広告事業者の広告を表示する(といっても各社、広告にはなぜか自社ロゴではなくわざわざ似たような (i) のロゴを設置していて、マウスオーバーしない限りどこの事業者の広告かはわからないことが多いが)か、表示しなくとも関連の広告事業者に追跡させるためのコードを明示的に書いたページのみが対象になっていた。従って opt-out しなければ学習の対象になるというのは、従来と比べると随分学習に使うデータを乱暴に増やそうとしているように感じる。

opt-out するための方法は一応準備されている(まだ正式仕様ではないので、変わるかもしれないが)。

For example, a site can opt out of all FLoC cohort calculation by sending the HTTP response header:

Permissions-Policy: interest-cohort=()

従って関係ないサイトの広告のために閲覧履歴を使用されたくない場合は以下のようなヘッダを追加することになる。一応ブラウザから直接出る外部に値というわけではないものの、これらをもとに学習されたデータは任意のサイトから読みだされることになるし、学習の過程でブラウザベンダとの通信も発生すると思われることから、おそらくセンシティブな情報を扱うのであれば入れた方が良いだろう。

Permissions-Policy: interest-cohort=()

本質的にクライアント側の挙動であり、サイト側というよりはブラウザをインストールした側の責任ではある。とはいえ、プライバシーに寄せましたと主張しながら関係ないサイトの情報も広告に使うというのは、個人的には賛成できるものではない。そのため、せめてもの抵抗として、(ちゃんと排除できているか確認はできていないものの)このブログは interest-cohort は許可しないようにヘッダを追加した。これを理由に Google 検索の順位が下げらなければ良いが。

fontconfig の優先順位を調整して中国語フォントより日本語フォントを優先する

Ubuntu MATE 20.04 を使っていて、日本語で表示されるべきものがいわゆる中華フォントになってしまった(後述するがこれ自体は Ubuntu MATE のせいではなく自分の操作によるものだった)ので、フォントの優先順位を修正して日本語フォントを優先するようにしたのでメモ。

まず、フォントの優先順位そのものの設定は /etc/fonts/ で行われているようだった。調べてみると Linuxでアップデートしたら中華フォントになった件について(修正方法) – Qiita に、 Noto シリーズの JP のものを serif, sans-serif, monospace それぞれのエイリアスに追加する良い感じの fontconfig の xml があった。この記事では /etc/fonts/local.conf に置いていた。

これをもう少し管理しやすい場所に置きたかったので調べてみたところ、このファイルは /etc/fonts/local.conf ではなく $HOME/.config/fontconfig/fonts.conf に置くことで、管理者権限なしに自由に修正できるようだった。世代によって推奨される場所が変わったりはするようだが、そんなに特殊な置き場所というほどでもないようだ。

さらに、これは /etc/fonts/conf.d/50-user.conf によって呼び出されていた。この後で優先順位を変えて絞り込んだところ、/etc/fonts/conf.d/56-kubuntu-noto.conf というファイルで Noto 関係のフォントにエイリアスが指定されており、これより前に読み込めば、通常の漢字表示が日本語フォントになるようだった(先ほどの /etc/fonts/conf.d/56-aaa.conf にすれば日本語フォントになり、 /etc/fonts/conf.d/56-zzz.conf にすれば中国語フォントになった)。おそらく Kubuntu を追加でインストールしたときに変わってしまったのだろう。

その後に読み込まれるファイルを漁ってみたところ、 /etc/fonts/conf.d/64-language-selector-prefer.conf というファイルがあり、 Noto の中で、どの言語から順番に表示するかの優先順位が設定できるようになっていた。手元の環境だと JP のものが一番優先になっていたので、おそらく Kubuntu をインストールする前はこのファイルによって日本語フォントが優先されていたのだろう。

ということで結局この環境では

sudo ln -s /etc/fonts/conf.d/64-language-selector-prefer.conf /etc/fonts/conf.d/52-japanese.conf

としてこのファイルの優先度を上げることでほぼ解決できた(すべてのアプリケーションではない模様)。似たような問題が起きた時や他の理由でフォントの優先順位を変えたいときも、この辺りのファイルから見ていけば良さそう。

sshrc の内容に関するメモ: Xforward で必要

ssh で X 転送しようとすると次のようなエラーが出て困っていた。ぐぐってもよくわからなかった。

PuTTY X11 proxy: Unsupported authorisation protocol

原因は色々あるようだがググって出てきたものがなかなか当てはまらなかった。別サーバと比べたりしていたところ、 .ssh/rc を置いている場合、 /etc/ssh/sshrc が呼び出されないため、この内容がないと困ることがわかった。man sshd すると SSHRC の項に説明とサンプルファイルがあり、これが .ssh/rc を置いていることに阻害されていただけだった。.ssh/rc に man の内容に従って xauth 関連の記述を追記すると繋がった。

Win10 で使っていた SSD (Crucial MX200)の完全消去でハマった

Crucial MX200 SSD を ThinkPad W550s で、 Windows 10 Pro で使用していたが、新しい PC を買った後しばらく経ち、 Ubuntu MATE に入れ替えようと思ったため、一旦 SSD のデータを消去しようとした。その際、ハマったのでメモしておく。

まず、 SSD の secure erase はハードディスクと異なり、 SSD に対してコマンドを発行することで行えるようだ。それ自体の手順は Solid state drive/Memory cell clearing – ArchWiki に記されている通り、一度 hdparm でパスワードを設定した後、 hdparm で –security-erase オプションを使用すればよいようだ。frozen 状態に注意するよう注意書きがあった。SSD はハードディスクと異なり使用中の領域かどうかを管理しているようなので、性能上も security erase して使うのが一番良いように思えた。

しかし、今回の SSD では、これをしようとすると、エラーが出た。エラーの内容は記録するのを忘れてしまったが、コマンドの実行で何かバッファでもあふれているかのような、怪しいエラーメッセージだった。その時ウェブ検索していた内容から見るに、bad/missing sense data といったことが書いてあったようだ。この時開いていた、hard drive – hdparm error: SG_IO: bad/missing sense data – Super User の質問のエラーメッセージ(以下に引用)と同じようだ(数字の羅列が一致するかはわからない)が、状況は違うように思えた。

sda:  Issuing SECURITY_SET_PASS command, password="PASSWORD",
user=user, mode=high SG_IO: bad/missing sense data, sb[]:  70 00 05 00
なお先の ArchWiki で書かれている frozen かどうかは、それらしい行がこの時は見当たらなかった。そもそも、Security の項がなかった。

また、先頭 593MB は読み書きできるものの、それ以上は Input/output Error で読み書きできない(dd if=/dev/sda of=/dev/null bs=1048576 count=1000 で簡単に確認できた)現象にもなり、削除せずに使い続けることもできなかった。

色々調べた結果、忘却の彼方: Crucial M500/M550/MX100とSecure Erase に、 Windows 8 以降ではいくつかの SSD において、ディスクのセキュリティ機能を変更できなくなるような修正を加えており、 PSID を復旧すると元に戻る旨が書かれていた。特に今回は BitLocker を使っていた端末でもあり、暗号化された領域にロックがかかっていたりしても不思議ではないように思えた。

結局のところ、 PSID をリセットする操作が必要であり、これを行うと無事に他の操作も行えるようだ。Crucial の SSD であったので、 Crucial Storage Executiveをインストールしたパソコンに、 USB-SATA で当該 SSD を接続し、「PSID を元に戻す」を押すと、データが消去される旨が表示され、その後無事 SSD を利用することができた。hdparm -I でも Security: の項目に frozen かどうか、表示されるようになった。security erase も ArchWiki の手順で成功した。

ISUCON 10 予選に参加した

いつものようにチームワイハリマで ISUCON 10 予選に参加していた。今回はいつもにもまして手も足も出ない状態だった。

今回は競技に関するルールが事前に公開され、当日の競技開始と同時に ISUCON10 予選マニュアルが公開された。

今回は競技開始後もなかなかサーバに入れなかったり、ポータルが終始不安定でベンチマークも運が良い時しか走らせなかったりした。まずはざっくり画面を操作して機能を把握したうえで、ベンチマークが走るようになったら重いクエリがどこにあるかなど、徐々に詰めていった。また、サーバは 3 台与えられた。EPYC 1vCPU, RAM 1GB なのは良いとして、ディスクが 10GB なのが目に付いていた。

まずは目に付いたいくつかの重い検索クエリを軽くしようと、インデックスを色々張ってみたり、 SQL の外で実行しようとしたり、そもそもウェブアプリケーションの外で実行しようとしてみたりしたが、特段速くはならなかった。SQL が詰まっていたので専用サーバにしたことも、効果を発揮しなかった。recommend では条件が 6 つ OR で繋がっているのを、データの入力を工夫して一つにまとめてみたが、これもうまくいかなかった。

検索が重いことが分かっていたので、レプリケーションを行って検索系クエリを逃してみたが、却って遅くなった。3台のマシンすべてを使うことで CPU 使用率が 100% のマシンはなかったが、全体に CPU 負荷があまりかからなかった。レプリケーションを有効にするためにバイナリログを有効にしたが、これが問題になったようだ。後で考えてみれば、ディスク 10GB に目がついていたが、このディスクが低速で(今となってはわからないが、 SSD ではなく本当にディスクだったのだろうか)書き込みもそれなりに詰まっていた可能性がある。

また今回は、いくつかの最適化を試みた時から、互換性を失わないと思われる変更でベンチマークが通らなくなる現象にも苦しんだ。json_encode 周り及び、 PDO から SQL の結果を受け取る時に独自のクラスで受け取るもとの実装について、どこがしかの挙動を変えてしまったようだが、デバッグログの出し方がわからなかったこともありデバッグには終始時間がかかっていた。

もともと PHP 実装を選択した後一度も Go の初期実装スコアを超えていないことに加え、最終的にはベンチマークでの互換性テストすら通らない状態となり、 0 点となった。高速化視点での実力不足とは別にも、色々と反省すべき点を整理したほうが良さそうだ。

Ubuntu MATE 20.04 を GPD Pocket 2 にインストール

(追記)Ubuntu MATE の公式ダウンロードページ に GPD Pocket 2 版の Ubuntu MATE イメージが公開されているようなので、今後はこの記事の方法ではなくそちらを使った方が良いかもしれない(既にこの記事の方法でインストールしてしまったので検証はしていない)

Ubuntu MATE 20.04 を GPD Pocket 2 にインストールした。

まず、 Ubuntu MATE のダウンロードページには GPD 用のイメージは 20.04 はなかったので、標準イメージの 20.04 をダウンロードした。これを Rufus で USB メモリに焼いてインストール。WiFi は最初から見えているので、とりあえずインストールは通常通りの手順で行える。画面は 100% スケーリングかつ X 起動後は向きもおかしいが、とりあえず首を90度傾けてどうにかする。

インストール後、実際に使えるようにいくつか追加していく。Ubuntu MATE 18.04 の UMPC ポートのページを見るに画面、タッチパネル関係が主のようだ。WiFi はこのページで言及されているが、 Ubuntu MATE 20.04 と GPD Pocket 2 の組み合わせではそのまま認識した。ファンコントロールはリンク先の GitHub のコードを見る限り OS 組み込みのように見える。GRUB などの、 X 起動前は、なにもしなくとも正しい向きで表示されるようだ。

なので、さきほどのページからリンクされている https://github.com/wimpysworld/umpc-ubuntu のリポジトリから必要な項目だけを持っていくことにした。

まずは X 起動後に画面が 90 度傾いていることを解決する。画面が傾いているだけなら GUI から画面設定をいじればよいが、それだと今度はタッチパネルが 90 度傾いた状態で作動し、よくわからない状態になる。加えて GUI から画面設定を修正した場合はログイン前の画面などが直らないため、今度はログイン前にタッチの場所がずれることになる。なので、両方設定ファイルを修正するのがよさそう。先ほどのリンクから 40-gpd-pocket2-monitor.conf を /usr/share/X11/xorg.conf.d/ に、 99-gpd-pocket2-touch.rules を /etc/udev/rules.d/ にコピーし、再起動すると画面の向きは正常になった。

DPI を上げる設定はいくつか方法があるようだ。ただし、 Ubuntu 20.04 から正式導入された fractional scaling の機能は、検索した限りだと MATE に来ている様子はなかった(少なくとも見つけられなかった)。100% だと小さすぎるが 200% だと大きすぎる場合、ほかの方法は、そもそも実際より小さな解像度に見せかけるか、 xrandr で実際より大きな解像度でレンダリングして HiDPI 扱いで 200% にする Mac の Retina ディスプレイみたいなやり方か、 テキストの DPI 設定だけ大きくする(GUI で設定可能)か、といったあたりのようだ。HiDPI – ArchWiki にだいたい載っていた。とりあえず、いくつかの UI 部品が小さく表示されるものの、それ以上に厄介な問題を生じにくそうなテキストだけ DPI を GUI の設定画面で大きく設定することにした。

Firefox でタッチパネルを使うとスクロールすらできない問題が起こった。これは MOZ_USE_XINPUT2=1 という環境変数を入れることで、 Firefox 側の設定を変更し、正しく動作させることができたので、 ~/.xsessions に設定した。

純粋な CUI の文字サイズが小さい点は実用上どこまで問題になるか不明だが、一応先ほどのリポジトリを参考に、 /etc/default/console-setup の FONTSIZE を 8×16 から 16×32 に変更した。

ほぼタッチパネル液晶周りしかいじっていないが、ハードウェア依存な部分は一旦これで使い始められそうだ。解像度回りを考えると素の Ubuntu の方が楽かもしれないが、致命的ではないので、ひとまずは好みを優先して Ubuntu MATE でしばらく使ってみることにする。

(追記)99-gpd-pocket2-touch.rules は MATE じゃない素の ubuntu-desktop で使う場合は不要というかあると回転が狂うようだ。この 9 つの数字は3次元の回転行列のようなので、単位行列、すなわち 1 0 0 0 1 0 0 0 1 を入れたときに素の Ubuntu では正しく、今度は Ubuntu MATE では正しく回転が認識できない状態になる。

色々試した結果、 99-gpd-pocket2-touch.rules は有功のまま、さらに次のコマンドを打つとどちらからでも正しくタッチパネルが使用できる状態になった。これを GUI から Startup Application に登録したところ、ログイン前の MATE の画面、ログイン後の MATE の画面、ログイン後の ubuntu-desktop の画面がすべて正しくタッチできるようになった。

xinput set-prop 'pointer:Goodix Capacitive TouchScreen' 'Coordinate Transformation Matrix' 1 0 0 0 1 0 0 0 1

(追記)音が鳴るようなタイミング(通知音が鳴るようなタイミングや、ブラウザで動画の再生が始まった時)で、たとえスピーカーをミュートにしていてもスピーカーから「カッ」という音がする現象があった。確認できていないが個の挙動としてはそもそも使わない間はスピーカーの電源を切っているのだろうか。Popping sound on speakers when starting 19.3 (Solved) – Linux Mint Forums の記事を見ると省電力設定を解除しているようだ。lsmod を見るとこの端末もこの記事の設定ファイルと同じ snd_hda_intel を呼び出しているようだったので、/etc/modprobe.d/alsa-snd-hda-intel.conf を次の内容で作成して再起動すると直った。

options snd-hda-intel power_save=0

(追記)WiFi はこの設定で動作しているものの、明らかに見えるはずの SSID を掴めないことが多いように感じる。/lib/firmware/brcm/brcmfmac4356-pcie.txt についても対応したほうが良いかもしれないので試しに入れているが、再現方法すらわかっていないので何とも言えない。

USB – 5Gbps Ethernet アダプタを買ってきた

業務外での技術力向上支援の会社の補助金で、 USB – 5Gbps Ethernet アダプタをいくつか購入し、まずはざっくり普通に繋ぐとどうなるのか試してみたのでメモ。

買ったもの

10G/5G アダプタ

以上の4つ。Solo10G のみ Thunderbolt 接続で 10Gbps だが、他が USB 接続で 5Gbps。なので今のところ、 10Gbps で繋がる相手がいないので、今回はすべて 5Gbps として使用。

ドライバ

Solo10G については 、 Windows 版のドライバをインストールして使用した。Solo10G ドライバとしてダウンロードしたものが、実際に起動したものは Aquantia のドライバインストーラだった。QNAP QNA-UC5G1 と、 StarTech.com のものは、いずれも Windows 10 では OS 標準か Windows Update かでドライバが入った。

Linux には USB の二機種(QNAP、StarTech.com)を接続したが、いずれも Aquantia 製のコントローラが入っているようで、 aqc111 というドライバが提供されている。ただし、 Linux 5.x には標準に同梱されているが、 Linux 4.x だと入っていないのでそのままでは他のドライバが適用される。また、 Linux 5.x 標準のものと、 AQ_USBDongle_LinuxDriver_1.3.3.0.zip (QNAP のウェブページよりリンクされていた、Marvell のサポートページよりダウンロード)で提供されているものは両方 aqc111 という名称で、かつ後者を解凍して出てくる README にも aqc111 Linux 5.x に同梱されているとの表記があるが、 ethtool で見ることができる項目が若干後者の方が多い(Low Power 5G と Thermal throttling が設定可能になる)など、若干の差があるようだ。

Linux 4.x の搭載ドライバ(試した環境は Ubuntu 18.04)でも、 1Gbps を超える転送が可能であり、接続相手からも 5000Mbps リンクとして認識されていた。ただし、ジャンボフレームを有効にした際に、大きなパケットを受信すると以後通信ができなくなるという問題があった。Linux 5.x (Ubuntu 20.04)にすると問題なかった。

クライアント-サーバー直結での性能測定

転送速度については、まずは Solo10G (ThinkPad X1 Extreme (Core i7-8750H 2.20GHz、 Windows 10 1909) に接続) と QNAP QNA-UC5G1T (DS77U5 (Core i5-7200U 2.50GHz、 Ubuntu 18.04/20.04) に接続)をケーブルで直結して測定した。

iperf で接続したところ、まず初期設定の MTU=1500 で、 iperf の並列度も上げずに試したところ、 2.1-2.3Gbps 程度だった(この時は Ubuntu 18.04)。この状態で、 QNAP 側を USB パススルーで Ubuntu 20.04 に見せるようにすると 1.1Gbps 程度まで下がった(これについては qemu のバージョンアップなどでも改善するようで、 Ubuntu 20.04 だと 2.0-2.5Gbps 程度出た)。

この後、 MTU=9000 に変更すると、その時のベンチマークでは iperf の並列度が 1 のままでも 3.63Gbps という転送速度が出た。ただし、前述の 4 系カーネルバンドルのドライバではジャンボフレーム受信が正常に行えない問題により、片方向しか通信できなかった。

さらにここまでの試行錯誤(主にジャンボフレームでトラブっていた時)で、 Solo10G のデバイスマネージャーの設定値で、 Large Send Offload V1, V2 の IPv4 の値をいじっている。結局、これは Disable のほうが当座の iperf でのスループットが出そうなので、今回の調査切ることにした。ただし、 iperf 側で並列化させない直列の転送速度でアプリケーション側込みだと Large Send Offload 有効のほうが速そうに見えたため、この辺りはよく確認したほうがよさそう(まだ詳細は調べていない)。そもそも、普通にしていて遅くなるようなものなら通常は offload しないだろうし。

ここからは、クライアント-サーバー直結での測定は次のような環境で試している。

  • Sonnet Solo10G, ThinkPad X1 Extreme, Core i7-8750H 2.20GHz, Windows 10 1909
  • QNAP QNA-UC5G1T, DS77U5, Core i5-7200U 2.50GHz, Ubuntu 20.04, Ubuntu 同梱 aqc111 ドライバ
  • MTU=9000 (Windows のプロパティの Jumbo Packet では 9014 Bytes)
  • Windows 環境では Large Send Offload V1, V2 (IPv4) を Disable
  • この前の段階でも iperf の並列度(-P) 1 で 3Gbps を超える速度が出たりしていたが、同じ条件でも再度試すと出なかったり、疑問だったので、この条件を決めてからは iperf の並列度は合計の速度が上がらなくなるまで上げる

この状態で、とりあえず 3.4Gbps 程度までは出せることがわかった。USB 側との接続も 5Gbps となるため、そちらで詰まれば製品としての上限がこのあたりに来る可能性はあるが、そこまでは確認していない。

nhirokinet@ds77u5:~$ iperf -c 192.168.150.2
------------------------------------------------------------
Client connecting to 192.168.150.2, TCP port 5001
TCP window size:  715 KByte (default)
------------------------------------------------------------
[  3] local 192.168.150.3 port 34642 connected with 192.168.150.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  4.00 GBytes  3.44 Gbits/sec
nhirokinet@ds77u5:~$ iperf -c 192.168.150.2
------------------------------------------------------------
Client connecting to 192.168.150.2, TCP port 5001
TCP window size:  812 KByte (default)
------------------------------------------------------------
[  3] local 192.168.150.3 port 34646 connected with 192.168.150.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  3.84 GBytes  3.30 Gbits/sec
nhirokinet@ds77u5:~$

また、全二重でも速度が出せるようだった。

ちなみに、 USB パススルーでの転送速度は、 Ubuntu 18.04 -> 20.04 で qemu のバージョンが上がっていることもあり、改善されたが、片方向で 2.0-2.5Gbps、双方向同時ならそれぞれ 1.5-1.7Gbps (あわせて 3Gbps 強)というところだった。

Ubuntu でのパケットの中継

Ubuntu をスイッチとして使ったような場合に、性能上限が変わってくるのか、といったあたりを見たかったため、簡単に試した。

環境は

  • Sonnet Solo10G, ThinkPad X1 Extreme, Core i7-8750H 2.20GHz, Windows 10 1909
  • StarTech.com アダプタ x 2、 DS77U5, Core i5-7200U 2.50GHz, Ubuntu 20.04, Ubuntu 同梱 aqc111 ドライバ
    • Linux 標準のブリッジにより libvirtd 上の仮想マシン(qemu+kvm, 3vCPU, Ubuntu 20.04)に NIC を見せる
    • 二つの StarTech.com は DS77U5 親機側では別物として扱ってブリッジも別々に作り、パケットの転送は VM 側で行う
  • QNAP QNA-UC5G1T, ThinkPad W550s, Core i7-5600U 2.60GHz, Windows 10 1909
  • MTU=9000 (Windows のプロパティの Jumbo Packet では 9014 Bytes)
  • Windows 環境では Large Send Offload V1, V2 (IPv4) を Disable
  • この前の段階でも iperf の並列度(-P) 1 で 3Gbps を超える速度が出たりしていたが、同じ条件でも再度試すと出なかったり、疑問だったので、この条件を決めてからは iperf の並列度は合計の速度が上がらなくなるまで上げる
  • この条件で、 ThinkPad X1 Extreme – Sonnet Solo10G – StarTech.com アダプタ1 – DS77U5 – StarTech.com アダプタ2 – QNAP QNA-UC5G1T – ThinkPad W550s と接続し、 DS77U5 上の VM で 3Gbps 程度でのパケット転送が可能か、試した。

    まず、/etc/sysctl.conf に

    net.ipv4.ip_forward = 1
    

    を追記し、 sysctl -p /etc/sysctl.conf を実施して反映して、別々のサブネット間をそのまま転送したところ、並列度を挙げれば 3.5Gbps 程度まで達した。

    次に、

    iptables -t nat -A POSTROUTING -o ens9 -j MASQUERADE
    

    (ens9 はここでは iperf -s をしているマシンと繋がっている NIC)を実施して、最低限の NAT を行うようにしたところでも、やはり並列度を上げると 3.5Gbps 程度まで達した。3vCPU を付しているものの、 VM の top で見たところでは、(3vCPU 全体を 100% とみなした割合で)3Gbps 以上で NAT 中でも idle が 95% 以上となっていた。

    もちろんこれではファイヤーウォールとしての機能は弱く、ここから iptables の設定を増やせば速度の低下は予想されるものの、この結果からは、ここで購入した機器を利用して、i5-7200U と同等かそれ以下のマシンを利用し、 3Gbps 以上のパケットを転送することができる可能性がありそうに思える。

    SECCON 2019 国内本選 writeup

    チーム「yharima」で、 SECCON 2019 の国内本選に出ていた。結果は 7 位。僕自身は、比較的解きやすい問題に手を付けたものの、それでも難易度が高くなかなかに手が出せる箇所が少ない状態が続いていた。解いたものについて write up。

    Factor the flag

    大きな数が一つ書いてあり、「大きな素数の中にフラグを隠した」と問題文が主張しているので、まずは素因数分解を試みた。13 と 97 で割れたが、その後残った数字の約数はざっくり探した範囲だと見つからなかった。factordb は「Probably Prime Number」という判定になっており、ほとんどの桁が 1 か 9 で例外は最後の 3 桁のみだった。

    色々試みたものの、数全体をバイナリと解釈したり 1 と 9 を二進数の二文字とみなしたりしても “SECCON{” に当てはまりそうな解釈の仕方も見当たらなかった。ふと、 9 がたいてい何回か連続していて 9 が妙に少ないことなどが気になったのと、テキストエディタで狭目の幅で開いたりすると微妙にパターンがありそうに見えたので、 1 と 9 は白と黒として解釈することを試そうと思い、幅を変えながら 1 をスペース、9 を # にして表示していくと、途中でフラグが出てきた。

                                                                                                     |
                                           ##                                                 ##     |
     ###  #####  ###   ###   ###  #   #   #   #####  ###    ##   ###   ###  #####       ####    #    |
    #   # #     #   # #   # #   # ##  #   #   #     #   #   ##  #   # #   #     #       #   #   #    |
    #     #     #     #     #   # # # #   #   #     #   #  # #  #   # #   #    #        #   #   #    |
    #     #     #     #     #   # #  ##   #   ####      #  # #      #  # #     #  ####  #   #   #    |
     ###  ####  #     #     #   # #   # ##        #    #  #  #     #    #     #   # # # ####     ##  |
        # #     #     #     #   # #   #   #       #   #   #  #    #    # #    #   # # # #       #    |
        # #     #     #     #   # #   #   #   #   #  #    #####  #    #   #  #    # # # #       #    |
    #   # #     #   # #   # #   # #   #   #   #   # #        #  #     #   #  #    # # # #       #    |
     ###  #####  ###   ###   ###  #   #   #    ###  #####    #  #####  ###   #    # # # #       #    |
                                           ##                                                 ##     |
    
    import sys
    import math
    
    
    num=1401111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111222079111111111111111111111111111111111111111111111222079112230890319889030880230880230889200120012002319889030879222080230880230890319887911122318879211992120012999912120013000013000013100892001200119912120012002208920013000011991211991112120012001199211991211991211991211991212001301010000120011991212001209300092001300001199211991111212001200119921199121199121199121199121200130010208012002318879112120929999112120929999121210318889200120011991223089031888919912119912120013000013100791111211992120920919912119921199121200130101111887912220791121299991211991211991212001300001200119911121200120012091992119921299992120013010099991112119911112129999121199121199121200130000120012001200120919922319888919921200120919921301009999111211992120012999912120013000013000013000012001200120012999911112120919912120012091992130100999911121199122308903198890308802308802308892001200119922308903198879212103198890308801199213010099991112119911111111111111111111111111111111111111122207911111111111111111111111111111111111111111111122207911111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111239593
    
    i = 1
    while num != 1 and i < 100000:
        i += 1
        while num %  i == 0:
            print i
            num = num / i
    
    cand_ramaining = -1
    
    for i in range(0, 256):
        if (i * ord('}')) % 256 == num % 256:
            cand_remaining = i
    
    #print cand_remaining
    print num
    
    s = str(num)
    
    for i in range(2,len(s)):
        print i
        for j in range(1,len(s)):
            if s[j] == '9':
                sys.stdout.write('#')
            else:
                sys.stdout.write(' ')
    
            if j % i == 0:
                sys.stdout.write('|\n')
        a = raw_input()
    
    num2 = 0
    while num > 0:
        d=0
        if num % 10 == 9:
           d=1
        num2 = num2 * 2 + d
        num /= 10
    
    num2 = num2 / 128
    a = []
    while num2 > 0:
        a.insert(0, num2 % 256)
        num2 = num2 / 256
    
    #for i in range(0, len(a)):
    #    sys.stdout.write(chr(a[i]))
    exit()
    
    lo = 1
    hi = num
    
    while lo < hi:
        mid = (lo + hi ) /2
        if mid * mid == num:
            lo = mid
            hi = mid
            break
        if mid * mid < num:
            lo = mid + 1
        else:
            hi = mid - 1
    i = hi + 1
    
    while num != 1:
        while num %  i == 0:
            print i
            num = num / i
        i -= 1
    

    syzbot panic

    基本的に syzbot 関係の問題が書いてあり、 Q1 – Q5 の答えをつなぎ合わせたものがフラグ、という問題。Q1 は syzbot 自体の管理アプリの URL (の FQDN)を答える問題で、 Q2-Q5 は syzbot で見つけたバグや syzbot 自体に当てられたパッチのハッシュの先頭 10 文字を答える問題。

    Q1 は syzbot で検索すると https://syzkaller.appspot.com/ にあるようだったが、 https://www.syzkaller.appspot.com/ にもヒットした。ただし、 www. がついている方がサーバが見た目としては FQDN っぽさはあるものの、後者は TLS 証明書が正しく認識できず、ドメイン名自体は前者の方が正しいと言えるように思えた。

    フラグの形式が SECCON{$answer_for_Q1+$answer_for_Q2+$answer_for_Q3+$answer_for_Q4+$answer_for_Q5}と示されており、これが 73 文字と示されていた。Q2-Q5はそれぞれ git のハッシュの先頭 10 文字なので、数えてみると syzkaller.appspot.com が正解なら “+” は必要、 www.syzkaller.appspot.com が正解なら “+” は結合を示すために書いてあるだけで不要ということになる。+ は直観的には書かないのではないかと思っていたが、わざわざ $ で、文字列に埋め込める変数名っぽい書き方をしているあたりどちらの可能性もあった。結局、二通りならどちらも試せばよいか、ということで提出する時に考えることにした。結論としては syzkaller.appspot.com にして、 + はつける、が正解だった。

    Q2 はマルチスレッド環境下でカーネルのメッセージを正しく読めないものに対する、 Tetsuo Handa 氏によるパッチのハッシュを答えよ、というものだった。

    最初に 検索すると LKML に LKML: Tetsuo Handa: Re: [PATCH] x86: Avoid pr_cont() in show_opcodes() という投稿が出てきて、これがあてはまりそうだと判断していた。その後 Q4 について調べているときに、セキュリティ・キャンプ全国大会2019 解析トラック E5 The SYZBOT CTF 2019.8.5 という記事に関連する記述があることを見つけ、特に Q4 も Tetsuo Handa 氏が関わっている話題であることがわかったこともあり Q2, Q4 ともこの記事の関係者による作問であることが推測できた。

    そのため、 Q2 は x86: Avoid pr_cont() in show_opcodes() · torvalds/linux@8e974b3 のパッチを当初はフラグとして送ろうとしたが、不正解だった。調べなおしたところ、 kernel/git/torvalds/linux.git – Linux kernel source tree に直したら通った。

    Q3 についてはなかなかよくわからなかったが、他の Q について調べている間に他のチームメンバーが BUG: workqueue lockup (2)kernel/git/torvalds/linux.git – Linux kernel source tree が関連しそうとのリンクを見つけて Discord に貼ってくれたので、これで提出した。

    Q4 については Q2 で挙げた セキュリティ・キャンプ全国大会2019 解析トラック E5 The SYZBOT CTF 2019.8.5 が見つかり、これの 6.1 章のリンク先から Re: INFO: task hung in __sb_start_write – Tetsuo Handaprog: sanitize calls after hints mutation · google/syzkaller@06c33b3 に辿り着き、このコミットで提出した。

    Q5 については struct とか syzbot とかのキーワードで色々検索していた結果、 LKML: Dmitry Vyukov: Re: BUG: unable to handle kernel NULL pointer dereference in mem_serial_out というページが見つかり、 Q2 や Q4 と同じく Tetsuo Handa 氏によるメールが出てきたうえに当該の原因の特定はわずか数日前の新鮮なネタであることがわかった。リンク先の https://github.com/google/syzkaller/blob/master/sys/linux/dev_ptmx.txt.warn#L20-L25 自体は該当部分ではなかったものの、ここ一週間とかのことのようなので、この syzkaller リポジトリのコミット履歴を新しいほうから見たところ、 tools/syz-check: add description checking utility · google/syzkaller@64ca0a3 が見つかった。これで提出。

    SECCON{syzkaller.appspot.com+15ff2069cb+966031f340+06c33b3af0+64ca0a3711}

    壱(1)

    Jeopardy 以外の問題は記憶の限りでは名前はなく、番号だけだったが、一度 TCP に繋いだ後 UDP のパケットを指示に従って送り続ける問題。

    問題のサーバのチームごとに一位に定まるポート番号(yharima チームは 25000)に接続する案内と、プロセスのバイナリが置いてあり、問題文に「全チームが点数を得られなくなった場合とトラブルを除いて、チームのサービスプロセスがおかしな状態になってもプロセスは再起動しない」旨の警告があった。

    実際に 25000/tcp に接続すると、トークンを聞かれ、適当に答えると次につなぐべき(今度は UDP の)ポートを案内された。またこの時点で、 Attack Flag は降ってきた(Attack Flag: SECCON{P13453 +4K3 C4R3})。しかし、一度繋いでポート番号を得るとそのチームは次に 25000/tcp に繋いでも何も出ないようだ。

    案内された次に繋ぐべき UDP ポートにトークンを送るとまた次のポートが案内され、しばらく手動で繰り返すと “You Lose!” という案内が来た。

    バイナリを読んでいた yuta1024 とこの話をしていたところ、どうやら制限時間があるようだった。

    しかし、このプロセス、一度他のポートで待ち受けている状態になると 25000/tcp では繋がりすらせず、さらに言えば You Lose! のあとも(この時点で逆アセンブリされたコードを読んだ yuta1024 は You Lose! のあとは元に戻るはずと言っていたし、後から Hopper で逆アセンブルした僕が見ても元に戻るはずだと判断したが、この時は何が起こっているかわからなかった)一切応答がなくなって何もできなくなった。再起動はしない、の警告の理由がようやくわかったが、要はどうにもならないのか、とこの時は判断した。しかし、しばらく放置するとなぜか接続が復活した(今から考えればプロセスが再起動されていると判断されることが、これに限らず何度かあったが、条件は謎)。ただ、どういう条件で復活するのかは全くわからなかった。幸い、 25000/tcp が割り当てられたチームに対しては 25000-25999 の udp しかこないことだけはわかった。

    とはいえ、偶然の復活に期待して進めていたところ、ある程度自動化して20秒(これが繰り返すと縮まることを、この時はまだ知らない)以内に10回返信すれば、最初に指定したトークンが Defense Flag (5分ごとにチームごとの Defense Flag が特定の場所にあるかチェックされ、特定の場所に含まれていると点数が入る。チェック作業が完了するとチームごとに書き込むべき Flag は変更される。これ自体は参加者全員に対し、全チーム分が公開情報。)として追記される。すなわち、解き方自体は、「自分のチームの Defense Flag を取得し、それを認証トークンとして 25000/tcp に投げたあと、指示されるポートに投げては返信されるポートに追随する」を繰り返すことであることがわかった。ただし、一日目はサーバの状態を見失い、できることがなくなった。

    二日目の開始時刻である 10:00 になり、一度でもおかしなことをすると二度と解けなくなるリスクを認識していたのでプロセスが不完全な状態でパケットを投げることを恐れてしばらく待ってからここまでの成果を投げたところ、これはうまくいった。しかし、しばらくするとこれも動かなくなっていた。いつの間にか制限時間が20秒から1秒になっていた。結局色々追うと、プロセスが生きている限り一度繋ぐたびに制限時間が1秒ずつ減る(1秒になったらそれ以上は減らない)ようだった。

    また、見失う現象についてもわかったことがあった。 25000/tcp で accept して、次のポートを案内した後は、 10 回謎ポートでトークンつきでノックするまで 25000/tcp で次の accept をしないので、変なパケットがサーバ側のキューに残っているとクライアント側でまた新規受付扱いされてしまい、10 回謎ポートをやり直ししないといけなかった。幸い、 25001-25999 であることはわかっていたし、運よく再度つながる状態に戻ったタイミングの繰り返しで培った謎ノウハウでトークンもローカルのファイルに都度保存するようにしたので、 25001-25999 の全ポートにトークンを投げつつ一度でもヒットすればあとは次のポートへの接続をする「復活スクリプト」も温まってきた。偶然失敗したがポート番号だけはわかっている場合用に、もっと速いスクリプトも用意した。少しミスしただけでも 1000 ポートに対する試行、しかも失敗した時点での Defense Flag の候補が複数ある場合はその数だけ試す必要があるが、それでも手も出せない状態と違って自力で復活を試みれた。

    ただし 1 秒の制限は結局突破できなかった。2秒になるまでは進んだし、その状態でローカル計測で運が良ければ 0.9 秒までは短縮できたものの、実際にそれで 1 秒制限を通すことはできなかったようで、制限が 1 秒になってからは一度も突破できなかった。

    CTF オンサイト会場は東京の秋葉原にあり、チームに割り当てられた IP アドレスはオンサイト会場に設置されたチーム用のルーターから接続する必要があった。一方でこの LAN からインターネットに出た場合のグローバル IPv4 アドレスは whois でも SAKURA-ISHIKARI という netname がついており、 CTF のサーバもこの部屋のネットワークの経路も何もかも北海道石狩市にあることが容易に想像できた。この問題のサーバへの RTT も 20ms を超えており、加えて前のポートへの接続で次のポート番号を経てから 59ms 程度 sleep しないと次のポートへの接続が失敗することがある(次のポート番号を送ってから、実際に bind するまでに時間がかかる。UDP のためクライアント側もそれに気づかない。短めでも毎回は起こらないが、なんとか一度も起こさず通せると判断したラインがローカルでは 59ms だった)ことも試行でわかり、それだけで 10 往復で 0.9 秒以上かかるのでかなり絶望的だった。何度かローカル計測で 0.95 秒未満までは達成した気がするが、結局のところ、ラップトップの省電力を解除したり、仮想マシンのネットワークを NAT からブリッジに切り替えたりしても、ついに 1 秒制限で実際に通すことはできなかった。シェルで書いていたかつ失敗の復旧に時間がとられていて C 言語への書き換えなどの作業をするどころではなかったが、何らかの書き換えを行い、投機的にパケットを何度か投げて全部のパケットを後から回収すれば 1 秒制限でも可能性はあったかもしれない。そしてそうこうしているうちにうちのチームのサーバがおかしな状態になり、どの defense flag を token としているうちにおかしくなったのかもよくわからなくなり色々試しているうちにコンテストが終わってしまった。

    最終的に使った(2秒制限までは通ったが、1秒制限は通らなかった)コードは以下の通り。

    #!/bin/bash -x
    
    sync
    
    # PHPSESSID は今となっては何の価値もないセッション ID のはずではあるが、コンテストの説明で何度もスコアページの権限を厳重に管理するよう何度も二か国語で説明があったので、コンテストの趣旨に則り伏せた。
    defense_key=$(curl -b 'PHPSESSID=CENSORED' http://score-dom.seccon/flagwords/ | grep yharima | grep -v 'Team Name' | sed -e 's/<th>yharima<\/th><td>//' -e 's/<\/th><\/tr>//')
    
    echo $defense_key
    echo $defense_key >> defense_keys.txt
    
    echo $defense_key | timeout 2 nc 10.1.1.1 25000 > output.txt
    cat output.txt
    cp output.txt output2.txt
    cat output.txt >> output_all.txt
    
    port=$(cat output.txt | grep 'first port is' | sed -e 's/^.*first port is //' -e 's/, time .*$//')
    echo $port
    
    start_time=$(date +%H:%M:%S.%N)
    
    while true
    do
      flag=F
      for i in 1 2 3
      do
        portstr=$(echo $defense_key | timeout 0.11 nc -u 10.1.1.1 $port | dd bs=18 count=1)
        #echo $portstr >> /dev/shm/output_all.txt
        if [ "a$portstr" != "a" ]
        then
          break
        fi
        echo -e '\e[31mRETRY\e[m'
      done
      port=$(echo -n $portstr | tail --bytes +14)
      echo $port
      if [ "x$portstr" = "x" ]
      then
        echo -e '\e[31mSURVEY NEEDED\e[m'
        exit 1
      fi
      if [ "$portstr" = "You Won!" ]
      then
        echo $start_time
        date +%H:%M:%S.%N
        echo -e '\e[32mSUCCEEDED\e[m'
        exit 0
      fi
      if [ "$portstr" = "You Lose!" ]
      then
        echo $start_time
        date +%H:%M:%S.%N
        echo -e '\e[31mYOU LOSE\e[m'
        exit 1
      fi
      sleep 0.059
    done
    

    なお、懇親会中に、このコンテストに関わった Network Operation Center の方々の紹介の時間があり、その際に改めて思い出したが、このコンテストのネットワーク環境は極めて安定しており、快適だったと感じる。インターネット接続でストレスを感じることがなかったし、コンテスト関係のシステムに繋ぎに行くのにもストレスを感じることもなかった。しかし、改めて思えば、東京都から北海道石狩市は通常でも 20ms 以上の RTT が必要な距離なのに、 UDP の問題を純粋に解くコードも回復スクリプト等も timeout 0.12 nc -u 10.1.1.1 $port_num のような感じで「東京秋葉原から UDP パケットを投げ、 120ms 以内に石狩市のサーバから返事が返ってこないと失敗扱い(しかも、失敗が正しいと見做して次に進むので、あとになって結果が間違いとわかる)」という処理を乱発しても、気付く範囲での問題が発生しない程度の安定したネットワークが保たれていた。コンテスト中はぎりぎりまで詰めることしか頭になかったが、冷静に考えたら 120ms で何もかも捨てるスクリプトは色々おかしいし、これが動いたのはネットワークが相当安定していたからだと思う。このコンテストの関係者の方は全員に感謝したいが、中でもこのネットワーク管理(しかも今回からクラウド移行したという)の関係の方には賛辞を送りたいと思う。

    (追記)チームメンバーの write up

    SECCON CTF 2019 国内決勝 writeup – yuta1024’s diary
    SECCON 2019 国内決勝 writeup – liniku’s blog