「Stack Overflow の機械翻訳サイトの除外用フィルタ」の管理をしていてわかったこと

こんにちは。ublacklist-stackoverflow-translation を管理している @shora_kujira16 です。

github.com

このリポジトリはよく目につく Stack Overflow の機械翻訳サイトがまだ両手で数えられるほどしかなかった2019年10月に公開を始めたものです。利用者数を計測することはできていないのですが、GitHub のスターの数などから推測するに今年の1月ごろから多くの方にご利用いただいているようです。

リポジトリを公開してから1年半ほど経ち、今では多くの方から継続的にプルリクエストをいただくようになりました。この記事ではこのリポジトリを管理していてわかったことや今後の展望についてまとめます。

対象ドメインの数

「Stack Overflow 機械翻訳」のようなキーワードで Twitter を検索してみることがあるのですが、リポジトリの公開を始めた2019年10月ごろと比較すると「機械翻訳サイトを見かけることが増えてきた」という旨のツイートの件数が増えているように思われます。

以下のグラフはフィルタのエントリである uBlacklist.txt の月ごとの件数*1を表したものです。

f:id:kujira16:20210502031251p:plain

2020年10月からの比較で約3倍に増えているということがわかります。フィルタのメンテナンスはあくまで利用者が機械翻訳サイトの存在に気づいた時点で行われるため、このグラフをもって機械翻訳サイトの立ち上げが増えていると結論付けることは難しいですが、目につく件数が増えているとみなすことはできるかもしれません。

最近追加されているサイトの傾向

プルリクエストの内容は毎回確認してから取り込むようにしています。その作業の中で気づいたことを紹介します。

既存サイトのドメイン変更やリダイレクト専用サイトの追加

SEO のテクニックなのか何なのか詳細はわかりませんが、似たようなドメインを大量に取得して同じ内容を配信したり、ひとつのドメインにリダイレクトして集約するような構成にしているサイト群がいくつか見つかります。

例としては以下のようなサイト群です。

stackovernet.com
stackoverrun.com
stackovernet.xyz
www.it-swarm-ja.tech
www.it-swarm.jp.net
www.it-mure.jp.net
www.it-swarm-ja.com
www.it-swarm.com.ru
www.generacodice.blog
www.generacodice.it
www.generacodice.com

このようなサイトは油断した隙にドメインが増えていることがよくあります。利用者の方でフィルタに引っ掛からないドメインが増えていることに気づいた方はお気軽にプルリクエストでご連絡ください。

CMS を利用して構築されたサイトの登場

evidence.md というファイルの Page という列に書かれている URL を上から順番に開いてみてください。内容は別として、上のほうに掲載されている老舗のサイトはデザインも悪くなく出来のいいものが多いと感じる方も多いのではないでしょうか。

一方、下のほうに掲載されている最近追加されたサイトについてもいくつか開いてみてください。最近は CMS として WordPress を利用して機械翻訳サイトを量産することが流行しているらしく、デザインが破綻しているものもいくつか見つかります。

このようなサイトの存在は #45, #46 で知りました。今後の傾向にも注視していきたいと考えています。

今後の展望

Stack Exchange 以外のサイトへの対応

#50GitHub の、#57 で teratail の機械翻訳サイトの存在を指摘されました。

このフィルタのほとんどの利用者にとってはこのようなサイトも除外リストに加えることが有益であると想像していますが、このリポジトリでは Stack Exchange の翻訳サイトのみを対象とすることとして今のところは採用しない方針にしています。これは悩ましいグレーゾーンの判断のために時間を要するものが増えてくると管理の負担が増えてくることを危惧しているためです。

一方、このようなサイトが増えてきた場合には私自身が利用するためにもフィルタを開発する必要があると考えており、目安として4つ以上のサイトが発見された場合には新しいリポジトリの運営を始めたいと考えています。

メンテナンスにおける工夫

リポジトリを公開した時点ではフィルタの本体である uBlacklist.txt と README しか無いリポジトリでしたが、エントリが増えてくるにつれて以下のような改善がありました。

フィルタへの収録根拠の明示

フィルタへの収録の根拠として元となった Stack Overflow のページと、そのページの機械翻訳ページの URL を evidence.md に記載しています。「このドメインって本当に機械翻訳サイトだったっけ?」と不安になることが増えてきたのでエントリが20件を超えたあたりで作成しました。

このファイルのメンテナンスには手間がかかりますが、フィルタを安心して利用してもらうためには根拠を明示することが重要であり必要なコストであると考えています。

CI によるメンテナンス作業の補助

上記の evidence.md と uBlacklist.txt の整合性を保つのが面倒になってきたため #33 で協力を募って #48 で開発していただいたものです。domain-list.yml という YAML を編集してコミットすると GitHub Actions により evidence.md と uBlacklist.txt を更新したコミットが自動的に作られるようになっています。

このような改善によりメンテナンス作業の負担が軽減されました。一方、エントリ数が数百に達してきた場合にはさらに別の課題が出てくるのではないかと感じており、今後の対策について検討しています。

まとめ

この記事では ublacklist-stackoverflow-translation を管理していてわかったことと今後の展望についてまとめました。私の生活に余裕がある間はメンテナンスを続けたいと考えているため今後も安心してご利用ください。

*1:正確には毎月の最終コミット時のファイルの行数

ICPC 模擬国内予選 2020 スタッフ参加記

問題

あなたは ICPC OB/OG の会が開催する ICPC 模擬国内予選 2020 のスタッフとして、選手に詳細案内を送付する仕事を任されている。

詳細案内は、模擬国内予選で使われるジャッジシステムの URL とログインに必要なチーム番号、パスワード、そしてチーム名と所属を含む文字列であり、与えられたテンプレートにチームごとに異なる文字列を埋め込むことによって表される。

あなたは詳細案内を「すごく難しい天才以外お断りプロトコル」(SMTP) を使って送付しなければならない。 プログラマーであるあなたは SMTP を使って詳細案内を送信するプログラムを作成することにした。

入力

入力は以下の形式で表される

N
A_1 T_1 S_1 I_1 P_1
A_2 T_2 S_2 I_2 P_2
...
A_N T_N S_N I_N P_N
L
M_1
M_2
...
M_L

最初の行は送付先のメールアドレスの数 N (1 ≦ N ≦ 103) を表す整数からなる。

2行目から続く N 行はそれぞれ i 番目の詳細案内の送付に必要な情報としてメールアドレス A_i、チーム名 T_i、所属 S_i、チーム番号 I_i、パスワード P_i を表す文字列からなる。

N + 2 行目は詳細案内のテンプレートの行数 L (1 ≦ L ≦ 103) を表す整数からなる。続く L 行は詳細案内のテンプレートを表す文字列を表す。このテンプレートのいずれかの行は、チーム名を埋め込むべき箇所を表す $team、所属を埋め込むべき箇所を表す $affiliation、チーム番号を埋め込むべき箇所を表す $login、パスワードを埋め込むべき箇所を表す $password を含む。テンプレートの文字列は 100 KiB を超えない。

出力

出力はない。

得点

模擬国内予選の終了までに「詳細案内のメールが届いていない」という旨の連絡を受け取った数を X とする。N - X があなたの得点となる。

入力例

6
king-1@example.com ___KING___ The_University_of_Tokyo 1 aaaaaaaa
king-2@example.com ___KING___ The_University_of_Tokyo 1 aaaaaaaa
good-yamikin-1@example.net good_yamikin Tokyo_Institute_of_Technology 2 bbbbbbbb
good-yamikin-2@example.net good_yamikin Tokyo_Institute_of_Technology 2 bbbbbbbb
good-yamikin-3@example.net good_yamikin Tokyo_Institute_of_Technology 2 bbbbbbbb
gazeru-1@example.org gazeru Kyoto_University 3 cccccccc
11
ICPC 国際大学対抗プログラミングコンテスト模擬国内予選 2020
チーム $team ($affiliation) の皆様
模擬国内予選への参加登録ありがとうございます.
このメールでは自動審判システムの情報についてお知らせします.
今年も自動審判システムとして国内予選本番で使われるものと同様の Web ベースの審判システムを利用します.
自動審判システムへのログインには,チーム番号とパスワードが必要です.
チーム番号: $login
パスワード: $password
審判システムの URL はこちらです.
    http://icpc2020.jag-icpc.org/icpc2020/common/login_ja.php
参加者は事前にチュートリアルにもよく目を通しておいてください.

部分点解法

JAG で運用しているサーバーで動いている Postfix を使ってメールを送信する方法が愚直解として考えられます。事実として、数年前まではこの方法でも問題なくメールを配信できていました。しかし昨年の模擬国内予選では「メールが届いていない。迷惑メールフォルダにも入っていない」という問い合わせが殺到してしまいました。さらに、去年までは1チーム1メールアドレスまでの登録としていたところ、利便性のため今年は1チーム3メールアドレスまで登録できるように変更したため、問い合わせがさらに増えることが予想されました。そのため今年は別の方法を探すことを余儀なくされました。

部分点解法の問題点

GmailOutlook.com をはじめとする多くのメールサービスプロバイダーでは、以前にも増して悪意あるメールへの対策が厳しくなってきています。悪意あるメールを判定する方法としてはメールの内容を調べることも重要ですが、「怪しい IP アドレス」から送信されているかどうか、という特徴量も強い影響を持っています。「怪しい IP アドレス」からメールが大量に送信されている場合、受信側のメールサーバーではそのメールを迷惑メールフォルダに隔離することさえなく捨ててしまう場合もあるようです。

この IP アドレスの怪しさは「レピュテーション」(評判)と呼ばれています。レピュテーションの算出方法はメールサービスプロバイダーの実装に依存し、悪用を防ぐため通常は公開されていませんが、一般論として以下のような要素が考慮されていると言われています*1

  1. 存在しないメールアドレスにメールを送ってくる IP アドレスはレピュテーションを低くする(無差別にスパムメールを送信している疑いがあるため)
  2. ユーザーがよく「迷惑メールを報告」ボタンを押すようなメールを送ってくる IP アドレスはレピュテーションを低くする
  3. スパムメール業者がよく利用しているホスティングサービス(レンタルサーバーや IaaS)の IP アドレス帯はレピュテーションを低くする
  4. これまであまりメールを送ってこなかった IP アドレスから急にたくさんのメールが送られてきたら警戒する

JAG で運用しているサーバーからメールを送信する方法は 3. と 4. の観点で大きく不利でした。3. については現在利用しているホスティングサービスのレピュテーションがどうなのか私は知りませんが、一般論としてレンタルサーバーに付与される IP アドレスは基礎的なレピュテーションにあまり期待できません。4. についてはかなり致命的で、平常時は1週間のうちにせいぜいに10通程度のメールしか送信していない IP アドレスから数時間のうちに何百通もメールが届くようなことがあれば、ブロックするように実装するのは妥当な考えでしょう。

満点解法

地道にメールの送信数を増やして JAG のサーバーの IP アドレスのレピュテーションを高くするというのが正攻法ですが、模擬国内予選は1年に1回の開催のためこの方法をとることはできません。

代わりに SendGridAmazon SES といったメール配信サービスを使います。これらのサービスを使ってメールを送信すると、高いレピュテーションを持った IP アドレスからメールが送信されるため、メールの到達率が格段に上昇します。

悪意のある利用者がメール配信サービスを使ってしまえばレピュテーションの意味がなくなってしまうのではと思われるかもしれませんが、メール配信サービス側では以下のような対策が行われています。

  • 利用に事前審査が必要
  • 送信元(メール配信サービス側)でも独自のレピュテーションを計算
    • 送信先のメールアドレスからバウンスメール(いわゆる MAILER-DAEMON)が返ってきたり、迷惑メールとしての通報があるとレピュテーションが低下
    • レピュテーションが一定のしきい値を下回ると送信をブロック。再審査が通るまで利用を制限

今回は SendGrid を利用しました。SendGrid は月に12,000通までの送信は無料で利用できるというのが大きな利点です。また、これは ICPC 特有の事情ですが、SendGrid の日本の販売代理店である構造計画研究所は以前に ICPC のスポンサーを数年間していたことがあり、もし審査で渋い反応をされたときでもアピールしやすいのではないかということもありました。

結果的に、この方法は功を奏し、数百通のメールを配信することができました。今年も「メールが届いていない」という問い合わせは何件かありましたが、申し込みに使っていたメールアドレスを勘違いしていたというような原因によるものであり、SendGrid 側でなにか問題が起きていたという事例はありませんでした。さすが大手のメール配信サービスだなという感想です。

おわりに

最近はコンテストにはあまり参加できておらず問題を作るのも解くのも現役のときと比べると難しさを感じており作問作業ではほとんど貢献できていなかったのですが、本業の知見を活かしてインフラ面での貢献できたのはよかったです。

dm-ebs で 4Kn ディスクの論理セクターサイズを 512 バイトに見せかける

ストレージ製品には論理セクターのサイズが 512 バイトのもの (512e) と 4K バイトのもの (4Kn) があり、混在している構成のシステムでは非常に厄介な問題を引き起こすことがあります。最近 Linux カーネルにマージされた dm-ebs という device mapper を使うと 4Kn のディスクの論理セクターサイズを 512 バイトに見せかけることができ、問題が発生した場合のワークアラウンドとして使うことができます。

注意

dm-ebs は Red Hat の開発者によって開発され Linux 5.8 に正式に含まれる可能性が高い機能ですが、開発されたばかりであるため利用の際は慎重な評価を行ってください。

発生する問題を確認する

512e のディスクの上に XFS のファイルシステムを作成したとします。今回は実験用として losetup コマンドで作成したループバックデバイス /dev/loop0 を利用します。

f:id:kujira16:20200802174759p:plain

バックアップためにこのディスクの中身をブロックデバイスレベルでファイルシステムごと別のディスクにコピーしたとします。このときコピー先のディスクが 4Kn だったとします。

f:id:kujira16:20200802175246p:plain

ここで もとのディスクが壊れてバックアップしたディスクをマウントしようとすると失敗します

確認してみましょう。まず losetup で 512e のブロックデバイスを作成して、その上に XFS のファイルシステムを作成します。

$ fallocate -l 128M disk.img
$ sudo losetup -f disk.img
$ lsblk -t
NAME   ALIGNMENT MIN-IO OPT-IO PHY-SEC LOG-SEC ROTA SCHED       RQ-SIZE  RA WSAME
loop0          0    512      0     512     512    1 mq-deadline     256 128    0B
...
$ sudo mkfs.xfs -f /dev/loop0
meta-data=/dev/loop0             isize=512    agcount=4, agsize=8192 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=0, rmapbt=0, reflink=0
data     =                       bsize=4096   blocks=32768, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=855, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
$

/mnt にマウントして適当に何か書いておきます。

$ sudo mount /dev/loop0 /mnt/
$ ls /mnt/
$ echo "Hello, world!" | sudo tee /mnt/hello.txt
Hello, world!
$ cat /mnt/hello.txt
Hello, world!
$ sudo umount /mnt
$ sudo losetup -d /dev/loop0
$

先ほど書き込んだデータを含む disk.img を 4Kn のブロックデバイスと見なして、再びマウントしてみましょう。losetup コマンドの --sector-size オプションは Linux 4.14 以降でしか使えない機能であることに注意してください。

$ sudo losetup --sector-size 4096 -f disk.img
$ sudo mount /dev/loop0 /mnt/
mount: /mnt: mount(2) system call failed: Function not implemented.
$ sudo dmesg | tail -n1
[  804.657310] XFS (loop0): device supports 4096 byte sectors (not 512)
$

マウントに失敗しました。mkfs.xfsファイルシステムを作成したときに何もオプションを設定しなかったため sectsz=512 としてフォーマットされていましたが、そのファイルシステムが今では 4Kn のブロックデバイス上に存在するので、サポートされていないセクターサイズ(512 バイト)での読み書きを防ぐためマウントできなかったようです。

このような問題が起きるとバックアップからファイルを読み出すことができなくなり困ります。

dm-ebs を使ってみる

4Kn のディスクに dm-ebs を重ねて 512e のディスクと見せかけることで XFS をマウントできるようにします。

f:id:kujira16:20200802184108p:plain

drivers/md/dm-ebs-target.cLinux 5.8 の rc にマージされた機能なので、Ubuntu では現時点の最新の LTS に含まれるカーネルを使ったとしても利用することができません。そのため Ubuntuカーネルチームが提供している mainline ビルドを利用しました。

gihyo.jp

現時点では Linux 5.8 は正式リリースされていないので v5.8-rc7 を使うことにしました。https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.8-rc7/amd64/ から以下のファイルをダウンロードしてインストールしました。

  • linux-image-unsigned-5.8.0-050800rc7-generic_5.8.0-050800rc7.202007262231_amd64.deb
  • linux-modules-5.8.0-050800rc7-generic_5.8.0-050800rc7.202007262231_amd64.deb
  • linux-headers-5.8.0-050800rc7_5.8.0-050800rc7.202007262231_all.deb
  • linux-headers-5.8.0-050800rc7-generic_5.8.0-050800rc7.202007262231_amd64.deb

セクターサイズが 4K バイトの /dev/loop0 の上に dm-ebs を重ねることで、セクターサイズが 512 バイトの hello というデバイスを作ります。

$ DEV=/dev/loop0
$ echo "0 $(sudo blockdev --getsz ${DEV}) ebs ${DEV} 0 1" | sudo dmsetup create hello
$ lsblk -t
NAME    ALIGNMENT MIN-IO OPT-IO PHY-SEC LOG-SEC ROTA SCHED       RQ-SIZE  RA WSAME
loop0           0   4096      0    4096    4096    1 mq-deadline     256 128    0B
└─hello         0   4096      0    4096     512    1                 128 128    0B
...
$

マウントしてみると何事もなかったかのように読み書きできます。

$ sudo mount /dev/mapper/hello /mnt/
$ ls /mnt/
hello.txt
$ cat /mnt/hello.txt
Hello, world!
$ echo "Hello, dm-ebs!" | sudo tee /mnt/hello.txt
Hello, dm-ebs!
$ cat /mnt/hello.txt
Hello, dm-ebs!
$

現実的に困る状況

512e ディスクと 4Kn ディスクが混在することで困った状況になる現実的な設定として LVM のボリュームグループ (VG) にディスクを追加して論理ボリューム (LV) を拡大した場合があります。この問題は2013年から指摘されていました。

bugzilla.redhat.com

まず 512e のディスクだけで VG を構成して LV を作成し、その上に XFS のファイルシステムを作成したとします。このとき LV の論理セクターサイズは 512 バイトになります。

f:id:kujira16:20200802191929p:plain

この VG に 4Kn のディスクを追加します。

f:id:kujira16:20200802192008p:plain

このあと LV を拡大すると、LV の論理セクターサイズは、LV のエクステントが乗っているディスクの中で最大の論理セクターサイズになります。すなわちこのような状況では 512 バイトから 4K バイトに変わってしまいます。

f:id:kujira16:20200802192320p:plain

このような場合でもやはり XFS をマウントできなくなってしまいますが、LV の上に dm-ebs を重ねることでファイルシステムの読み書きができることを確認しました。

f:id:kujira16:20200802192830p:plain

まとめ

dm-ebs を使って 4Kn のディスクの論理セクターサイズを 512 バイトに見せかけることで問題が発生した場合のワークアラウンドとして使えることを確認しました。