geom 管理領域を(無理やり)消す
gmirror とか glabel の管理情報は、該当するオブジェクトの最終セクタに書いてあります。
なので、grom_mirror.so を読み込まない状態で、dd
とかを使って最終セクタをつぶすと、
そのオブジェクトは gmirror への参加をしなくなったり、glabel データがなくなったりします。
その時に使うコマンドについての注意点。
ダメな例。
$ su dd if=/dev/null of=/dev/ada0 bs=512 seek=hogehoge
大丈夫な例。
$ su dd if=/dev/zero of=/dev/ada0 bs=512 seek=hogehoge
おわかりいただけただろうか。
入力インターフェースとして /dev/null
を使うと、こいつはサイズが 0 なのでシークできなくて、
最終セクタを消さないまま成功扱いになります。
これで消したつもりになって kldload geom_mirror
とかやると、意図しない結果になります。
というかなりました。
今まで /dev/null
と /dev/zero
の違いはあまり意識していませんでしたが、
こういうところで差がでるのですね。うーんぐぬぬ。
ポート 33060 ってなんぞ??
netstat
を眺めてたら覚えのないポートがあいてたので調べてみた。
なんか知らんけど MySQL の Extended Interface とかいうものらしい。
なんぞ??
という訳で、 my.cnf
に以下の行を加えて再起動。
mysqlx=OFF
これで消えました。なんだったんだ…??
今回は本当にメモ書きだけ。
ezjail の終了に 10 秒かかる問題を直した
なんか、いつ頃からか。 ある ezjail の仮想マシンを終了するときに 10 秒待たされることに気づいたのでした。
そう、前回の記事と同時に発症してたのです。まぁ、終了時にしか stop しなければ……と思ってましたが、やっぱり不便なのです。 そこでいろいろ調べた記録の片割れです。
その仮想マシンの中では tomcat さんが動いていたので、試しに起動自体をしないようにしてみると、問題なし。 一方、手動で tomcat さんを終了すると 10 秒待つことになる。なるほど、こいつが原因だ。
という訳で、ログを眺めてみると
WARNING [main] org.apache.tomcat.util.net.Acceptor.stop The acceptor thread [ajp-nio-127.0.0.1-8009-Acceptor] did not stop cleanly INFO [main] org.apache.coyote.AbstractProtocol.destroy Destroying ProtocolHandler ["ajp-nio-127.0.0.1-8009"]
うーん、こいつっぽい。
この Acceptor
とやらの実装を眺めてみると、どうやら 10 秒でタイムアウトさせているようだ。
こいつっぽいな。
ていうか非同期のソケットとかにはして……なさそうだな?と思って終了に関する実装を眺めてみると、
どうやら unlockAccept()
メソッドを呼ぶことで「自分自身のソケットに connect
を行うことで accept
から戻らせる」
ということをやっている様子。ま、マジすか……
という訳で、ファイアウォールで AJP のポートを開ける際に、自分自身からの接続も許可してみると、 無事に 1 秒もかからず正常終了するようになりました、とさ。
マジかー、そういう終了シーケンスは初めて見た気がするな……いや、私の見識が狭いだけで、実は定石なん?
ezjail の終了時に umount できない問題を直した
いつの頃からか、手動で ezjail-admin stop
とするとエラーがでる。
どうやら Device is busy
とのこと。 mount | grep /mnt/jails
とかやってみると、ルートを umount
できてない様子。
まぁ、どうせシャットダウン時しかやらなければいいし……とか思って放置してたけど、 それはそれで地味に面倒な気がしてきたので、いい加減まじめに調査した時の記録。
問題が起きる jail はいくつかあるけれども、共通点としてはイメージを 1 ファイルに詰めて
md
でマウントするタイプ(ファイルベース?とか man
にはある)だと問題が起きる様子。
いろいろネットを捜索してみた感じ、だいたいみんな fuser
とか fstat
を使って使用中のファイルを調べてるみたい。
ファイルを開いているプロセスを殺すと umount
できるとか。
だがしかし!今回は誰もルートをマウントしていない!!!なんなら ps
で見ても誰もいない!!
一週間くらい、あーでもないこーでもないと調べまわってたところ、どうやらソケットが残ってる場合も
なぜかルートを umount
できなくなるらしい。あー?うーん??
という訳で、jail の中で netstat -an
とか叩いてみると、なんかコネクション一杯あるなぁ……という訳で、
tcpdrop
を叩いてから ezjail-admin stop
すると安定して正常終了するようになりました。
まぁ、これで解決としてもいいのでは?という気もしたけれども、偶然暇があったので終了スクリプトに組み込もうと…… 思ったところからが地獄の始まりだった気がします。
tcpdrop はホストで実行する必要がある
生きてるコネクションをリストアップする tcpdrop -al
は jail の中でも実行できますが、
それを皆殺しにするにはホストで実行する必要があります。
どうやら、カーネルとの通信は sysctl
を使っているようで、
net.inet.tcp.drop
に殺したいコネクションのエンドポイントを送る形らしい。
そして、カーネルのソースを読むと、どうやらこのリーフは通常の CTLFLAG_RW
になっている(
/usr/src/sys/netinet/tcp_subr.c
あたり )ので、jail の中からは WRITE アクセスは拒否される模様。
気になったのでその辺のカーネルソースを眺めてたら、 /usr/src/sys/kern/kern_priv.c
あたりから、
priv_check_cred_pre()
に飛んで jail の判定を入れているらしい。
/usr/src/sys/kern/kern_jail.c
の prison_priv_check()
では、jail 内部だった場合にホワイトリスト以外は
全て default
でひっかけて EPERM
を返すという強烈な仕様。
うーん、この辺りいじりたくないなぁ……という気持ちになったので諦め。
jail の poststop に仕掛けてみるパターン
jail.conf
には、 poststop
という項目があるので、ここで tcpdrop
すれば良さそう。
jail
コマンドのソースコードを読むと、 prestop (host) -> stop (in jail) -> poststop (host)
での実行になるようだ。
jail に割り当ててる IP を使って grep
すれば良さそう。
そしてみなさまご存じの通り、 /usr/local/etc/ezjail
にある設定ファイルは、環境変数の設定を行うだけで、
/etc/rc.d/jail
でテンポラリの /var/run/jail.NAME.conf
に変換されているので、そのまま設定できそう。
export jail_NAME_exec_poststop0="tcpdrop -al | grep -Fw -e 192.168.0.10 -e 2001:db8::10 | sh"
こんな感じで ezjail の設定として追記しておくと、 /var/run/jail.NAME.conf
では
exec.poststop += "tcpdrop -al | grep -Fw -e 192.168.0.10 -e 2001:db8::10 | sh"
となって、無事に安定して正常終了するようになりました。わーい。
すべての ezjail 設定に書くの面倒だな…?
いや、別にこれでも特に困りはしないんですけどね。
ezjail を何匹も飼ってると、同じことを書くのがだんだん面倒というか、 同じじゃなくて IP アドレスだけ差し替えるのが面倒。
という訳で、 jail.conf
の仕様を眺めてると、なんと変数展開に対応しているらしい。そしてできたのがこれ。
export jail_exec_poststop0="sh -c 'tcpdrop -al | grep -Fw -e \\\${0%/*} -e \\\${1%/*} | sh' \${ip4.addr} \${ip6.addr}"
/etc/rc.conf
に 1 回書いておくだけ。こうすると、各 ezjail を動かすたびに /var/run/jail.NAME.conf
では
exec.poststop += "sh -c 'tcpdrop -al | grep -Fw -e \${0%/*} -e \${1%/*} | sh' ${ip4.addr} ${ip6.addr}"
と変換されて、 jail
コマンドを実行するときに後ろの IP アドレスが変数展開される。
最終的には、ホストで
tcpdrop -al | grep -Fw -e 192.168.0.10 -e 2001:db8::10 | sh
みたいなコマンドが実行されて、無事に TCP コネクションが皆殺しになる。これならいいかな。
IPv6 がない jail でエラーになる&あと同じ IP アドレスが巻き添えになる
ぇー……ダメじゃん……
prestop
ならまだ jail は生きてるから、ホスト側で jexec tcpdrop -al
して sh
に流し込めばいいかな。
tcpdrop -al
の出力がそのまま実行できるスクリプトになってるのは助かった。
……しかし、 /etc/rc.d/jail
が生成する jail.conf
はデフォルトで persist
がついてないので、
中のプロセスが全員死ぬと jail の箱ごと消えるのでした。
でも中のプロセスが殺されたコネクションを再作成してくれると、また umount
できなくなる罠が再発する……
という訳で確実にプロセスを皆殺しにした上で、 tcpdrop -al
を実行する必要がある。はてさて……
そしてしばらく試行錯誤してできたのがこちら。
jail_exec_prestop0="jexec \${name} /bin/sh -c '/bin/sh /etc/rc.shutdown jail >&2 ; kill -TERM -1 >&2 ; sleep 20 >&2 & tcpdrop -al' | sh | logger -t tcpdrop/\${name} -p daemon.notice"
同じく /etc/rc.conf
に書くだけ。前のやつは消すのを忘れずに。
先頭の /etc/rc.shutdown jail
はデフォルトで exec.stop
にあった奴をそのまま持ってきただけ。
その後、 kill -1
で jail の中を掃除します。ただ、これは sh
の内部コマンドになっているので、
これを実行している /bin/sh -c
で始まってるプロセスは jail の中で生き続けます。
でっ、 sleep
コマンドを &
つけて起動することでしばらく jail を生存させ続けて、
その間に jexec
内部で tcpdrop -al
を実行した出力「のみ」を stdout
に送り込む。
他のコマンドはすべて stderr
につながないと sh
が実行できないので注意。
唯一の生存者、 sleep
も、 jail
コマンドの exec.poststop
の後で強制的に kill -TERM
してくれるので、
デフォルトのタイムアウト、10 秒より長ければまぁ何でも変わりはないはず。
ただし、パイプ工事は上手くやらないと sleep
が終わるまで jexec
が終了してくれなかったりするので、
なかなか微妙なバランスで動いてるっぽい。うへぇ……
最期の logger
はおまけ。せっかくなので syslog
にコネクションの撃墜記録でも流しておこうかと(デバッグに役立つので)。
MAP-E の設定を永続化した話
ここまでのあらすじ。
FreeBSD 13.1-R で MAP-E 対応が降りてきたので、いろいろ試して仮組みながら動くようになったのがこの辺。 13.1-R で MAP-E ルータを作ってみる - ほっしーの技術ネタ備忘録
で、その後いろいろ不具合を直した話がこっち。 使い物になる NAT with pf を作る - ほっしーの技術ネタ備忘録
でっ、だいぶ安定して動いてくれてるので、そろそろ再起動後に自動設定されるようにしようかと。というのが今回のお話。
/etc/rc.conf
まぁ、上の記事である通りの設定をそのまま rc.conf
の形式で書くだけなので特に面白みはなく。
# em0 にこっち側の終端 IPv6 アドレスを割り振る ifconfig_em0_alias0="inet6 240b:11:d4e0:2000:6a:49d4:e000:2000 prefixlen 64" # MAP-E の設定をする gateway_enable="YES" gif_interfaces="gif0" gifconfig_gif0="inet6 240b:11:d4e0:2000:6a:49d4:e000:2000 2404:9200:225:100::64" ifconfig_gif0="inet 106.73.212.224/15 106.73.212.224" # pf の設定 pf_enable="YES" pf_rules="/etc/pf.conf" pflog_enable="YES"
今どきは rc
とかいろいろなスクリプトがやたら高機能なので、特に変なことを書く必要はなかったので面白くない。
/etc/pf.conf
あと pf.conf
も改良版で一旦確定。
# /etc/pf.conf scrub all fragment reassemble scrub all reassemble tcp nat log on em0 from 192.168.XXX.0/24 to any -> 106.73.212.224 map-e-portset 4/8/32 pass all pass out log route-to gif0 from 106.73.212.224 pass in log reply-to (em0 192.168.XXX.1) from 106.73.212.224
落ち穂拾い
あとは ipfw のルールに ipencap
を追加するのを忘れない。
いずれ ipfw から完全に移行しておきたい気持ちはあるけれども面倒。 パケットフィルタが 2 つ動いてるのはやっぱり邪魔だし保守の手間が継続ダメージになるので。
特に面白みはないね。
認証エラーログを見て ipfw で ban するやつ
せっかく(10年くらい前に)作ったのでここで公開しておく。
ログを監視して IP アドレスごとバンするやつ
こういうスクリプトを root で走らせておく。
#!/bin/sh detect() { ip=`echo "$1" | egrep -o -e "\[([[:digit:]]+\.){3}[[:digit:]]+\]" | sed -e "s/[][]//g"` if [ ! -z "$ip" ]; then /sbin/ipfw add 8`date +%H` deny all from $ip to any 2>&1 | /usr/bin/sed 's/^/add ipfw rule: /' | /usr/bin/logger -p auth.info -t ${0##*/} -s fi } exit_me() { kill `jobs -p` exit 1 } PID=$$ trap "exit_me" TERM INT tail -n 0 -F /var/log/maillog | (trap "kill $PID" TERM INT && while read line; do if echo "$line" | egrep -e 'Password mismatch ' > /dev/null 2>&1; then detect "$line" fi if echo "$line" | egrep -e 'SASL LOGIN authentication failed:' > /dev/null 2>&1; then detect "$line" fi done) & wait $! exit_me
内容を要約すると、 tail -f /var/log/maillog
を走らせっぱなしにして、
何か出てくるたびに grep
でフィルタして、認証エラーから IP アドレスを抽出したら
ipfw
のルールにつっこむ。
でっ、その時に 800 番台の番号を振っておく。800 + 現在時刻の hour の値。
例えば、10:34 に流れてきたログから抽出した IP アドレスなら 810 って。
800 番台にしたのは、うちの ipfw
ルールが偶然あいてたから。別に何でもいいんだけど。
マッチングルールはまぁいろいろお好みで。 他のコードは、あとで daemon にする時にシグナル周りをいい感じに何とかするためのヤツ。
ほとぼりが冷めたころに ban を解除するやつ
で、あとはこういうスクリプトを cron で毎時 30 分に回す。
#!/bin/sh export LC_ALL=C id=`printf "8%02d" \`expr \( \\\`date +%H\\\` + 20 \) % 24\`` echo "delete ipfw rule: $id" | /usr/bin/logger -p auth.info -t ${0##*/} /sbin/ipfw show | grep "^00$id" | /usr/bin/logger -p auth.info -t ${0##*/} /sbin/ipfw delete $id > /dev/null 2>&1
20:30 に実行されたら、816 番のルールを全部消す。 だいたい 3 時間くらいで解除する感じかな?別にそんなに厳密にする必要もないので、 書きやすい感じで書いただけ。この辺の数字もお好みで。
最初のスクリプトを daemon にする
/usr/local/etc/rc.d/watch_mx
とか適当な名前で下のスクリプトを置いとく。
パスとかは適宜直して使う。
#!/bin/sh # # PROVIDE: watch_mx # REQUIRE: ezjail . /etc/rc.subr name=watch_mx rcvar=watch_mx_enable command=/usr/sbin/daemon load_rc_config $name : ${watch_mx_enable:="NO"} pidfile=${watch_mx_pidfile:="/var/run/watch_mx.pid"} command_args="-cfr -P ${pidfile} -t ${name} /usr/local/bin/${name}.sh" run_rc_command "$1"
こっからメモ書き(ポエム)
/usr/sbin/daemon
って初めて使ったけど案外便利ね。
基本的なノリとしては
$ sudo daemon -cfr -P /var/run/watch_mx.pid -t watch_mx sh ~/bin/watch_mx.sh
こんな感じで起動すると、 sh
以降のコマンドを子プロセスとして起動して、
死んでたら再起動したり、シグナルを中継したりしてくれるみたい。
実際、 tail
の方に SIGKILL
とか送ると勝手に再起動してくれる。
sh
の方に送ってもちゃんと再起動してもらうために、
パイプの接続先は子プロセスでも trap
してから while read
とかしてる。
あんま見かけない書き方だけど、なんか動いてるから多分大丈夫(?)。
LISTEN から accept
して fork
するだけの、サーバプログラムの共通になりがちなコードの
親プロセス部分の実装が inetd
だとするなら、daemon の実実装部分の管理をするだけの
親プロセス部分が daemon
といったところか?
ただ、PGID の親が daemon
さんになってしまうせいで、
kill -$$
がちゃんと動かないのはハマった。
子プロセスからみた自 PID、 $$
と同じ PGID は存在しないので。
おかげでシグナルトラップをちゃんと動かすのに大変手間取った……
解決策は
$ kill `jobs -p`
こんな感じ。
素直に perl
とかで書けばよかった気がしないでもない。
なんでもかんでも root
で動かすとセキュリティホールになるしね。
いずれ作り直したいところではある。
2022-09-29 注:IPv6 対応がバグっているまま10年以上運用していたらしい。まぁ別に害がある訳でもないか……
ssh での接続がなんか遅い問題の対策
なんか知らんけど、前々からさくら VPS にある FreeBSD マシンへ SSH するときに限って、 なんか接続に時間がかかる。
ssh
って打ってから公開鍵のパスフレーズを聞かれるまでに体感で 5 秒くらい待たされる。
でも2回目以降は他の場所にあるマシンと同じくらいのリズムでつながるようになる。
……あ~~~~~~~~これ絶対 DNS とか名前引きとかそういうアレだよー……タイムアウトしたりそれがキャッシュされたり忘れたりする挙動、これ絶対 DNS とかそういうやつだよ……
みたいなことを思いながら調べた時の記録。
まずは接続するときに ssh -vvv
で接続する。
遅いときは最初にあるサーバからのあいさつが遅い。
つまり返事するまでにサーバが何かしてる。
次。 /etc/ssh/sshd_config
で LogLevel DEBUG3
にする。
過去のトラウマがフラッシュバックしつつ $ sudo /etc/rc.d/sshd restart &
を叩く。
嫌な予感がする時は、最後におまじないのごとく &
をつけるのがポイント。こいつがあれば大体救われる。
XXX XX 03:44:32 host sshd[19640]: debug3: Trying to reverse map address 240b:11:xxxx:xxxx:xxxx:xxxx:xxxx:xxxxx. XXX XX 03:44:35 host sshd[19640]: Connection from <snip>
ほらー!なにこのタイムスタンプの不自然な間は!!! どうみてもクライアントの IPv6 アドレスを逆引きしようとしてタイムアウトしてんじゃん。
$ drill -x 240b:11:xxxx:xxxx:xxxx:xxxx:xxxx:xxxxx ;; Query time: 4267 msec
ほらぁー……
という訳で、原因が分かったので対策します。
今回は /etc/rc.conf
で local_unbound_enable="YES"
こういうことして、
unbound をローカル用のリゾルバにしてたので、そこに固定のレコードを入れることに。
/etc/unbound/unbound.conf
の server
セクションに以下の行を追加。
local-zone: "0.0.0.2.0.e.4.d.1.1.0.0.b.0.4.2.ip6.arpa." static
そして
$ sudo /etc/rc.d/local_unbound restart $ drill -x 240b:11:xxxx:xxxx:xxxx:xxxx:xxxx:xxxxx ;; ->>HEADER<<- opcode: QUERY, rcode: NXDOMAIN, id: 23003 ;; Query time: 1 msec
ほい。この IP アドレスは誰も返事を返しません。あきらめてください。
これで ssh での接続が格段に速くなりました。やったね。
……これでいいのか?