ほっしーの技術ネタ備忘録

技術ネタの備忘録です。基本的に私が忘れないためのものです。他の人の役にも立つといいなぁ。

なんか fdc0 がエラー吐いてるが…?

という訳で、たまにシステムアップデートして再起動すると、 なんか起動時のログで fdc0 がエラーを吐いている訳です。

fdc0: <Enhanced floppy controller> at port 0x3f0-0x3f5,0x3f7 irq 6 drq 2 on isa0
fdc0: non-PNP ISA device will be removed from GENERIC in FreeBSD 14.
fdc0: cmd 08 failed at in byte 1 of 1
fdc0: sense intr err reading stat reg 0
fdc0: cmd 08 failed at in byte 1 of 1
fdc0: sense intr err reading stat reg 0
fdc0: cmd 08 failed at in byte 1 of 1
fdc0: sense intr err reading stat reg 0
fdc0: cmd 08 failed at in byte 1 of 1
fdc0: sense intr err reading stat reg 0

うーん?フロッピーのコントローラですか……しかも ISA の……? いや、このマシンにフロッピーディスクドライブなんてついてないが???

という訳で、そんなときはこれ。 /boot/loader.conf に以下の一行を追加。

hint.fdc.0.disabled="1"

はい、消えたー。

てか、いつのバージョンからこれ出るようになったんだろう?ちなみに今は 13.1-R。

PPPoE ルータの機能も持ってきてしまった話

普通に Buffalo とか NEC の民生用ルータを使ってたんだけれども、 いい加減いろいろ嫌気がさしてきたので。

まぁ、おうちサーバのメンテをするとインターネットに繋がらなくなる問題があるので、 今まで妥協して民生用ルータを使っていたけれども、最近は IPv6 だけでもだいぶ生きていけるし、 いざとなればスマフォがあるので大丈夫かな?と思って。

PPPoE クライアントの設定をする

いつの間にか、ppp デーモンが PPPoE に対応してたのですね。 なんと便利な時代になったことでしょう。 インストール直後状態の FreeBSD でも設定を書くだけで PPPoE 接続できるだなんて。

などとうわ言のように繰り返しながら設定を書きます。

# /etc/ppp/ppp.conf
default:
  set device PPPoE:em0
  set mru 1454
  set mtu 1454
  disable ipv6cp

prov:
  set authname <PPPoE_LOGIN_ID>
  set authkey <PPPoE_PASSWORD>

末尾2行をプロバイダから提供の文字列に置き換えて使います。あと NIC のデバイス名が1か所。

なお、パスワードをベタで書いてあることから分かる通り、 /etc/ppp/ppp.confパーミッションを 600 にするのを忘れないこと。

# /etc/rc.conf
ppp_enable="YES"
ppp_mode="ddial"
ppp_profile="prov"
ppp_nat="NO"

これで普通に立ち上がるようになります。

$ sudo /etc/rc.d/ppp start
$ ifconfig tun0
tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> metric 0 mtu 1454
        options=80000<LINKSTATE>
        inet XXX.XXX.XXX.XXX --> YYY.YYY.YYY.YYY netmask 0xffffffff
        groups: tun
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
        Opened by PID 735

すばらしい。ただ設定ファイルを書いただけで動くのである。 これが本来 OS のあるべき姿なのかもしれない。 もう昔とは違うということを思い知るのである。

pf NAT の設定をする

まぁ、こっちは MAP-E の時に散々実験したので省略。 基本的に同じことを tun0 に対してやるだけ。

うーん、ひねりがないな。まぁ備忘録として。

ああ、 tun0 のデバイス名は ppp.conf で固定できる気がするからそのうちやる。

あとなんか起動時に warning が出てるのでそのうち調べる。 なんか知らんけど動いてるから気にしなくてもいいのかも知れないけど。

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.cprison_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 つ動いてるのはやっぱり邪魔だし保守の手間が継続ダメージになるので。

特に面白みはないね。