2020年に読んだ本を振り返る

はじめに

2020年の12月には書いていたのだけど、完全に書いたのを忘れていた。

2020年はひと月に一冊は本に触れようという目標を立てていた。

備忘録として今年触れた作品について簡単な所感を並べていく。

こうしてみると、ひと月で読むには無理のある分量の本を買いすぎな気がする。

小説

ロリータ(ウラジーミル・ナボコフ、訳 若島正

ロリータという少女を取りつかれたように愛してしまった中年男性ハンバート・ハンバートの物語。

「恋愛小説であると同時に、ミステリでもありロード・ノヴェルであり、…」という書評のままの内容で、たとえ内容を紹介してみてくださいと言われたとしても答えに窮する感じだった。色んなネタを仕込んでいるのであろう、註釈の量がえげつなかった。

ロリータとの出会いを書いた第一部、いろいろあってロリータと旅をしに出る第二部との二つに大きく分かれるが、個人的に第二部が好きだった。車を走らせながらロリータと古びたラブホテルを点々としていく旅は、始終不穏な終わりしか感じられない。

驟雨・原色の街(吉行淳之介

吉行淳之介の短編集。表題作はいずれも娼婦が関わる作品。ロリータに続けて読んだので倫理観が終わってしまった。

倫理観がどうという話は実際はこの小説を語るのに無粋な問題で、人間の複雑な感情を性を通して異常なほどつぶさに描写しているという印象があった。

「原色の街」という題に似つかわしくない澱んだ色彩と、閉塞感が好きだった。

恐るべき子供たちジャン・コクトー、訳 東郷青児

第一次大戦後のパリが舞台。ポールとその姉エリザベート、友人のジェラール、アガートの4人が織りなす愛憎の物語。

ニンフの視座を持ち合わせていて、現世の人間とはどうあっても交差できない人間が好きで、その例に漏れずこの小説も好きだった。

子供時代にあるコントロールできない暴力性と人生に投げやりな態度がなんとなく好きで、「部屋」を作り出してそこで社会から抜けて退廃的に生きている子供たちの様子が最高だった。

花・死人に口なし 他7編(シュニッツラー、訳 番匠谷栄一・山本有三

死や罪、愛についての感情の機微が丁寧に書かれていて、分かる〜となることが多かった。バカの感想。特に「死人に口なし」の狼狽具合は自分も同じ状況であればそうなるであろうことは想像に難くない。文春砲にあった時の気持ちを味わえた。

「花」「死人に口なし」「わかれ」などで描写される、死人となった方がむしろ生きている人間に多弁に語り掛けてくるような感覚は分かる。

技術書

時系列解析 自己回帰型モデル・状態空間モデル・異常検知【Advanced Python 1】(島田 直希)

ARIMAなどの時系列解析を実装してみたかったので購入。個人的にこういう題だとフルスクラッチで実装するのかと期待してしまったが、ライブラリでの実装だった。マッチングがうまくいかなかった。

数多くの時系列解析の教科書で挫折した初心者の自分にとって、読むにはちょうど良い内容だった。この本のおかげで難易度高めの本もある程度読めるようになった。

時系列解析入門 [第2版]: 線形システムから非線形システムへ (SGCライブラリ 160)(宮野 尚哉, 後藤田 浩)

第1版を図書館で読んだことがあったが、新たに第2版でエントロピーの箇所などが加筆されたと聞いて購入。AR,MA,ARIMAなどについて丁寧に導出されていて非常によく理解できた。カオス理論の箇所はSF小説を読んでいるような気持ちになった。

エントロピーとカオス理論にここまで関連があったのかと驚いた。

個人的にサンプルエントロピーの話が面白かった。

ベイズ深層学習(須山敦志)

ベイズ推定とニューラルネットワークの基礎と、分布サンプリング法、ベイズニューラルネットワークと盛り沢山な内容で非常に勉強になる内容だった。

レビューで初心者でも分かりやすく丁寧と言われていたが、流石にそれは嘘だと思う難易度だった。少なくともベイズニューラルネットワークそれぞれの基礎が無いと理解は進まないと思う。

しかし丁寧であることは紛れもなく真実だった。後半部で出てくる式変形は基本的に全て本書の前半部の内容でカバーされているため、非常に分かりやすかった。いまいち理解が進まない箇所があるが、単に自分に数学的素養がないのが原因で本書の問題ではありません。

個人的に確率分布の式変形をしてカルバックライブラーダイバージェンスと期待値をあっちこっちする部分の数式がパズルのようで面白かった。

ハッカーの学校 IoTハッキングの教科書(黒林檎、村島 正浩)

ハードウェアハッキングの知識が全くなく、勉強したかったため購入した。

インターネットで公開されていたセキュリティキャンプの資料を参考に、たぶんこれじゃないかと推測しながら道具を揃えて、持っていたラズパイでSPIのところまではできた。ケチ臭い性分が発揮されている。念の為、本で得た知識は断じて悪用していませんし、今後決してすることはありません。

テスターなど高価で手が出なかった道具は揃えられなかったのでそこらへんはまた機会があればやりたい。

RFCの読み方―インターネット技術の公式仕様書(瓜生 聖, 秋月 昭彦)

下記記事を書くために図書館で借りた。

RFCに苦手意識があったのだけど、この本のおかげですらすら読めるようになった。

簡単な例から初めて少しずつ複雑なRFCにシフトしていく内容で無理なく知識を身に着けることができた。

2004年の本であるが内容は全く色褪せていないと感じた。

madomadox.hatenablog.com

Distributed Denial of Service Attacks: Real-world Detection and Mitigation (İlker Oezçelik, Richard Brooks)

自分の研究分野に近い内容だったので即ポチした。9千円近くとえらい値段が高かった。この手の値段とフォーマットの英書をよく見かけるけれどある程度こうしたアカデミックな本を発刊する機会があるのだろうか。

DDoSの歴史、ツールの種類、検知方法、緩和方法、実験環境の構築方法など有益な情報があって助かった。これを研究を始めたばかりで無駄サーベイ・開発に費やしてしまった頃の自分に読ませたい。

Deflectという技術があるのを知らなかった。調べてみたがいったん公開停止されているような気がする。

 

積んでしまった本

マシンリソースの不足、興味が持続できなかったという理由でほんの一部だけ読んだだけの本。

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方(Noam Nisan, Shimon Schocken 訳 斎藤 康毅)

低レイヤーに対する苦手意識を克服するべく購入したが途中で苦手意識が肥大化して興味が薄れてしまった。来年できるといいなぁ。

つくりながら学ぶ!PyTorchによる発展ディープラーニング(小川雄太郎)

最新のディープラーニング技術を勉強したくて購入。

マシンリソースが足りず学習ができなかったため1章だけ読んで放置してしまった。

 

プログラミング言語C++ 第4版(ビャーネ・ストラウストラップ、訳 柴田 望洋)

目を通してSTLなどがそういうことだったのか~とはなったが全部は読めなかった。この手の本はリファレンスとして必要に応じて読まないとモチベーションが続かない感がある。プログラミング言語の本は何か一冊持っておくべきということだったので購入したが、本当にその通りだなぁと感じた。言語の特徴などを分からずにぐちゃぐちゃに書いてしまうため。

Effective PythonPythonプログラムを改良する90項目~ (Brett Slatkin、訳 石本 敦夫・黒川 利明)

2章まで読んだけれど、上と同じように感じて持続できなかった。とはいえ目が鱗の情報が多くて買って良かった。

コンピュータネットワーク 第5版(アンドリュー・S・タネンバウム,デイビッド・J・ウエザロール,訳 水野忠則・相田仁・東野輝夫・太田賢・西垣正勝・渡辺尚)

興味のあった輻輳制御アルゴリズムQoSだけ読んで放置してしまった。他の部分も必要に応じて読んだけれども、業務などに利用するには少し古かったりする箇所があるなぁという印象。

トラフィック測定のためのサンプリング技術についてまとめる(Sample and Hold法)

はじめに

ネットワークトラフィック測定技術、ストリーミングアルゴリズムに興味があり色々と調べているのですが、今回はサンプリング技術についてまとめようと思います。
その中でも、2002年に発表されたSample and Hold法を提案している論文[1]を読んだので手法部分にフォーカスしてまとめます。
一部数式の記号を変更している箇所がございます。また、認識に誤りがある箇所がございましたらご指摘いただけますと幸いです。可及的速やかに訂正いたします。

[1] Cristian Estan and George Varghese, (2002). “New Directions in Traffic Measurement and Accounting.” In Proceedings of the Conference on Applications, Technologies, Architectures, and Protocols for Computer Communications, SIGCOMM, pp. 323–336, New York, NY, USA: Association for Computing Machinery.

論文のURLはこちらになります(フリーアクセスです)。
https://dl.acm.org/doi/10.1145/633025.633056

トラフィック測定とサンプリング技術

単純なトラフィック測定方法

ネットワークトラフィックの様態を知るためその特徴量を正確に把握することが求められます。その際、監視対象をフロー単位でまとめることが一般的です。

フローとは、通信の同一性を区別するために用いられる情報です。一般的には「送信元IPアドレス」「宛先IPアドレス」「送信元ポート番号」「宛先ポート番号」「プロトコル(IPヘッダ)」の5つの組で表現されます。

フロー情報で通信の同一性を区別できるので、フロー単位でトラフィックを監視して、単位時間ごとにその出現回数をカウントすれば、ネットワークトラフィックのパケットレートやエントロピー値などの特徴量を計算することができるようになります。なお、次のインターバルに移った時にはカウンタの値はクリアします。

単純な方法での問題点

しかしながら、ネットワークトラフィックの流量は大規模かつ高速であるため、単純な方法で実装してしまうと次のような問題点が発生します。

  • メモリ消費量の問題:出現回数を数えるということはメモリにフロー用のカウンタ(連想配列が主)を用意する必要があります。IPアドレスは理論的に2^{32}種類あることを考えればフローの種類数は膨大であり、出現回数も非常に多いことが予想されるので、メモリを大量に消費する可能性があります。
  • スケーラビティの問題: 1パケット到着するたびにフローの出現回数を更新していくという方法では、単位時間あたりのメモリアクセス回数が膨大に増加してしまい、大量にトラフィックが到着したときに対応できなくなる可能性があります。

サンプリング法

メモリアクセス回数増加とメモリ消費量という課題に対して、サンプリング法が利用されています。サンプリング法を利用したものとしては、NetFlowなどが有名です。

サンプリング法では、サンプリングしたパケットのみを利用してフローの出現回数をカウントします。これにより処理対象となるフロー数が制限されることになりメモリ消費量を小さくできる可能性があります。また、サンプリングされたフローに関してだけメモリ中のカウンタを更新するため、通常の方法に比べてメモリアクセス数も少なくなります。

サンプリング法では単位時間(あるいはバイト数)ごとに、所与の確率p(=サンプリングレート)で、その間に到着したパケットそれぞれについてサンプリングするかどうか決めます。パケットのサンプリングに成功した場合は、パケットのフロー情報を取り出しそのフローに対応するカウンタ(以降フローカウンタ)を作ります。すでにフローカウンタが作られている場合は、出現回数を加算します。サンプリング処理を繰り返していって、フローカウンタを更新していくことで、フローごとの出現回数を算出します。

しかし、当然ながらサンプリングされたパケットのみを監視対象としてカウントしていくので、出現回数の推定値の誤差が大きくなることが予想されます。

Sample and Hold法

Sample and Hold法はサンプリング法の長所はある程度残しつつ、推定精度を向上させるために考案されました。
それでも推定精度にはまだ難はあり、同論文で別の手法も提案されていますが、実装が単純でリソースの消費量も少ないという点が利点です。

Sample and Hold法はサンプリング法と同じく所与のサンプリングレートpでパケットを取得するのですが、その前にエントリが存在しているかどうかを確認します。
そして、フローカウンタが存在する場合は、サンプリングレートに関係なくパケット情報を取得し、対応するエントリを更新します。
フローカウンタが存在しない場合は、通常のサンプリング法と同様の処理を行います。

その違いについて直感的に捉えてみると、単純サンプリング法はすべてをサンプリングするので、真の出現回数との差が大きくなってしまう可能性があります。
一方、Sample and Hold法は一度サンプリングされたフローに関してはそれ以降取りこぼすことがないため、正確性が増します。ただしメモリアクセス回数はサンプリング法に比べて増加することがあります。

Sample and Hold法の理論的エラー率

論文では、その精度についても分析をしています。まずはフロー数の推定精度、そしてフローの観測成功確率をご紹介します。

フロー数についての推定精度

まずはフローの出現回数についての推定精度です。あるフローの実際の出現回数をs、Sample and Hold法による推定出現回数をcとして、その推定誤差を\sqrt{E[(s-c)^{2}}]という式で評価することにします。

このSample and Hold法では一度でもサンプリングされれば情報をずっと保持します。つまり、推定出現回数cであるということは、本来s回観測されるはずだったがそれまでに(s-c)回サンプリングに失敗したということになります。

成功するまでにx = (s-c)回サンプリングを失敗した場合を考えると、このときの確率を(1-p)^{x}pという式で表すことができます。

このときxは幾何分布(Geometric Probability Distribution)に従い、それぞれ期待値 E[x] = \frac{1}{p}、分散はVar[x] = \frac{1-p}{p^{2}}標準偏差SD[x] = \frac{\sqrt{1-p}}{p}となります。

ここで一般的な分散の定義式を変形すれば、二乗誤差の期待値E[x^{2}] = V[x] + E[x]^{2}となるので、

E[x^{2}] = V[x] + E[x]^{2} = \frac{1-p}{p^{2}} + \frac{1}{p^{2}} = \frac{2-p}{p^{2}}

したがって、

\sqrt{E[(s-c)^{2}}] = \frac{\sqrt{2-p}}{p} となります。

観測成功率

メモリ容量的にフローテーブルを持てる数に限りがある場合を考えます。限りがあるということは、フロー数が多すぎると新規にフローテーブルを作れなくなるため、出現回数を全くカウントできないフローが存在する可能性があります。

ここからは、論文に示されている例示を紹介します。

ある測定期間中に、メモリ許容量を1%超える数のフローで構成されたトラフィック流入してきたとし、その1%のフロー数は最大で100あるとします。
この時確保されるであろうフローカウンタは、100%のフロー数が10,000だとすれば、直感的には10,100必要になるはずです。
しかしここでは、フローメモリの限界が10,000だとします。トラフィックがメモリ許容量を超えている時のフローの観測成功率を考えます。

フローカウンタ数は10,000に抑えたいため、もしトラフィックのデータ量がBバイト到着するときは、サンプリングレートp=10,000/Bに設定します(この例、というより論文全体では単位バイト数ごとにパケットのサンプリングを行います)。これで多くの場合は10,000種類のフローだけが取得されるようになります。

この時、メモリ許容量を1%超えるフローをFとして、その観測成功率を求めます。サンプリングレートが10,000/Bであることを考えると、
FB/100バイト以上は送信されていることになります。(Bの1%)。

このFが全くサンプリングされない確率を考えると、 (1-10000/B)^{B/100}となります。
この値は、 B/100 = B^{\prime}とおけば、 (1-10000/B)^{B/100} = (1-100/B^{\prime})^{B^{\prime}} と変形できます。

これを \displaystyle \lim_{x \to \infty} \left( 1+\frac{x}{n} \right)^{n} = e^{x}と照らし合わせてみると、

 (1-100/B^{\prime})^{B^{\prime}} \approx e^{-100}となります。この100は、先程から考えている、メモリの許容量を1%超えたフロー数Fと一致します。

つまり、メモリ許容量を超えたたために全くサンプリングされない確率は、そのはみ出ているフロー数をfとすると、 e^{-f}で近似することができます。
すなわち、メモリの許容量を超えているトラフィックが到着した際に、許容量を超えているフロー数fについてフローカウンタが存在する確率は 1-e^{-f}で近似できます。

では、Fの5%のフロー数(5つ)が送信された時、それに対応するフローカウンタが存在する確率を確認します。

全くサンプリングされない確率はe^{-5}で近似できるということなので、 1-e^{-5}となり、フローカウンタが存在する確率は99%を超えることになります。

(自分の)TODO

※この章は論文著者が示した今後の課題ではなく自分のやり残したことを書いています。

単純な方法では色々と不都合があるので、同論文ではSample and Hold法以外にもMultistage Filter法という手法を考案しています。
また、これらの2つの手法をさらに改善する方法として、3つの工夫にも言及しています。

この記事はSample and Hold法単体に目を向けたものですので、これらについては別の記事でまとめようと思います。

まとめ

ネットワークトラフィックの様態を知るためその特徴量を正確に把握することが求められます。その際、監視対象をフロー単位でまとめることが一般的です。

単純に実装してしまうとメモリ消費量とメモリアクセス回数が膨大になり処理効率が悪くなってしまいます。これを解決するためサンプリング法によって計算対象となるパケット数を減少させる方法が利用されています。

一方、サンプリング手法も推定精度に難があります。そこで推定精度を改善するため、一度サンプリングされたフローはその後常にカウント対象とするSample and Hold法が考案されました。

理論的な推定誤差・メモリ許容量を超えた時にフローカウンタが存在する確率については、論文に詳細に書かれた例示と数式をもとに自分なりにまとめました。

実証実験などの詳細については論文をご覧ください。
(ブログで詳細に正確に書こうとするともはや翻訳のようになり、著作権的にまずいのではないか…というのと、正直しんどいというのとがあります)。

勉強に利用した文献

T. H. Cormen, C. E. Leiserson, R. L. Rivest, (1992). Introduction To Algorithms 1st Edition, MIT PRESS, (T.H. コルメン,C. E. ライザーソン,R. L. リベスト 浅野哲夫,岩野和生,梅尾博司,山下雅史,和田幸一 (共訳) (1995). 『アルゴリズムイントロダクション [第1巻] 数学的構造とデータ構造 』 近代科学社 初版)
※図書館で借りたんですが丁寧に書いてあってめちゃくちゃわかりやすかったです。

NetFilterを利用したパケットフィルタリングプログラミング

はじめに

iptablesについて調べていると,どうやらNetFilterの仕組みを自分でも利用できるということを知りました.
実際にそれを用いてパケットフィルタリングのプログラムを作成します.

作成プログラム

8.8.8.8に対するICMPトラフィックをフィルタリングするプログラム

関連知識

LKM (Loadable Kernel Module)

LinuxのNetFilterの仕組みを用いて自分なりの機能を作成するには,NetFilterに関するLKM(Loadable Kernel Module)を新たに作成してカーネルにロードする必要があります.

これにより,ネットワークスタック関連の機能をフック(プログラムのある特定の箇所に独自の機能を追加・挿入)できるため,マシンにパケットが到着したり,出ていったりする時に動作させたいコールバック関数を実装してフィルタリングすることができます.

LKMはカーネルの機能を拡張するための追加モジュールです.

Linuxシステムプログラムに新たに機能を追加・変更しようとすると,カーネルのプログラム全体をリビルドすることになり,効率が悪くなってしまいます.

そこで,変更頻度が少ない基本的なモジュールは元々カーネルに組み込んでおいて,変更頻度の高いモジュールはLKMを用いて導入することにします.LKMでは動的にモジュールを追加できるため,カーネルのプログラム全体をリビルドする必要が無くなります.

LKMをロードすることで,ロードされたモジュールは特権モードで実行され,システムのハードウェアを利用することができます.アンロードも同様に可能なので,必要なくなったら削除することもできます.

LKMについては下記が詳しいです.
www.atmarkit.co.jp

NetFilter

NetFilterはネットワークスタック関連の機能をフックできるLinuxカーネルフレームワークで,主にパケットフィルタリングに利用されます.

Linux関連でのファイアウォールのツールとして一般にiptablesが利用されていますが,それはNetFilterの機能を利用して実装されています.

NetFilterと,フック可能なタイミングについては下記が詳しいです.
qiita.com
wiki.bit-hive.com

実装

プログラムがカーネルに組み込まれるため,あんまり変なフィルタリングの設定をしてしまうと普段使いの際に悪影響を及ぼします.
また,できる限り自前で持っているプログラムを用いて動作確認できる方が望ましいと考えました.
そのため,自システムから8.8.8.8に対して送信されるICMPトラフィックをフィルタリングするようにして,pingコマンドを用いてそれを確認します.

環境

カーネルのバージョン

5.4.0-47-generic

OSのバージョン

Ubuntu 20.04 LTS

ソースコード

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ip.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/netfilter_ipv4.h>
#include <linux/skbuff.h>

MODULE_LICENSE("GPL");

//登録するコールバック関数の定義
//sk_buff型の変数skbが引数として渡される。
static unsigned int handle_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
        // skbからネットワークヘッダを取得
        struct iphdr *iph = (struct iphdr *)skb_network_header(skb);

        // パケットの宛先IPアドレスが8.8.8.8であり、IPヘッダのプロコトル情報が0x01(ICMP)の場合
        if((be32_to_cpu(iph->daddr) & 0xffffffff) == 0x08080808 && iph->protocol == 0x01){
                return NF_DROP //パケットをDROP(破棄);
        }

        return NF_ACCEPT; //パケットをACCEPT(送信)
}

// 登録するフックのルール
static struct nf_hook_ops hook_ops = {
        .hook   = handle_hook, //コールバック関数
        .pf     = PF_INET, //IPV4
        .hooknum = NF_INET_LOCAL_OUT, //フックのタイミングはローカスシステムから外部にパケットが送信されるとき
        .priority = NF_IP_PRI_FILTER, //フィルタのフック処理
};

//モジュールのロード時の処理
//モジュールのロード時にnf_register_net_hook関数でフックを登録
int init_module(){
        int err;
        err = nf_register_net_hook(&init_net, &hook_ops); //フックを登録

        if(err < 0){
                return err;
        }

        return 0;
}

//モジュールのアンロード時の処理
void cleanup_module(){
        nf_unregister_net_hook(&init_net,&hook_ops); //フックの登録を解除
}
        

実験

pingコマンドを用いて作成したLKMがきちんと動作しているか確認することにします.
mymodule.cというファイル名でコードを作成したことを想定します.

コンパイル方法

1. LKMのためのMakefileの作成

obj-m := mymodule.o

LKMを作成するための最小限の内容にしています。
2. makeによるコンパイル

$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

コンパイルに成功すると、mymodule.koファイルが作成されます。
3. モジュールをロード

 $ sudo insmod mymodule.ko

4. モジュールがロードされたか確認

$ lsmod
Module                  Size  Used by
mymodule               16384  0
:
:
:

5. モジュールをアンロードする場合

$ sudo rmmod mymodule

pingコマンドによる動作確認

$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) バイトのデータ
ping: sendmsg: 許可されていない操作です
ping: sendmsg: 許可されていない操作です
ping: sendmsg: 許可されていない操作です
$ ping 192.168.10.1
PING 192.168.10.1 (192.168.10.1) 56(84) バイトのデータ
64 バイト応答 送信元 192.168.10.1: icmp_seq=1 ttl=63 時間=21.2ミリ秒
64 バイト応答 送信元 192.168.10.1: icmp_seq=2 ttl=63 時間=10.8ミリ秒
64 バイト応答 送信元 192.168.10.1: icmp_seq=3 ttl=63 時間=9.37ミリ秒

8.8.8.8に対してのICMPトラフィックは送信されず、その他のIPアドレスに対してのものは送信していることがわかります。

まとめ

NetFilterを用いたパケットフィルタリングプログラムの実装方法を調べました。

NetFilterの機能を利用するには、NetFilterに関するLKMを作成してフック処理を登録する必要があります。

実験用のプログラムとして、8.8.8.8に対するICMPトラフィックをフィルタリングするプログラムを作成し、pingコマンドによる簡単な実験を行いました。

百合小説をサーバクライアントプログラムとして実装したい

1. Introduction

経緯

ある梅雨の深夜,じんわりと汗を感じながら寝苦しさに唸っていると,次のような発想がお告げのように降ってきました.

「百合小説をRFC形式で記述しなさい」

私は百合作品に造詣が深いわけでもないため,まったく自分とは違った存在から接続されたような気がして,古代の巫女の畏れを感じました.
そんな突拍子の無さを感じつつも,その試みは自分にとっても興味深く映り,非常に興が乗ったのでさっそく翌日から取り掛かることにしました.

方針

RFC形式で小説を書く」としてしまうと,原稿ファイルをRFCのレイアウトにして文章を書いても達成できてしまうので,少し面白みが欠けているかもしれません.

そこで,お互いの恋愛関係とそのやり取りを記述できるような通信方式を考えてみることにしました.そのプロトコルでは散文でもやり取りを行えるような設計にしておきます.最終的にそのプロトコルに則ったサーバクライアントプログラムを作り,手動か自動でやり取りさせます.これにより,ちょうど往復書簡のような塩梅で,小説に近い形式で物語を作成できるのではないかと考えました.

設計したプロトコル

百合小説の実装のため,オリジナルの通信プロトコルSAPPを設計し,その仕様をRFC形式でまとめていくことにしました.
SAPP(Simple Affection Protection Protocol)は非常に簡単なプロトコルで,主に以下のルールで通信を行います.

  1. SAPPクライアントが魅力的で付き合いたいと思う相手(SAPPサーバ)に告白
  2. 告白方法は,「普通に告白」「曖昧に告白」「脅迫的に告白」の3種類から選択
  3. SAPPサーバはその告白に対し,受諾 / 保留 / 拒絶することができる
  4. 受諾された場合,SAPPクライアントがSAPPサーバとメッセージをやり取りできるようになる
  5. 保留された場合は通信を切断される
  6. 拒絶された場合は通信を切断されたのち,しばらくSAPPサーバと連絡を取れなくなる

以下に,作成したRFC風文書を公開します.なお,クオリティがクオリティなので影響力は微塵も無いと思いますが,少しでも本家にご迷惑をおかけするわけにはいかないので,数字は割り当てずに???としました.
RFCは技術文書ですので基本的に晦渋な言い回しを避ける方針を取りましたが,小説を書くというゴールを考えて,遊び心としてある程度小説的な表現も入れようとしています.

drive.google.com

記事の構成

2章ではRFC風文書を書くために勉強した,RFCについての基本的な事項について説明し,3章で設計したプロトコルの設計思想を紹介します.その後,4章でRFCに則って実装したプログラムを紹介し,5章で動作実験を行い,簡単に物語を創作できるかどうか確認します.

2. RFC(Request For Comment)

概要

RFC(Request For Comment)とは,IETF(Internet Engineering Task Force)という機関から発行される,インターネットに関する様々な技術・事柄をまとめた文書群のことです.再配布が可能という特徴があり,非常に容易に入手可能な文書です.

主に通信プロトコルの仕様を定義し標準化するための文書として利用されることが多いですが様々な種類があります.文書は表題もありますが,特に通し番号で区別されます.

ところで,Request For Commentという名前は,ARPANETの研究グループが様々なアイディアを出して公開したものの,論文として出せるようなものではなかったため,あくまでコメントを求むという意味合いで発表した時の名残のようです[RFC-HOWTO].

種類

RFC文書には複数のタイプがあります.そのうち,インターネット技術として標準化されたStandards Trackとそれ以外のNon-Standards Trackがあります.
Standards Trackの文書は以下の流れで
Internet Draft ==> Proposed Standard ==> Draft Standard ==> Standard
まずIETFなどで議論された後に,その文書はInternet Draftという状態になります.
その後ある期限内にIESGに承認されると,Proposed Standardとなり,この段階からRFC番号が振られるようになります.
その6か月後,IESGに承認されると,Draft Standardとなり,さらに4カ月以上が経過して承認を得ると,Standardという文書になり,新たにSTD番号が振られるようになります.

Non Standards Trackには以下の3つがあります.

  • Informational

業界において,有益な情報として認められた仕様

  • Experimental

研究機関や企業などが実現した仕様

  • Historic

代替技術などの登場により現在は使用されていない仕様

自分の文書をどこに置こうかと考えたのですが,後述のジョークRFCもExperimentalで発表されているのでこちらを採用します.

ジョークRFC

自分のような初学者にはRFCは固い技術文書というイメージがありますが,実態はそういう面ばかりではありません.例えば4月1日には,ジョークRFCという形でユーモアたっぷりなRFCのパロディ文書が発表されます.

この中で有名なのが,RFC 1149「鳥類伝送によるIPデータグラムの転送」[RFC1149]です.これは通信に鳩を用いた時の通信プロトコルを書いた文書です.初めて読んだときに,ユーモアが巧みで非常に感動しました.実際に鳩を使って実装してみようとした人もいるらしいです.

自分の設計したいプロトコルは実用性が皆無であるため,ゴールとしてジョークRFCのようなものを目指すことにしました.

ちなみにRFCで文芸作品を表現しているものは無いのかなと探してみたところ何件かありました.RFC 527[RFC527]では,ARPAWOCKYという詩が書かれています.また,RFC 1121[RFC1121]ではARPANETの20周年記念を祝したシンポジウムACT ONEにて,一部のスピーカーが発表した詩をまとめています.英語の詩の楽しみ方がまだ分からないのでクオリティは判断できませんが,韻の踏み方が好きでした.
他にもご存知の方がいらっしゃれば教えていただけますと幸いです.

RFCの読み方

RFCは再配布可能なので検索すればすぐに情報が出てきますが,公式には以下のRFC Editorが使われます.Referecesとして引用する際にはこちらのURLが使われています.
www.rfc-editor.org

ところで,RFCは公開された後に修正されることはなく,技術仕様に修正が必要な時は,また新たなRFC文書が作成されます.
この時に重要となるキーワードがUpdateとObsoleteです.例えば数字の若いRFCを探してみると,上部にUpdated ByやObsoleted Byという文字の後ろにRFC番号が書かれていることが分かります.
ここで,Updated By〇〇〇となっている場合は,現在参照しているRFC文書への追加情報が書かれたRFC番号が〇〇〇に入ります.
また,Obsoleted By〇〇〇となっている場合は,現在参照しているRFC文書を廃止させたRFC番号が〇〇〇に入ります.
つまり,もし最新情報を得ようと思うのならば,以下のような読み方になります.
Updated By:現在読んでいるRFCと合わせて,Updated Byに続くRFC番号の文書を読む
Obsoleted By:現在読んでいるRFCではなく,Obsoleted Byに続くRFC番号の文書を読む

RFCの書き方

RFCの書き方もまたRFC 7322[RFC7322]などで定義されています.今回は,RFC 7322とそれをUpdateするRFC 7997[RFC7997]を参考にして書き進めることにしました.
ところでこれら二つの文書には,ObsolteしているRFC 2223[RFC2223]に記載されていた,Post Scriptで作成する際のページレイアウトやフォントの情報などが盛り込まれていませんでした.
そのため,ページデザインはRFC 2223を参考にしました.

RFC 7322では,RFCに盛り込まなければならない章・節とその書き方の指針,また参考文献などの体裁についての情報が記載されています.
RFCは英語で書かれなければならないことが示されていますが,正直自分の英文は目も当てられない出来だと思うので,ここは日本語で書くことにしました.

拡張バッカスナウア記法

RFCではある規則を表現するため,拡張バッカスナウア記法(Augmented BNF:ABNF)が使われることがあります.この記法自体もRFC 5234[RFC5234]で定義されています.
自分の勉強のためRFC風文書にこちらを盛り込んだので,簡単に触れようと思います.不慣れなのでおかしいぞという箇所がありましたらご指摘いただければ幸いです.

ABNFでは,基本的なルールを<規則> = <仕様>という形式で定義していきます.
そうやって定義した規則を組み合わせて,多様な規則を表現することができます.

ここからは,実際にSAPPプロトコルのREQUESTコマンドをABNF形式で表現していくことにします.
以下にREQUESTコマンドの一例を示します.これは,ハグによってSAPPサーバに対して告白することを意味します.

REQUEST DECLARE HUG

このコマンドは以下のように構成されています.

<req_cmd_name><スペース><req_type><スペース><situation><改行>

ここで,req_cmd_nameに該当する文字列は"REQUEST"のみです.

一方,req_typeは候補の文字列が3種類あります.DECLARE,PRAY,FORCEです.DECLAREが普通の告白であれば,PRAYは踏ん切りのつかない,あるいは自覚のない迂遠な告白,FORCEは壁ドンなどで強引に迫ったり,はては監禁・拷問などで相手の心をへし折り服従させたりするような告白を意味します.

そしてsituationでは,相手にどのように好意を伝えたのかを,0~400文字のUTF-8の文字列によって表現できます.

しかしこうした規則は上述の構成を眺めるだけでは伝わりません.

そのため,それを表現するためにABNF形式に直していきます.

まず,req_cmd_nameを定義します.文字列の候補はREQUESTのみであるため,素直に次のように書きます.これは「req_cmd_nameは"REQUEST"という文字列である」という意味になります.

req_cmd_name = "REQUEST"

続いて,req_typeです.DECLARE,PRAY,FORCEの3種類がありました.このような時,ABNFでは「/」をつかって「選択」という操作を使えます.

req_type = "DECLARE / "PRAY" / "FORCE"

これは,「req_typeはDECLARE,PRAY,FORCEの3つのうちのどれかである」という意味になります.

最後に,situationです.situationは0~400のUTF-8文字から表現されますが,それをABNF形式で書き直すと以下のようになります.

situation = *400 ( UTF8-char )

これは「situationは0~400回,UTF8-charが繰り返された文字列である」という意味になります.UTF8-charはUTF-8の文字を意味します(RFC 3629[RFC3629]で規則が定義されています).ちなみに,カッコの中の規則は数式と同じで先に評価されます.

他にも,スペースと改行が必要でした.スペースはSPですが,改行はCRLFで表現できます.

これらを踏まえ,REQUESTコマンドを表現する規則req_commandは以下のようになります.下と同じようにすっきりしている上,記法に慣れれば想定している規則もきちんと伝わります.

req_cmd_name = "REQUEST"
req_type = "DECLARE / "PRAY" / "FORCE"
situation = * 400 (UTF8-char  /  SP)
req_command = req_cmd_name  SP  req_type  SP  situation  CRLF
<req_cmd_name><スペース><req_type><スペース><situation><改行>

3. Protocol

概要

ここからは設計したSAPPの詳細をまとめていきます.

SAPPの簡単なルールは冒頭で述べましたが,以下により詳細にルールを示します.なお,通信内容は全てSSL/TLS通信により暗号化されます.

  1. SAPPクライアントが魅力的で付き合いたいと思う相手(SAPPサーバ)に対してREQUESTプロトコルコマンドを用いて告白
  2. SAPPサーバに受け入れられると,SAPPクライアントとサーバ間でメッセージをやり取りできる状態に遷移
  3. メッセージをやりとりできる状態のみMESSAGEプロトコルコマンドを用いてメッセージを送信
  4. もしリクエストがSAPPサーバに拒絶 or 保留された場合は通信が終了
  5. 拒絶したSAPPサーバはDENYテーブルにIPアドレスを登録し通信をしばらく受け付けないようにする

ちなみにプロトコルの名前は,Sapphism(女性同性愛)などの単語の元になった古代ギリシャの女性詩人サッポーから取っています.
TwitterInstagramで検索してみたところ侮辱的な言葉ではなさそうだったので採用しましたが,もしも侮辱的な言葉であれば早急に取り下げます.

設計思想

プロトコルの最終目的は,自分たちの関係を秘密にしておきたい人が周囲の目を気にせずに安心してやりとりできることを実現することです.
ただし,あくまで秘密にしておきたいと思う人のためのものであり,その関係は大っぴらに見せるべきではないと主張するものではありません.

記事タイトルでは「百合」と標榜していますが,特に一般的な関係と差別化を図ったり,対象となる人物像を制限したりしないようにしました.恋愛はどんなものも恋愛であり,その形式に当人たちの属性は関係ないためです.
また,別れなどの悲しい出来事や,浮気や二股など醜い部分も表現できるものを目指し,より細かく関係性を表現できるようにします.

4. プロトコル実装

作ったRFCを基に,実際にプロトコルを実装しました.
プログラミング言語はGoを選択しました.自分はGoは触ったことがなかったのですが,ゴルーチンという非同期処理により,サーバプログラムを作成するのが非常に容易だと聞いたので利用することにしました.
以下が実装したプログラムになります.
gitlab.com


プログラムにおいて,SAPPクライアントからの告白に対して,SAPPサーバの対応を決めるところは,request_func関数とmessage_func関数の箇所になります.
例えば,以下のように実装することができます.
素直に告白されたら受け入れ,あいまいであれば保留,強権的であれば拒否するようにしています.
シチュエーション情報は何でも良いのですが,便宜的にオウム返ししています.
柔軟に対応を変えるには,相手から届いたシチュエーション情報を解析して対応を変えるような実装に変更する必要があります.
このプログラムを書き換えることで,サーバとクライアントの関係を記述し,小説の展開で通信できるのではないかと考えました.

func request_func(connInfo *ConnInfo, mtype string, situation string) int {
	
	resp_situation := situation
	if mtype == "DECLARE"{ //普通に告白されたら...
		response := fmt.Sprintf("+OK %d ACCEPT %s",OK_CODE, resp_situation) //告白を受け入れる(ACCEPT)
		sendCommands(connInfo.conn, response)
		if recvAck(connInfo) {
			sendCommands(connInfo.conn, "ACK")
			connInfo.status = MESSAGE
		}else {
			r := fmt.Sprintf("-ERR %d",ER_CODE4)
			sendCommands(connInfo.conn, r)
		}

	}else if mtype == "PRAY"{ //曖昧に告白されたら...
		response := fmt.Sprintf("+OK %d SUSTAIN %s",OK_CODE, resp_situation) //返事を保留(SUSTAIN)
		sendCommands(connInfo.conn, response)
		if recvAck(connInfo) {
			sendCommands(connInfo.conn, "ACK")
			return -1
		}else {
			r := fmt.Sprintf("-ERR %d",ER_CODE4)
			sendCommands(connInfo.conn, r)
		}

	} else if mtype == "FORCE" { //脅迫的に告白されたら...
		ip := paser_IP_from_conn(connInfo.conn)
		DENY_TABLE.AddTable(ip,time.Now()) //相手をブラックリストに入れて,
		DENY_TABLE.ShowTable()
		response := fmt.Sprintf("+OK %d DECLINE %s",OK_CODE, resp_situation) //相手を拒否する(DECLINE)
		sendCommands(connInfo.conn, response)
		if recvAck(connInfo){
			sendCommands(connInfo.conn, "ACK")
			return -1
		}else {
			r := fmt.Sprintf("-ERR %d",ER_CODE4)
			sendCommands(connInfo.conn, r)
		}
	} else {
		r := fmt.Sprintf("-ERR %d",ER_CODE1)
		sendCommands(connInfo.conn, r)
	}
	return 0
}

5. 実験

ここでは,実際にSAPPサーバとSAPPクライアントの間で通信を行い,百合小説のようなものを作れるかどうかを確認します.
物語を創作する技術は乏しいので,その利用用途でも使えるよね,ということを単に確認するだけにとどめようと思います.

設定ファイルの環境

SAPPサーバのsapp.confファイルを下記のように設定しました.Timeout系は全て単位は秒になります.

#コネクションのタイムアウト時間
ConnectionTimeout       60

#ACK待機のタイムアウト時間
AckTimeout              60

#bindするIPアドレス
IPAdress                127.0.0.1

#ポート番号
Port                    10000

#同時に通信可能な人数(何股までするか)
ListenLimit             3

#サーバにアクセス可能なアドレス
AllowAddresses          "127.0.0.1", "192.168.0.1/24"

#DENYテーブルの数
TableSize               10

#DENYテーブルから排除されるまでの時間(タイプではない相手から告白された時にどのくらいの間連絡を取らないようにするか)
TableTimeout            3600

SAPPサーバ・クライアントの背景

SAPPサーバとクライアントの設定を決めることにします.

SAPPサーバはあまり分かりやすく好意をぶつけられることが苦手です.
まじめな性格で,自分の好意と誠実に向き合っては,これは果たして好意なのだろうかと考え込む面倒臭い面があります.
また,自分の気持ちを認めることに気恥ずかしさを覚えており,素直じゃない言い方しかできません.
しかしSAPPクライアントとともにいる時間は気持ちが穏やかになり,ずっとこの時間が続けば良いのにと願うこともあります.

SAPPクライアントは普段明朗な性格ですが,告白するにあたっていざ本人を目の前にすると,茶化してしまう癖があります.
SAPPサーバに好意を抱いているものの,その気持ちを押し殺して,SAPPサーバとは友人関係のまま行くことにしようと密かに考えていました.
そう思っていたのに,ふとしたきっかけで,公園で二人きり,ベンチに座って話すことになりました.
SAPPクライアントは,月明りに照らされたSAPPサーバの横顔を見て,押し殺していた気持ちがあふれ出し,たどたどしく告白をしてしまいます.

そうした背景を踏まえ,以下のような実装を行いました.

func request_func(connInfo *ConnInfo, mtype string, situation string) int {
        resp_situation := situation
        if mtype != "DECLARE" && mtype != "PRAY" && mtype != "FORCE" {
                r := fmt.Sprintf("-ERR %d",ER_CODE1)
                sendCommands(connInfo.conn, r)
                return 0
        }

        if mtype == "PRAY" && strings.Contains(situation, "手を繋いだ") {
                resp_situation = "私は躊躇いながら手を握り返した"

                response := fmt.Sprintf("+OK %d ACCEPT %s",OK_CODE, resp_situation)
                sendCommands(connInfo.conn, response)
                if recvAck(connInfo) {
                        sendCommands(connInfo.conn, "ACK")
                        connInfo.status = MESSAGE
                }else {
                        r := fmt.Sprintf("-ERR %d",ER_CODE4)
                        sendCommands(connInfo.conn, r)
                }
                return 0
        }

        if strings.Contains(situation, "キスをした"){
                ip := paser_IP_from_conn(connInfo.conn)
                DENY_TABLE.AddTable(ip,time.Now())
                DENY_TABLE.ShowTable()
                resp_situation = "「ごめん、そういうのじゃないから」私はぐいと押し返した。"
                response := fmt.Sprintf("+OK %d DECLINE %s",OK_CODE, resp_situation)
                sendCommands(connInfo.conn, response)
                if recvAck(connInfo){
                        sendCommands(connInfo.conn, "ACK")
                        return -1
                }else {
                        r := fmt.Sprintf("-ERR %d",ER_CODE4)
                        sendCommands(connInfo.conn, r)
                }

        }
        resp_situation = "「じゃあ、また今度ね」と私はそそくさとその場を後にした"

        response := fmt.Sprintf("+OK %d SUSTAIN %s",OK_CODE, resp_situation)
        sendCommands(connInfo.conn, response)
        if recvAck(connInfo) {
                sendCommands(connInfo.conn, "ACK")
                return -1
        }else {
                r := fmt.Sprintf("-ERR %d",ER_CODE4)
                sendCommands(connInfo.conn, r)
        }
        return 0
}

func message_func(connInfo *ConnInfo, message string) int {
        var response string
        resp_message := message

        if strings.Contains(message,"もう少しこのままでも"){
                resp_message = "「いいよ」なんでもないように答えたつもりで、うまく声が出なかった。"
        }else{
                resp_message = "..."
        }
        if connInfo.status == MESSAGE {
                response = fmt.Sprintf("ACK")
        } else {
                response = fmt.Sprintf("-ERR %d",ER_CODE4)
        }
        sendCommands(connInfo.conn, response + " " + resp_message)
        return 0
}

実験結果

まずは成功パターンを見ます.

REQUEST PRAY 私はおずおずと手を繋いだ
[RECV] ==> +OK 0 ACCEPT 私は躊躇いながら手を握り返した
ACK
[RECV] ==> ACK
message 「もう少しこのままでもいいですか」
[RECV] ==> ACK 「いいよ」なんでもないように答えたつもりで、うまく声が出なかった。
ACK

次が断られるパターンです.SAPPクライアントは枕を濡らして眠ることでしょう.

REQUEST FORCE 「ねぇ」私は彼女に呼びかけ、キスをした
[RECV] ==> +OK 0 DECLINE 「ごめん、そういうのじゃないから」私はぐいと押し返した。
ACK
[RECV] ==> ACK
REQUEST PRAY 私は彼女の肩に頭を傾けた。「今日は帰りたくないなぁ」
[RECV] ==> +OK 0 SUSTAIN 「じゃあ、また今度ね」と私はそそくさとその場を後にした
ACK
[RECV] ==> ACK

それっぽく設計をすることができました.とはいえ,単純な解析なのでいくらでも悪戯は可能だと思いますが…

6. まとめ

非常に単純なプロトコルですが,自分のやりたかったことを実現することができました.
幾度となく,自分は何をやっているんだ?と我に返ってしまったので細かい部分で誤りがあると思いますがご容赦ください.
効率が悪い,セキュリティ的にまずい実装があると思うので細々と修正を加えていきます.

RFC風文書にも書いたように,ポート番号が広く知られている場合には,通信内容は分からなくとも,SAPPを利用していることがわかります.
このサービスは利用していることもセンシティブな情報となりうるので,個人的にはSAPP over HTTPSを実装してHTTPS通信に紛れさせようとしたのですが,モチベーションがいったん枯れてしまったのであきらめました.今後気が向いたら実装しようと思います.

ご意見・ご指摘等がありましたらよろしくお願いいたします.

7. 参考文献

[RFC-HOWTO] 瓜生聖,秋月昭彦(2004),『RFCの読み方 インターネット技術の公式仕様書』,株式会社すばる舎
[RFC1149] Waitzman, D., "Standard for the transmission of IP datagrams on avian carriers", RFC 1149, DOI 10.17487/RFC1149, April 1 1990, https://www.rfc-editor.org/info/rfc1149.
[RFC527] Merryman, R., "ARPAWOCKY", RFC 527, DOI 10.17487/RFC0527, May 1973, https://www.rfc-editor.org/info/rfc527.
[RFC1121] Postel, J., Kleinrock, L., Cerf, V., and B. Boehm, "Act one - the poems", RFC 1121, DOI 10.17487/RFC1121, September 1989, https://www.rfc-editor.org/info/rfc1121.
[RFC2234] Crocker, D., Ed., and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", RFC 2234, DOI 10.17487/RFC2234, November 1997, https://www.rfc-editor.org/info/rfc2234.
[RFC7322] Flanagan, H. and S. Ginoza, "RFC Style Guide", RFC 7322, DOI 10.17487/RFC7322, September 2014, https://www.rfc-editor.org/info/rfc7322.
[RFC7997] Flanagan, H., Ed., "The Use of Non-ASCII Characters in RFCs", RFC 7997, DOI 10.17487/RFC7997, December 2016, https://www.rfc-editor.org/info/rfc7997.
[RFC2223] Postel, J. and J. Reynolds, "Instructions to RFC Authors", RFC 2223, DOI 10.17487/RFC2223, October 1997, https://www.rfc-editor.org/info/rfc2223.
https://www.rfc-editor.org/info/rfc5234
[RFC5234] Crocker, D., Ed., and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, DOI 10.17487/RFC5234, January 2008, https://www.rfc-editor.org/info/rfc5234.
[RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November 2003, https://www.rfc-editor.org/info/rfc3629.

「Miraiのソースコードを理解する」という記事に関して

Miraiのソースコードを理解する、という題で一連の記事を書いていたのですが、非公開にすることにしました。

ずっとどうなのだろうと考えていたのですが、以下の懸念事項が大きな理由です

GitHubで一般公開されているとはいえ、ソースコードを一部引用して紹介しているので、自分としてはそのような意図は一切なく、悪用禁止の文言を書いてはいたものの悪用を助長するリスクが高いと考えられるため

ソースコードをもとにした記事であるため攻撃に関する情報しか記載されておらず、特徴をまとめる意義はあるものの対策法が書かれていないため

・自分の理解に誤りがある可能性は捨てきれず、間違いを流布するおそれがあるため

 

このような理由があるにも関わらず公開をしていたのは(自分の思い上がりの面もありますが)次の通りです。

・英語ではまとまった文献はあるが、日本語ではなかなか見つからず、自分自身理解に苦労した経験があったので、日本語でそうした記事を提供できないかと考えたため

・アクセス数が実際多く、おそらく需要があるのではないかと考えたため

 

ただ探していくと、日本語で書かれた有用な資料を見つけることができてきたため、自分の中の葛藤を解決するには、記事は全て非公開にし、自分が理解するのに参考にさせていただいた有用な参考文献をまとめていくだけの方が良いと感じました。

 

以下に参考文献を示します。

 

個人的にソースコードを理解していく上で、日本語文献の中で最も参考になった記事です。

https://www.atmarkit.co.jp/ait/articles/1611/08/news028.html

 

Miraiが踏み台機器への不正ログインに利用したIDとパスワードの組などが記載されています。

https://www.ipa.go.jp/files/000062277.pdf

 

ボットネットアーキテクチャなどが詳細に記載されています。

G. Kambourakis, C. Kolias and A. Stavrou, "The Mirai botnet and the IoT Zombie Armies," MILCOM 2017 - 2017 IEEE Military Communications Conference (MILCOM), Baltimore, MD, 2017, pp. 267-272. doi: 10.1109/MILCOM.2017.8170867

 

Miraiで使われているコマンド類など詳しく記載されています

https://www.iij.ad.jp/dev/report/iir/pdf/iir_vol33.pdf

 

HajimeやMiraiについて詳細に書かれている文献(自分自身購入してはいないのですが、試し読みできる範囲ではかなり詳細に書かれていると感じます。めちゃくちゃ高いですが…)

https://www.amazon.co.jp/Botnets-Architectures-Countermeasures-Challenges-Security/dp/0367191547

メモリ効率の良いトラフィック監視を可能にするSketch技術をまとめる(1/?)

はじめに

本記事は効率的なインターネットトラフィック情報監視技術であるSketch技術について,論文を読んで勉強したものです.実際のトラフィック監視業務や理論的背景となる諸定理の厳密な定義に明るくないため,誤ったことを書いている場合にはご指摘いただけますと幸いです.勉強しなおし,可及的速やかに対処いたします.
本記事では,ネットワークトラフィック監視に特化した手法ではなく,Sketch手法として有名なCount-min Sketch[1]を追い,Sketchの基本的事項についてまとめます.

ネットワークトラフィックの監視

統計量

ネットワークトラフィック監視の文脈では,トラフィックの統計情報を用いて異常や攻撃の検知を行います.インターネットが普及している昨今,ネットワークトラフィックは非常に大規模なデータストリームであるため,1パケットずつ監視を行うのは現実的ではありません.
そこでトラフィックを統計情報という縮約された情報に落とし込んで監視することが一般的となっています.
また到着する全てのパケットを対象に計算するのではなく,NetFlowやsFlow,一度サンプリングされたデータを以降の監視対象としそれ以外は無視するSample & Hold[2],サンプリングに優先順位を付けるPriority Sampling[3]などを用いて計算対象となるパケットを減らすことなども行われています.

ここで統計情報というのは,例えば,パケットレートや,データレート,エントロピー(平均情報量)などです.

パケットレートやデータレートが,パケット量やデータ量の異常度を測るのに用いられることは容易に想像できると思うのですが,エントロピーは聞きなれない言葉ではないでしょうか.

エントロピーは主にDDoS攻撃やポートスキャンなどの攻撃を検知するためによく用いられます.

エントロピーは次の計算式で算出される値で,情報源の出現頻度の偏りを測るのに用いられます.ここで, Nは情報源の総数, p(x_{i})は情報源 x_{i}の出現確率です.

 E = - \sum_{i}^{N}p(x_{i}) \log p(x_{i})

しかし当然全てのIPアドレスの真の出現確率は分からないので,実際には,ある単位時間(ウィンドウサイズ)に観測・サンプリングされたデータを使って以下のように計算します.ここで, Mはウィンドウサイズ内に観測されたパケットの総数, Xはウィンドウサイズ内で観測された情報源の集合, m_{j}は,情報源 jの出現回数です.

 E = - \sum_{j \in X}\frac{m_{j}}{M} \log \frac{m_{j}}{M}

エントロピー値には,出現頻度がある情報源に偏っていると値が小さくなり,逆に分散されていると値が大きくなるという特徴を持っています.
例えば,DDoS攻撃が行われると,一般的には大量の異なるホストからの攻撃トラフィック流入するのですが,情報源を送信元IPアドレスや宛先IPアドレスに設定したエントロピー値を監視することで,送信元IPアドレスが異常に分散していないか,または宛先IPアドレスが異常に集中していないかが分かります.

問題点

ネットワークトラフィックから統計量を計算する時に問題となるのは,メモリ効率です.

例えば,エントロピー値は情報源の確率密度分布(ここでは単にヒストグラムを仮定)を用いて計算されますが,これを実現するにはIPアドレスをキーにしてそのIPアドレスの出現数をデータとして保持しておく必要があります.

しかしIPv4アドレスは全部で2^{32}種類あり,かつ,大量のデータが到着することが予想されます.

そのため,全てのIPアドレスに対応可能な形式を単純に実装してしまうと,メモリ効率が悪くなってしまいます.

そこで考えられたのが,Sketchデータ構造の導入です.Sketchというデータ構造を用いると,メモリの使用量を抑えられ,次の章で示すように利用するメモリ量を事前に把握できます.これによって多くの場合はKB(キロバイト),多くてもMB(メガバイト)のデータ量でトラフィックを監視することが可能となります.

Sketch技術はネットワークトラフィック用に提案されたものではありませんが,今後,Sketch技術とは,ネットワークトラフィック監視にSketchデータ構造を用いている技術全般を指すことにします.

Sketchデータ構造

基本形式

Sketchデータ構造は,キーの候補数(カテゴリ数)が多い場合に使われるデータ構造[4]で,データストリームを計算対象とする,Streaming Alorithmの一種です.

Sketchは以下に示すようにH \times K個の二次元カウンタで構成されたデータ構造です.Hハッシュ関数の個数で,Kハッシュ関数の値域の最大値です.

Sketchの配列をSで表すとすると,S[i][j]のカウンタは「i番目のハッシュ関数を用いてハッシュ値jが出力されるkeyが何個観測されるか数えるためのカウンタ」となります.

Sketchはカウンタ(たいていはintを利用)を利用するので,カウンタ変数の型とSketchのサイズから,使用するメモリ量が事前に分かります.
理論的な空間計算量はSketchの設計によって異なります.例えば以下で説明しているCount-min SketchはPoint Query(あるキー値のカウンタ値がどのくらいあるか知るための操作)をするためだけでは,空間計算量は O(\frac{1}{\epsilon^{2}} \log \frac{1}{\delta}) となります.

(追記:2020/07/26 『メモリ効率の良い』と言いつつ空間計算量の話が明示的に書かれていなかったので追記いたしました)

f:id:madomadox:20200411194140p:plain
Sketchデータ構造

Update操作

データ構造に(key, val)の組のデータを挿入することを考えます(以降この操作をUpdate操作と呼ぶことにします).
ここでkeyIPアドレスvalIPアドレスの出現回数です.(ところでvalを計算するのにSketchが新たに必要となり堂々巡りになりそうな気がしますが,運用上,出現回数を数えることはせずに,valを1に設定して,新たにパケットが到着するごとに更新しているのだと思います).
updateは, i = 0, 1, 2, ..., Hについて,次に示す手順で行われます.

  1. 入力となるkeyを,ハッシュ関数h_{i}でハッシュ化し, j = h_{i}(key)を取得
  2. 得られたデータjに対応するカウンタ S[i][j] valだけカウントアップ

f:id:madomadox:20200411192738p:plain
Update操作

この更新方法により,管理しておくべきカテゴリの数が高々K個に圧縮されます.
また,レアなカテゴリは基本的に寄与が少ない割にヒストグラムを無駄に大きくしてしまいがちなのでどこかに混ぜ込んでしまったりするのですが,Sketchだとそれが自然に行われるので,そういった点でも都合が良いと言えます[4].
ここで,ハッシュ関数としてどのようなものを用いるかという話ですが,基本的には以下の式を用います.

 h_{i}(key) = a_{i} \times key + b_{i} \mod K

ここで,a_{i}b_{i}は整数であり,値域は素数pを使い,[1,p]です.(ハッシュ関数やパラメータの選び方は手法によって異なります.)

Query操作

次にこのSketchを利用して,keyに対応するvalを取得します(以降この処理をQueryと呼びます).ハッシュ値が,値域Kによっては衝突する可能性があるため,どのような操作で取り出せばより推定値として適切かを考える必要があります.

今回は,Count-min SketchのQueryを考えてみます.Count-min SketchのQueryは以下の式で表現される操作を行います.

 \displaystyle  Q(key) = \hat{v}_{key} = \min_{i} S[i][h_{i}(key)]

文字に書き下すと,1行目から H行目までh_{i}(key)を計算した時の,対応するカウンタ値S[i][h_{i}(key)]の最小値を,keyに対応するvalの推定値として出力します.
この操作の意味を直感的に考えると「カウント数が最小値よりも大きい場合は確実に衝突しているので,より衝突の可能性の少ない最小値を推定値として採用する」となります.
この推定値がどのくらい良いかというのは,次の定理で示されています[1].ここで, \|x\|_{1}xのL1ノルムを指します.

keyの真のvalとなる値をv_{key},Queryによる推定値を\hat{v}_{key}とすると,v_{key} \leqq \hat{v}_{key}かつ(1-\delta) の確率で \hat{v}_{key} \leqq v_{key} + \epsilon \|v\|_{1} を満たす.

Count-min Sketchは\epsilon\deltaの2つのパラメータを持っており,Sketchのサイズは w \times d = \frac{e}{\epsilon} \times \ln(\frac{1}{\delta})で決まります.なお,eネイピア数です.
定理から,\delta\epsilonを小さくしていけば,比較的高い確率で真の値に近づいていくことが分かります.しかしその分Sketchのサイズが大きくなってしまうのでバランスを調整する必要があります.

 実際に簡単にプログラムを作成して確認してみます.以下はSketchに(10,1), (150,2), (132,3), (140, 4), (13, 5), (2, 6)の6つの(key, val)の組を挿入し,key=2の,val=6を取得できたか試してみたものです.
cmsketch.cpp

#include <iostream>
#include "cmsketch.hpp"

int main()
{
    CMSketch cm_sketch(0.05,0.01);
    cm_sketch.setHash(173);
    
    cm_sketch.update(10,1);
    cm_sketch.update(150,2);
    cm_sketch.update(132,3);
    cm_sketch.update(140,4);
    cm_sketch.update(13,5);
    cm_sketch.update(2,6);
    
    cm_sketch.printSketch();
    int q = cm_sketch.query(2);
    std::cout << q << std::endl;
}

cmsketch.hpp

#ifndef _CMSKETCH_H
#define _CMSKETCH_H

#include <iostream>
#include <vector>
#include <random>
#include <limits>
#include <algorithm>
#include <cmath>

class CMSketch
{
    public:
        CMSketch(float epsilon, float delta);
        void setHash(int prime);
        void update(int key, int value);
        int query(int key);
        void printSketch();

    private:
        int prime;
        int h;
        int d;
        std::vector<std::vector<int>> counter;
        std::vector<int> hash_coef;
        std::vector<int> hash_intercept;
};

CMSketch::CMSketch(float epsilon, float delta)
{
    h = ceil(log(1.0/delta));
    d = ceil(exp(1.0)/epsilon);

    counter.resize(h);
    for(int i = 0; i < h; i++)
    {
        std::vector<int> tmp(d,0);
        counter[i] = tmp;
    }

    hash_coef.resize(h);
    hash_intercept.resize(h);
}

void CMSketch::printSketch()
{

    for(int i = 0; i < h; i++)
    {
        for(int j = 0; j < d; j++)
            std::cout << counter[i][j] << "," ;

        std::cout << std::endl;
    }
}

void CMSketch::setHash(int prime)
{

    std::random_device seed_gen;
    std::default_random_engine engine(seed_gen());

    std::uniform_int_distribution<> dist(0,prime);
    int row_size = counter.size();
    
    for(int i = 0; i < h; i++)
    {
        hash_coef[i] = dist(engine);
        hash_intercept[i] = dist(engine);
    }
}

void CMSketch::update(int key,int value)
{
    
    for(int i = 0; i < h; i++)
    {
        int hash = (hash_coef[i] * key + hash_intercept[i]) % d;
        counter[i][hash] += value;
    }
}

int CMSketch::query(int key)
{
    int query = std::numeric_limits<int>::max();

    for(int i = 0; i < h; i++)
    {
        int hash = (hash_coef[i] * key + hash_intercept[i]) % d;
        query = std::min(query,counter[i][hash]);
    }

    return query;
}
#endif //_CMSKETCH_H

Count-min Sketchのパラメータ\epsilon\deltaは,それぞれ0.05, 0.01,そしてp=173として試してみました.
実行結果の一例を以下に示します.

f:id:madomadox:20200516204342p:plain
実行結果
上の二次元に羅列されている数値がSketchのカウント数を表示したもので,一番下の数字が推定値として出力された値です.
Sketchを見てみると,2行目に「11」という文字が見えます.これは,2と13のハッシュ値が衝突してしまっていることが原因です(key=2val=6key=13val=5のため).しかし衝突が発生していない行があるので,各行についてkeyハッシュ値が示すカウンタ値(6,11,6,6,6)の最小値を取ると,key=2の出現回数として,正しい結果の6が出力されています.

課題

しかし,Sketchデータ構造を用いたネットワークトラフィックの統計量の計算には,次の課題があります.

1つ目は統計量の精度が低くなることです.ハッシュを用いて確率的に処理を行う関係でどうしても実際の統計量との誤差が生じてしまいます.

2つ目はホストの特定ができないことです.異常検知ではその異常の原因となるホストのIPアドレスを特定する必要があるのですが,Sketchではそれができません.なぜならハッシュ関数を用いてIPアドレスをハッシュ化したものを用いるためです.ハッシュ関数は一方向関数なので,ハッシュ値からIPアドレスへの逆変換ができません.(追記:2020/07/26 ハッシュ関数の概念はあくまで「データ構造とアルゴリズム」で出てくるものであり,暗号学的ハッシュ関数に必要となる一方向性という性質にはそぐわないと考えたため訂正いたします.ただしハッシュ値からIPアドレスに変換するのはそのままでは困難であることは変わらないため,工夫が必要となります)

この二つの課題を解決するため,研究者はより精度の高い統計量を計算したり,ホストの特定が可能となるような手法を研究しています.

まとめ

大量にデータが観測されるネットワークトラフィック監視において,パケット単位で監視するのではなく,統計量を計算してトラフィック情報を縮約する必要があります.
しかし,IPアドレスという,考えられるキーの数が非常に多いデータを対象にした計算を行うとなると,単純な実装ではメモリ効率が悪くなります.
そこで,Streaming Algorithmの一種であるSketchデータ構造を利用し,メモリ効率の良いネットワークトラフィック監視手法が提案されています.
今回は有名な手法,Count-Min Sketchを実装して,Sketchの基本を確認しました.
次回以降は,実際にネットワークトラフィック監視に特化したSketch技術をまとめていきます.
どこかで,今回引用させていただいたサンプリング手法についても紹介していこうと思います.

(2021/04/11 追記)
1個書きました。
madomadox.hatenablog.com

参考文献

[1] Cormode, Graham, and S. Muthukrishnan. “An Improved Data Stream Summary: The Count-Min Sketch and Its Applications,” Journal of Algorithms & Computational Technology Vol. 55, No.1, pp. 58–75, 2005.
[2] Estan, Cristian, and George Varghese. “New Directions in Traffic Measurement and Accounting,” In Proceedings of the Conference on Applications, Technologies, Architectures, and Protocols for Computer Communications, SIGCOMM ’02. New York, NY, USA, pp. 323–36, 2002.
[3] Noga Alon, Nick Duffield, Carsten Lund, and Mikkel Thorup. "Estimating arbitrary subset sums with few probes," In Proceedings of the twenty-fourth ACM SIGMOD-SIGACT-SIGART symposium on Principles of database systems (PODS ’05). Association for Computing Machinery, New York, NY, USA, 317–325, 2005.
[4]機械学習のための特徴量エンジニアリング 著 Alice Zheng, Amanda Casari,訳 株式会社ホクソエム,株式会社オライラー・ジャパン,2019.

SecHack365の参加記録

はじめに

SecHack365に参加して1年が経ち、なんとか無事に修了することができました。軽い気持ちで受けたこのイベントでしたが、自分にとっては人生が一変するような刺激的な経験となりました。この場を用意してくださった全ての方々に対し感謝の念に堪えません。

sechack365.nict.go.jp

この記事では、SecHack365を通して学んだことについて,個人的な視点で書いていきます.とりとめのないトピックがつらつらと並んで大変恐縮ですが,少しでも次回以降参加される方に貢献できるならば幸いです.

応募時の気持ち

SecHack365というイベントを知ったきっかけは,Twitterでふと見つけたネット記事がでした.セキュリティ関連の研究をしていたので,NONSTOPを利用してみたいし(最終的に使いませんでしたが),第一線で活躍される研究者に指導されてみたいと思い応募を決めました.

アルバイトの時間が迫る中,締め切りギリギリまで課題を埋めた覚えがあります.非常に失礼な話なのですが,開発ができるわけではないし深い洞察が出来るわけでもないし…という消極的理由で表現駆動コースを選択しました.結果としてこのコースが一番自分に合っていたかなと感じ,選んでよかったと思いました.

応募に際して,学生の場合は担当教員との相談が必須であるとされていますが,自分は担当教員に特に相談せずに応募していまいました.自分の担当教員はこうしたイベントへの参加を好意的にとらえてくださる方だったための行動だったのですが,特にB4やM2は卒論・修論と被るので,いい顔をしない先生もいるかもしれません.なので,相談は必要そうだと感じました.

受かると思っていなかったので,電話で合格した旨を伝えられた際には嬉しさのあまり研究室でひとり棒立ちしていました.あふれる嬉しさをTwitter上でも表明したところ,経験のない「いいね」爆撃が発生し,何事かとTwitterを開くと,他のSecHack365参加者の方々にフォローされていました.

このときフォローしてくださった方のbio欄のすごさに自分は目を疑いました.スペシャリスト系の試験めっちゃ受かってるし,めっちゃ勉強会行ってるし,めっちゃ技術のこと詳しそうだし,「なんかやばいところに参加することになったな…」と戦々恐々としてしまいました.

 

表現駆動コース

自分は表現駆動コースに応募したと書いたのですが,本コースではとにかくアウトプットというか,まさしく「表現」を求められた印象です.

前半のオフライン回でのコースワークでは,自分のアイディアを発表してコメントをいただく活動が多かったです.自分で口に出したり,他の人からの指摘を受けたりすることによって,深く考えなおし,必然的に毎日自分の考えを磨いていくことになりました.

また,オフライン回では,Night Challengeという1-day(夕方から翌日の朝にかけてなので半日かも)のハッカソンが開催され,成果をプレゼン形式で発表しました.表現駆動コース内のトレーニーでランダムにチーム分けをして,テーマに沿ったプログラムを作ったり,将来のビジョンを提示したりしました.

これら二つの活動は,長期的な頭の使い方と,短期的な頭の使い方をそれぞれ学んでほしいという意図であったようで,非常に良い経験になりました.言われてみると頭の燃える箇所がそれぞれ違っていたようにも思えてきました.

 

チーム開発

SecHack365では,4人チームを組み,「プライバシーに配慮したTwitterクライアント “PEACE” 〜安心・安全なSNSを目指して〜」という作品名でデスクトップのTwitterクライアントを作成しました。ちなみに、PEACEとは"Privacy Enhanced Alternative Client for Education"の頭字語です。

本来応募時ではそれぞれ別のことをやっていたのですが,テーマが近かったり,興味があったり,といった動機で最終的に4人チームとなりました.2018年度のSecHack365では一番多い人数でした.

大体半分頃にチームが確定してから,週に1回,2時間程度のミーティングを行うようになりました.最終的に24回までしたので48時間くらいは話し合いました.重要な発表の前日にはほぼ毎回朝4時くらいまで作業したのを考えればもう少しいくかもしれません.忙しさにかまけて話し合いをしなかったら最終的によく分からない着地点に到達することはなんとなく想像できたので,ここを徹底することが出来てよかったです.

24回分のミーティングで,サービス開発や発表方法などを議論しましたが,そこまで掘り下げるかというところまで議論を掘り下げていったりもして,この点に関しては他のどのプロジェクトよりも考え抜いたのではないかという自負があります.

チーム開発ということで,Gitを利用してコードを管理していたのですが,自分は利用したことがなかったので苦労しました.根気よく教えてくれたチームメンバには感謝しかありません.

チーム開発で良かった点は,誰かが忙しい時に他の人がカバーできる点,機能を分担できるので最終的に開発物の規模感が大きくなりやすい点,より多くの人のチェックの目を通ることで発表資料のクオリティが上がりやすい点かなと思いました.

逆に欠点としては,個人でやるよりも意思決定が遅くなる点と,意思の共有に時間を費やしがちな点でした.気を付けないと何かと行動が遅くなる危惧があります.

チームでの議論を経て,いくつか反省したことがあります.一つは,自分が口頭で説明するのがめちゃくちゃ下手くそだったことです.話し言葉の語彙が貧弱な自分はすぐに「なんか」とか「あれ」とかで濁してしまうので,なかなか他の人に共有できずに申し訳なかったです.他のチームメンバはまさにドンピシャな表現で伝えてくださったので,議論するには議論の技術がいるなと思いました.

もう一つは,思うよりも論理の綻びに対して鈍感であったという点です.発表資料を作成する際に,気を付けないとすぐに良く分からない方向に進んでしまっていたので,チームメンバにその都度指摘していただいてなんとか形になる発表をすることができました.論理に対する美意識を常に持っておきたいです.

色々と思うところを書いてきましたが,チーム開発は本当に楽しくて,チームメンバの雰囲気も最後まで良かった気がします.考えの違う人同士でやるのでどうしても多少はピリつくこともありましたが,まさにこれこそ遅れてきた青春という経験でした.

開発能力の成長

成果物はVue.jsとPythonで作ったのですが,自分は授業で習ったC言語とJavascirptくらいしか書いたことがなかったので大変でした.

開発を進めていくうえで「REST APIって何?」「Node.jsとは?」と次から次へと疑問が出てくるありさまだったのですが,チームメンバの方々にも恵まれてどうにか乗り越えることができました.

今となってはこれらについてもある程度理解できたので,レベルとしては高くないとは思いますが,去年の自分に比べれば成長できたと言ってもよいのではないでしょうか.知らないことを知ることができて本当によかったです.

トレーニーとトレーナーの方々

最初のオフライン回でトレーニーの方々と対面したとき,オーラの違いというか,レベルの高さに愕然としました.同世代でこんなにもういわゆる天才エンジニアとしてバリバリやっている方がいるのかと,自分の見てきた世界の狭さを痛感したところでした.

実力的に,自分はどう贔屓目に見積もってもダメダメ村のダメダメ住人でしたが,それでも優しく接していただいて安心しました.自分の優秀さを鼻にかけることなく,ただひたすらに技術に誠実な方々ばかりでした.

トレーナーの方々にも本当に暖かく迎えていただきました.軍隊の教育を覚悟して臨んだ初回だったので,ほっとしたのを覚えています.

トレーナーの方はとにかくどんな話でも真剣に耳を傾けてくださいました.おそらくは日常に出すには憚られるようなエモも受け入れてくれて,すぐにポエムを吐き出してしまう自分にとっても精神的に過ごしやすい環境でした.

また,ただでさえ本業がお忙しいにも関わらず我々トレーニーに対し親身に接してくださいました.SecHack365ではプロジェクトとしてチームが固まり始めると,そのプロジェクトの担当トレーナーがつくようになるのですが,自分の場合は表現駆動,開発駆動,思索駆動のトレーナー陣が担当者になり,非常に多くの方にお世話になりました.特に週一のミーティングには遅い時間帯にも関わらず参加していただき,自分の生活を削ってまでサポートしてくださる姿に感動いたしました.若者のために命をかけられるような大人に自分もなりたいです.

 

習慣化とアウトプット

2018年のSecHack365では「習慣化」と「アウトプット」を特に意識してご講演されていたような感じがしました.

SecHack365オフライン会の初日にはマンダラートを埋めて自分がやるべき行動を分析し,それらを習慣化できるように各回でフォローアップされていました.

このブログも「勉強した成果をアウトプットする」の習慣化の一環として設立したものになります.

ブログ記事の投稿履歴を見れば一目瞭然だと思いますが,正直なところ今年は習慣化はうまくいきませんでした。2019年度はひと月に1回は更新したい…

もうひとつの柱としてアウトプットの重要性がよく語られました。SecHack365ではオフライン回がアウトプットの役割を持っており,ショットガンセッション,面談,ポスター,プレゼンと多様な形式で発表を行いました.

アウトプットに関わることでとても印象に残ったのは,トレーナーの方の「アウトプットはデバッグだ」という言葉です.アウトプットというと,とにかく批判に晒されるし,めちゃ怖いし…という印象だったのですが,単にエラーを潰すために行うものなのだと思うようになりました.考えてみれば当然のことなのですが,この言葉を聞いたおかげで,「うまくいくかな~」と軽くEnterキーを押すくらいの気持ちでアウトプットができるようになりました.

 

忙しさ

正直なところ,かなり忙しくなりました.自分は修士論文がなかなかうまくいかなかったこともあって,両方の成果が求められてくる11月から2月にかけては本当に精神が参ってしまい,幾度と体調不良に陥ってしまいました.

あまり良い印象を与えない物言いになりましたが,二足の草鞋を自らの意思で履いたのだから至極当然なことで,SecHack365が悪いのだということはもちろんありません.

念のためですが,SecHack365では習慣化が大事だということをモットーにされていて,とにかく無理をせずコツコツとやりましょうということを何度もお話しされていましたので,コツコツやらないとしわ寄せが来るという単純な話なのかもしれません.

最終的に優秀修了生という栄誉ある称号をいただき,これらの苦労は報われました.ただ,報われなかったにしてもここで得た苦労は一生ものの宝であると考えていたことと思います.苦しむことを分かっていても,参加することに意義はあったと感じました.

ところで,いつの時期にSecHack365を受けるべきかという話があるのですが,正直いつの時期に受けても忙しいのは忙しいし,つらいのはつらいので,自分としてはそこはあまり考えなくても良いのではないかと思いました.ただ,修士一年の人は授業や就活が被ってかなり厳しそうだったので注意が必要そうでした.

まとめ

自分は正直なところ意識の高い人間ではありませんでした.ほんの少しの努力で評価されるような,自分にとってコスパの良い世界に進んで身を投じ,ぬるま湯でゆったりとした世界にいたいような人間でした.

しかし,SecHack365に参加して,人生観がガラッと変わりました.できないと思うことも少しづつやっていこうと思えるようになりましたし、もしかしたら自分も世の中を変えられるような人材なのかもしれないと,端から抱きようもない根拠のない自信感も身に付きました.この自信感を持ってたくさん失敗してたくさん成功して,やがて行動に裏付けされた自信に変えていきたいです.

最終回では、今後は君たちのSecHack365が始まるのだという言葉で激励されました.修了は単なる第一歩で,これからまさしく365日,誠実に研鑽していくことで,いずれセキュリティイノベーターの入り口が見えてくるのだろうと思いました.自分はまだまだ半人前ですが,ちょっとずつでも頑張っていこうと思います.

あまり関係の無い個人的感傷

つい先日,自分の敬愛してやまないミュージシャンであるwowakaさんが永眠されました.

究極の個人的感傷で本当に申し訳ないのですが,自分が修士論文とSecHack365で苦しんだ時期に発表された楽曲である『ポラリス』に心の底から救われたため,たとえどれだけ本記事の意図に沿わない内容だとしても,どうしても書かずにはいられませんでした.

www.youtube.com

自分としては,この曲の伝える内容は,SecHack365を通してセキュリティイノベーターという修羅の道に足を踏み入れる方々にとっても決して無縁ではないと思ったので,最後のサビの歌詞を紹介させていただき,本記事の締めとさせていただきます.

ひとりきりのこの道も 覚めない夢の行く先も
誰も知らぬ明日へ行け 誰も止められやしないよ


また一歩足を踏み出して
あなたはとても強いから
誰も居ない道を行ける
誰も居ない道を行ける