erlang and firewall
Concurrent 読書
オイラーの友人に読書好きな人がいる。ってか、夏の暑い盛りに図書館に涼みに行き、日がな一日を雑誌をみたり、専門書を手当たり次第に読んでいた。たまに電話すると、読書で得た蘊蓄をたっぷりと聞かされてものだ。
それが、コロナの時代になって、図書館恐いになってしまったそうな。今は引き籠りに徹しているとか。暇で暇でとぼやいていた。そんなに図書館恐いかね? あさイチの空いている時に行って、手当たり次第にサーと借りてくれば、滞館時間はせいぜい10分ぐらいだろうに。3日に一度スーパーへ行くより、よっぽど安全だと思うんだけどな。まあ、人それぞれですから。
オイラーは、さっと行ってさっと借りてくるを励行。今回は
ビルゲイツ推薦の 液体 三谷幸喜の 予測不能 素数の不思議な見つけ方 みんな大好きなパイと双璧を成す ここまで分かった『黄砂』の正体 もう旬は過ぎたの? ブラック・スワン降臨 帯に惹かれて 雨の狩人 大沢在昌さんの本を前回読んだんで、そのヨシミです 劔岳線の記 新田次郎さんの点の記にインスパイアされた?
最初の3冊は新刊コーナーに有ったもの、残りの4冊は、昨日返還された本のコーナーに有ったものだ。探索範囲を狭めれば、選択も直ぐに終わるよ。あれこれ迷う必要無し。せいぜい迷うとすれば、表紙裏に貼ってある帯を眺めて可否を決める。
で、読書の方法だけど、新刊はなるべく早く返却しましょって事で、3冊を並行に読んでいる。どれも小説じゃないんで、適当に読み進めても問題無い。飽きたら次の本にスイッチするんで、自分から権利を他に譲る事になる。この間知った、rubyに実装されてるファイバー機能の実戦だね。
ああ、ゲイツ推しの本はちょいと楽しくて、優先度があがってるな。数々の液体についての解説だ。ヒースロー空港から飛行機に乗りサンフランシスコまでの旅の間に出くわす液体についての蘊蓄ものだ。まず出て来るのは、飛行機の燃料になるケロシンの話、次が機内サービスで出て来たワインの話、こんな調子で次々と液体が出て来る。ワクワク感が止まらないぞ。
年金生活者が、12080円の節約です。作家の大沢在昌は、たまに図書館の講演に招かれる事があるそうです。図書館で人気になっても、所詮回し読みのシステム。作家にとっては憎きシステムと、心情を露呈されてました。技術書ならともかく、文学書を買って読むって、なんだかなあ、なんですよ。許してください。
Concurrent Programming
前回、目を付けておいたコンカレント・プログラミングに手を出してみる。クッキー大事だよ。ちゃんと設定しておこうね、なんて言う案内が有った。
接続時にコマンドラインから設定出来るのに、何を今更と思ったり、思わなかったり。単に入力の手間を省くだけ? 入力ミスの防止? いや違うな。コマンドラインからだと、クッキーデータが、psコマンドなり、/proc/pid/commandline とかで覗き見可能。 .bash_history
とかで、後で確認可能。要するに、セキュリティー上、好ましく無い。
設定しようとしたら、既に先客が居るよと、怒られた。おかしいな、生まれてこの方、設定なんてした事ないのぞ。取り合えず先客確認。
ob$ ls -l .erlang.cookie -r-------- 1 sakae sakae 20 May 9 00:00 .erlang.cookie ob$ cat .erlang.cookie RFIETDLHZXHUNLSNAWORob$
真夜中に設定されてるよ。小人さんが勝手にやったのかな?
vbox$ ls -l .erlang.cookie -r-------- 1 sakae sakae 20 Apr 18 00:00 .erlang.cookie vbox$ cat .erlang.cookie WXTUXKCBLKKRMIGTUUKBvbox$
別なマシンでも、真夜中に設定されてた。月日が違うけど、この日に何をやってたかなんて、ちっとも思い出せない。まあ、普通の人はそんなものだろう。
DLしたソースそのままでは、自分の環境に馴染まない。単一のhostだけで動くように改変する。 それと同時に長ったらしいファイル名も変更してみた。オリジナルからの相違点は下記。
vbox$ diff -u /home/sakae/messenger.erl mine.erl --- /home/sakae/messenger.erl Sun May 30 13:43:21 2021 +++ mine.erl Tue Jun 1 14:54:40 2021 @@ -48,13 +48,13 @@ %%% Configuration: change the server_node() function to return the %%% name of the node where the messenger server runs --module(messenger). +-module(mine). -export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]). %%% Change the function below to return the name of the node where the %%% messenger server runs server_node() -> - messenger@super. + boss@vbox. %%% This is the server process for the "messenger" %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...] @@ -74,7 +74,7 @@ %%% Start the server start_server() -> - register(messenger, spawn(messenger, server, [[]])). + register(messenger, spawn(mine, server, [[]])). @@ -120,7 +120,7 @@ case whereis(mess_client) of undefined -> register(mess_client, - spawn(messenger, client, [server_node(), Name])); + spawn(mine, client, [server_node(), Name])); _ -> already_logged_on end.
サーバーになるマシンの名前を変更。ファイル名変更の影響を受ける、spawnの第一引数(モジュール名)を変更。
実行例
そして、サーバーにを稼働して、画面をウォッチ。
vbox$ erl -sname boss Erlang/OTP 21 [erts-10.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] Eshell V10.2 (abort with ^G) (boss@vbox)1> c(mine). {ok,mine} (boss@vbox)2> mine:start_server(). true list is now: [{<11215.89.0>,hoge},{<11213.89.0>,fuga}] list is now: [{<11215.89.0>,hoge},{<11213.89.0>,fuga}]
サーバーは、通信が有る度に、logonしてるクライアントを報告して来るのね。
vbox$ erl -sname hoge Erlang/OTP 21 [erts-10.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] Eshell V10.2 (abort with ^G) (hoge@vbox)1> c(mine). {ok,mine} (hoge@vbox)2> mine:logon(hoge). true logged_on (hoge@vbox)3> mine:message(fuga, "hey fuga-san"). ok sent Message from fuga: "How are you ?"
一台目のクライアント。まずはログオンする。
vbox$ erl -sname fuga Erlang/OTP 21 [erts-10.2] [source] [smp:1:1] [ds:1:1:10] [async-threads:1] Eshell V10.2 (abort with ^G) (fuga@vbox)1> c(mine). {ok,mine} (fuga@vbox)2> mine:logon(fuga). true logged_on Message from hoge: "hey fuga-san" (fuga@vbox)3> mine:message(hoge, "How are you ?"). ok sent
通信は相手がいればこそ成り立つので、2台目も起動して、ログオン。通信開始。
これって、サーバーがepmdみたいに振る舞うのね。通信したい人は、サーバーに相手が居るか確認してる。
Firewall
ふと、上でやったのは1台のパソコン内の話、erlの特技である複数パソコンの場合はどうよと思った。パソコンが直接インターネットに晒されている場合、ファイアウォールは必須だよね。 そんな環境に有るerlでも、参加出来るのか? 例では、そんな事は範疇外って事になってる。
ならば、斜め上の環境、もとえerlを動かしているOS寄りの話になるんで、斜め下を見据えて、やってみる必要がありそう。こんなヒントを与えてくれたのは、これから綴る話になる。
TVのコマーシャルで、昔懐かしい Gメン'75 のDVDを宣伝してた。懐かしいな。昭和の香りがプンプンするよ。 ああ、キーハンターなんてのもやってたな。今のTVは、安上りに、局に芸人を集めて、学芸会ばかりだものな。全く見る気無し。昔は良かったぞ、とおじさん気分丸出し。 太陽にほえろ! も思い出した。 cap って言ってたのは、どの番組だっけ? bossは裕次郎が似合うか。
話が逸れた。先の例で、クッキーファイルが真夜中に設定されてた。プチ疑問。一度消してから、erlすると、それでは作成されず。erl -sname hoge とすると、その日の真夜中に作成したよってなった。と言う事は、作成日を見れば、ネットに乗り出した日が同定出来るって事ね。こんな細かい事は、どうでもいいんだけど、プチ気になったのさ。
で、今度は、オイラーが持ってるパソコン全てで、erlしたいな、なんて言う遊びを思い付いた。けど、よく考えたら、Windows10と同一サブネットに鎮座するdebian(32Bit)から、Windows10内にあるVmwareゲスト(OpenBSD(64),Debian(64))に接続する方法を編み出せない。
なら、Windows10内に更に居るvirtualboxのゲスト(FreeBSD(64),OpenBSD(32))はどうだ? これらの*BSDは、nat接続でPuTTYからログインしてる。その為にvirtualboxで高度な設定と称して、2022 -> 22 へのポートフォワーディングなんてのをやってる。穴を開けるのいやだな。
ってな事で、最初の目論見は大幅に後退せざるを得ない。Vmware内のサブネットに繋がっている、OpenBSD(64)とDebian(64)の2台でやってみるか。で、この際だから、OpenBSDは、ファイアウォールを追加して、要塞化してみる。なんたって、bossの丹波哲郎が居るのは、要塞化した基地で、鉄壁な守りになってるはず。そうでなくちゃ、ロシアのクラッカー集団に襲われて、身代金を要求されるからね。
前置きが非常に長くなったけど、本邦初のファイアウォールをOpenBSDに施してみる。リナはありきたりで面白くなさそうだからね。
Chapter 31. Firewalls(in FreeBSD) を見て、要塞の作成方法一般を調べた。OpenBSDは、 PF - Packet Filtering ってのがデフォなのね。
Packet Filter
InterNet |-------|em0|----|PF|----|OpenBSD|----|PF|----|dc0|-------| LocalNet
packet filter(PF)の概念は、上記の図で表せそう。真ん中にあるのはOpenBSDのホスト(kernel)だ。 em0,dc0は、NIC(ネットワークI/F)。2枚刺さっていると、インターネットをローカル側と結びつけるルーターになる。身近にあるな。NTTが設置した光回線の装置がそれだ。
BSDの場合は、NICにちゃんとした呼称が付いている。それに対してリナときたら、昔は味も素っ気もない、eth0,eth1って名前だったな。最近は少しは態度を改めたようで、enp8s0みたいな名前になっている。何でもNICが刺さっているスロットを表しているらしい。dmsgの表示と言い、リナな連中にはハードウェアへの愛ってものが無いのかね。オイラーのリナ嫌いの一端が、こんな所に表われているのさ。
ちなみに、em0とかは何よ? そんなの、/sys/arch/amd64/conf/GENERICを見れば書いてある
em* at pci? # Intel Pro/1000 ethernet dc* at pci? # 21143, "tulip" clone ethernet
emて名前のNICは、PCI-Busに差し込む、Intelのやつって一目瞭然。それで飽き足らんかったら、OS起動時のログdmesgを見れば、プローブした結果が出て来る。チップ名まで分かるって楽しいな。
em0 at pci2 dev 0 function 0 "Intel 82545EM" rev 0x01: apic 1 int 18, address 00:0c:29:aa:bb:cc
そんなのリナでもコマンド叩けば分かるよ。そんなにコマンド増やしてどうするねん、と言っておこうか。
debian:tmp$ sudo dmesg|grep enp8 [ 3.188229] r8169 0000:08:00.0 enp8s0: renamed from eth0
ああ、一応dmesgにも出て来てるな。昔の名前だとeth0でしたとさ。
また脱線したな。PFを有効にするか否かは、専用コマンドで行う。pfctl -e で、イネーブル。-dでディセーブル、バイパスするって事だ。
ルールは /etc/pf.confに書く。再読み込みは、pfctl -f /etc/pf.conf だ。ヘタな設定をしちゃうと、2度とsshでログイン出来なくなるんで、シリアル回線でloginの上操作をすればよい。そんな事がいつかは起きるだろうと思っていたんで、手回しよく準備しといたよ(イトカワの面々みたいだな)。
で、フィルターをイネーブルにすると、その設定により、パケットを通過させたり(pass)、破棄(block)させたり出来る。パケットはOSから見て入って来る(in)か、出ていくか(out)の方向がある。
ファイアウォールだと、外からやって来るパケットを破棄するのが主目的になろう。いや、出てくパケットを落としたい事もあるぞ。会社で使うなら、youtubeなんて見るんじゃねぇとか、有るだろう。そういう場合、outの何某に付いてはドロップさせるとか、いやらしく、うんと帯域を絞るなんて芸当も出来る。勿論、パケットの書き換えとかも出来るんでNATも朝飯前だ。
余りに機能が多すぎて、それ用の本が出版されてる。そして著者が講演録を公開してくれている。Firewalling with OpenBSD's PF packet filter
まずは、みようみまねで /etc/pf.conf
set skip on lo block in all pass out all keep state pass in on egress proto tcp from any to egress port ssh modulate state
ルールの適用は、最後にマッチしたものが、勝者となり有効化される。
loって言うループバックI/Fは、ルールの適用外。次は、全部禁止ね。これじゃ、どうにもならないので、出てくパケットは、有効化するよ。この時、sshのセッションみたいに、行ったり来たりするのも有るだろう。そういうのに備えて、接続状況を覚えておくよって設定。 この行が無いと、中から外にsshが出来なくなる。
最後の行は、外から入って来るssh接続要求は、許してあげるって設定だ。on egress ってのは、デフォルトgwが設定されてるNICを表すaliasだ。
protoは、TCP,UDP,ICMPのどれかだ(細かい事を言うと、IPv4とIPv6の詳細に分かれる)。 UDPとICMPについての設定が無いので、pingにも反応しない、困ったちゃんぽい設定になる。
aa.bb.cc.1 VMware GW? aa.bb.cc.128 OpenBSD(ob) aa.bb.cc.129 Debian(pen)
これからの実行例では、生IPが出て来るけど、そのまま晒すのもあれなんで、CIDR/24の部分は伏字にした(クレカの請求書に書かれている、口座番号みたいだな)。カッコ内は、プロンプトだよ。
sakae@pen:~$ ping aa.bb.cc.128 PING aa.bb.cc.128 (aa.bb.cc.128) 56(84) bytes of data. ^C --- aa.bb.cc.128 ping statistics --- 11 packets transmitted, 0 received, 100% packet loss, time 231ms sakae@pen:~$ ping aa.bb.cc.128 PING aa.bb.cc.128 (aa.bb.cc.128) 56(84) bytes of data. 64 bytes from aa.bb.cc.128: icmp_seq=1 ttl=255 time=0.599 ms 64 bytes from aa.bb.cc.128: icmp_seq=2 ttl=255 time=0.987 ms
下側は、pfctl -d して、無効化した場合だ。尚、OpenBSD側から外側のDebian機に向かってのpingは、何の問題もなく出来る。
ただ、syspatchは内部的にftpが使われている為、ftpの特殊性を考慮してないpf.confでは、利用出来ない。この問題は根が深く、pfだけでは解決出来ない為、ftp-proxyと言うラッパーを用意して、そこにリダイレクトするって方法で逃げている。最近のブラウザーが軒並みftpのサポートを打ち切ったけど、その理由の一つになってるんだろうね。
今度は、OpenBSD側で、erl -name boss@aa.bb.cc.128 して、erlを起動
sakae@pen:~$ echo -ne "\x00\x01\x6e" | nc aa.bb.cc.128 4369 name boss at port 47530 sakae@pen:~$ echo -ne "\x00\x01\x6e" | nc aa.bb.cc.128 4369 ^C
リナ側で確認。下は、PFを有効化した場合。返事は4369へのパケットが拒否されているので、無い。これを有効にしないとな。
epmdのportも開いてあげる。
ob# pfctl -sr block drop in all pass out all flags S/SA pass in on egress inet proto tcp from any to aa.bb.cc.128 port = 22 flags S/SA modulate state pass in on egress inet proto tcp from any to aa.bb.cc.128 port = 4369 flags S/SA modulate state
こんなコマンドを叩くと、現在ロードされてるルールが確認出来る。
ob# pfctl -ss all tcp aa.bb.cc.128:22 <- aa.bb.cc.1:58506 ESTABLISHED:ESTABLISHED all tcp aa.bb.cc.128:40353 -> aa.bb.cc.129:22 ESTABLISHED:ESTABLISHED
また、このコマンドで、内部的に保持してる状態を確認出来る。
なお、PFの先進的(ロギング)な使い方は、 OpenBSD本家でpfを動かす その2 を参照。
erlnag に足枷
普通にerlすると、自由奔放に、通信用のport番号を割り当ててしまう。これって、firewallと極めて相性が悪いよね。だって、firewallはどのポートで待ったらいいか見当付かないから。 だったら、間口を拡げておけってのでは、firewallを築く意味が無い。
そこで、erlの方に足枷をはめて、開くポートを制限しちゃえって発想になる。その為のオプションが、ちゃんと用意されてる。
debian:tmp$ cat ports.config [{kernel,[ {inet_dist_listen_min, 31415}, {inet_dist_listen_max, 31416} ]}].
こんな風にport番号を限定するファイルを作る(2台って言うか2ノード用の例)。
debian:tmp$ erl -name hoge@aa.bb.cc.66 -config ports
そして、起動時に設定ファイルを指定すれば良い。若い番号から使われていくよ。
Protocol 'inet_tcp': register/listen error: eaddrinuse
3台目を起動しようとしたら、こんなエラーになって、起動出来なかった。余裕を持って、リソースを用意しておいた方が良いな。
erl with firewall
te.erl
server_node() -> 'boss@aa.bb.cc.128'.
ホスト間に跨るやりとりになるので、ホストに相当するIPアドレスを付与した。
そして起動時には、-nameを使う。firewallを使う予定なので、portsに制限を課す。
ob$ erl -name boss@aa.bb.cc.128 -config ports Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] Eshell V11.2 (abort with ^G) (boss@aa.bb.cc.128)1> c(te). {ok,te} (boss@aa.bb.cc.128)2> te:start_server(). true (boss@aa.bb.cc.128)3> te:logon(cap). logged_on true
サーバー側でもlogonして、通信出来るようにする。
sakae@pen:/tmp$ erl -name z1@aa.bb.cc.129 Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] Eshell V11.2 (abort with ^G) (z1@aa.bb.cc.129)1> c(te). {ok,te} (z1@aa.bb.cc.129)2> te:logon(hoge). true (z1@aa.bb.cc.129)3> te:message(cap, "hello boss"). ok
こちらはクライアント側。logonした時の logged_on
と言う、微妙な応答が無い。通信も相手側に届いていない。そう、サーバー側のfirewallに穴が開いていないからね。
pass in on egress proto tcp from any to egress port 31415:31416 modulate state
/etc/pf.conf に、上記を追加してから、再読み込み。開くportsに幅が有る場合、コロンで区切る指定が使える。
(z1@aa.bb.cc.129)2> te:logon(hoge). true logged_on (z1@aa.bb.cc.129)3> te:message(cap, "hello boss"). ok sent Message from cap: "Catch him" Message from cap: "I'll trace her" (z1@aa.bb.cc.129)4> te:message(cap, "roger!"). ok sent
今度は、firewallに穴が開いたので、ちゃんと通信出来ます。
list is now: [{<12861.93.0>,hoge},{<0.94.0>,cap}] Message from hoge: "hello boss" (boss@aa.bb.cc.128)4> te:message(hoge, "Catch him"). list is now: [{<12861.93.0>,hoge},{<0.94.0>,cap}] sent ok (boss@aa.bb.cc.128)5> te:message(hoge, "I'll trace her"). list is now: [{<12861.93.0>,hoge},{<0.94.0>,cap}] sent ok list is now: [{<12861.93.0>,hoge},{<0.94.0>,cap}] Message from hoge: "roger!"
こちらは、boss側。奴さんをたいーーほしてこいと指令。capみずから現場に出て、ストーカーもとえ尾行を始めるようです。大将はずっと本部に詰めていないんですかね? 10年前、おれがおれがと言いながら、福島の原発に出向いた首相みたい。何か有ったらどうすると。
現在のfirewall
firewallの今の状況を確認出来る
ob# pfctl -sa FILTER RULES: block drop in all pass out all flags S/SA pass in on egress inet proto tcp from any to aa.bb.cc.128 port = 22 flags S/SA modulate state pass in on egress inet proto tcp from any to aa.bb.cc.128 port = 4369 flags S/SA modulate state pass in on egress inet proto tcp from any to aa.bb.cc.128 port 31415:31416 flags S/SA modulate state STATES: all tcp aa.bb.cc.128:22 <- aa.bb.cc.1:52955 ESTABLISHED:ESTABLISHED all tcp aa.bb.cc.128:26085 -> aa.bb.cc.129:22 ESTABLISHED:ESTABLISHED all tcp aa.bb.cc.128:31415 <- aa.bb.cc.129:50563 ESTABLISHED:ESTABLISHED INFO: Status: Enabled for 0 days 01:07:15 Debug: err State Table Total Rate current entries 3 half-open tcp 0 searches 4108 1.0/s inserts 13 0.0/s removals 10 0.0/s Counters match 22 0.0/s :
このfirewallはオプションで、パケットのかなり細かい所まで確認出来るようになっている。たとえば、TTLの値を抽出したり書き換えたり。/etc/pf.osなんてのも用意されてる。何かと思ったら、パケットを精緻に分析して、相手のOSをフィンガープリントと比較し、炙り出すとか。その為の指紋ファイルがpf.os。
こんなビット操作って、erlangも得意なんだよなあ。今度やってみるか。
Fingerprint entry format: # # wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details # # wwww - window size (can be *, %nnn, Snn or Tnn). The special values # "S" and "T" which are a multiple of MSS or a multiple of MTU # respectively. # ttt - initial TTL # D - don't fragment bit (0 - not set, 1 - set) # ss - overall SYN packet size # OOO - option value and order specification (see below) # OS - OS genre (Linux, Solaris, Windows) # Version - OS Version (2.0.27 on x86, etc) # Subtype - OS subtype or patchlevel (SP3, lo0) # details - Generic OS details
とか、色々なテクニックが使われている。まるで、nmapみたいだな。
とまあ、pf.confを上書きしてしまったけど、オリジナルな奴は
vbox$ doas pfctl -sr block return all pass all flags S/SA block return in on ! lo0 proto tcp from any to any port 6000:6010 block return out log proto tcp all user = 55 block return out log proto udp all user = 55
こんな設定になっている。blockした時、returnで、適切な返答を返す親切設計。それに対して、オイラーの書いた奴は何も返答しない設定だ。ステルス動作してる。
よく、興味本位でtracerouteをすると、途中のルーターでタイムアウトするって事が有る。これは、ステルス動作させてて何の応答も無いものだから、しびれを切らせてtracerouteがタイムアウトさせてるんだ。
こういうステルス動作で防御って言う戦法の他、気に入らないパケットが届いたら、あんたとは話したくないって言う積極的に意思表示も出来る。例えば、finパケットを送って、もうお終いですとか、resetパケットを送って、ピシッと拒否の意思表示をする、とかね。
奥が深くて面白そうだな。
firewall on Linux
リナを商用利用するってのは、RedHat若しくはCentOSを使えってのと、同義語なのかな。ggすると、そんなのばっかり引っかかってくるよ。お遊び系のウブとかDebianの人は、そこまでシビアに考えない?
etc
今回はerlのアプリでメッセンジャーの端くれを試した。中央にサーバーが居るってアーキテクチャだ。サーバー無しの対等な通信システムって、初回起動時に取っ掛かりの通信相手をどうやって見つけるのだろう? Peer to Peer なんてのを調べてみた。有名な取っ掛かりを見つける訳ね。ggのお世話なるのかな?
VMwareのネットワーク構成ってどうなってるの? VMware Playerの仮想ネットワーク | NAT、ブリッジ、ホストオンリーの仕組み なんだ、virtualboxと一緒じゃん。
と言う事は、仮想マシンをWindows10の接続されてるラインまで引っ張り上げないと、どうやってもdebian(32Bit)とは、お話出来ないのだな。