2007.07.29 ng_nat(4) 越え 近頃流行の ng_nat(4) に挑戦をする。単純には以下の URL を参考に作 業すればよい。 http://www.furiru.org/~nyan/unix/ng_nat.html http://www.ebug.jp/pub/docs/20060311/ng_nat.pdf 実際特記事項は無いのだが, ng_nat 複数運用したい場合だとか, そう 言えば natd(8) ってファイアウォールに穴開けられたたなぁとか色々 気がついて ng_nat(4) の限界(?)を調査してみる。 このあたりは netgraph(4) についての調査の延長的な部分があるので http://blog.ninth-nine.com/diary/20060912.txt http://blog.ninth-nine.com/diary/20060911.txt あたりで復習することをお薦めする:-)。いや, 結構リハビリになった というか, よくぞ調べておいてくれた > 俺 って感じで。。 1. ng_nat(4) の複数運用は可能か。 a. ng_nat(4) のマニュアルによれば ng_ipfw(4) を使って, ipfw からのパイプライン(?)を形成している。ここで 60 とか 61 と言ったマジックナンバーが出ているが, 実はてきとーである:-)。 b. ng_ipfw(4) のフックは自然数で 1〜65535 が指定可能。先行 0 はエラーで 10 進数として解釈される。0 は先行 0 の条件にひ っかかるのでエラー。数字以外の文字はエラー。パラメーター チェックを行った後は, strtol して下位 16 ビットを取り出し ている。むむ。65536 指定すると 0 が指定できるかな。特にオ ーバーフローチェックはしてないけど, 運用上注意ということ で。まぁ自動的に何々の類ではないから, 問題無いかな。 c. ng_nat(4) のフックは in と out。これをうまく上下(?)につな げてやればよい。と。 d. 複数運用可能なのは, ng_nat(4) を複数のインスタンスで運用 でき, 入出力(ng_ipfw(4) が担当)で切り分けることができる からである。マニュアルでは 60-out, 61-in として運用して いたのを 160-out, 161-in みたいな形で複数用意すればいい のである。 (このあたり運用については後述) 2. ng_nat(4) は, どの程度 natd(8) の機能をサポートしているか。 現時点で原理的に -use_sockets と -punch_fw に対応していない。 これは userland では実現しやすくても in-kernel では難しいも のであるからです。どちらも in-kernel で socket(3) 通信を必 要とする機能なので, 色々と難しいと思われ。 -log, -deny_incoming, -same_ports, -unregistered_only, -proxy_only, -reverse 及び -alias_address と -target_address に対応している。natd(8) にしても ng_nat(4) にしても libalias(3)のクライアントなので, さっき言った原理的にサポー トされない機能以外は実装可能ですが(-redirect_* 等), いくぶ んインターフェースが実装されてないので ng_nat(4) では実現 できない機能が多々あります。まぁ逆に言えば ng_nat(4) にその ためのインターフェースを用意するだけでなんとかなる世界では ありますが。。。 そもそも -proxy_only って, -proxy_rule が指定でない ng_nat(4) で何を意味するんだろうとか気がついちゃいけない:-0。 他にもへーすげー -log サポートしてるのか〜と試して見てがっ かり。期待した通り動かなかったのでソース読んでみたら 128 バイトのバッファに書き込みして終わり。って。ちょっと…。。 また -dynamic 相当の機能が無いので, 頻繁にエイリアスのアド レスが変わるものに対しては運用が面倒です。もっとも変更する ような事態があれば, その都度一緒に設定してしまうのも手です が。。。 (dhcp のクライアントスクリプトに仕込むなど) また -interface が無いので -alias_address で対応する必要が あります。このあたりのルールは natd(8) に書かれた注意に従 う必要があります。 3. ipfw(ng_ipfw) 使う以外で ng_nat(4) を使う方法があるか。 原理的にある。現実的にはほぼない。なにせ netgraph(4)。とは 言え制限があり, L3 な netgraph(4) である必要があります。こ れは, ng_nat(4) が L2 ヘッダーが除去されている前提で作られ ているためです。L3 ヘッダー(IPv4)前提なので, L2 ヘッダーが 付いていると処理できません:-(。当然 IPv6 パケットも処理で きません。 L3 な netgraph(4) と言うと ng_iface(4) あたりが代表的ですが, これを使う場合 ipfw(ng_ipfw) を使用せずに NAT してくれます。 実際 mpd4(ports/net/mpd4) では set iface enable nat set nat address XXX.XXX.XXX.XXX と指定すると ng_nat(4) で NAT してくれます。 < 6.0-RELEASE以降 気のせいか微妙に使いにくい気がするけど:-)。せめて ppp -nat 程度には簡単に使えないとねぇ。。。いちいち set nat address 指定しないとだめってちょっと…。。 ネットワークインターフェース(NIC)から直接 NAT の設定ができ るといいのですが, このあたり ng_ether(4) を使うと, どうし ても L2 なので上記原理が原因で ng_nat(4) が処理してくれま せん:-(。 というわけであんまり現実的でないです。 > ipfw(ng_ipfw)以外の手段 まぁセキュリティ的な観点で言うと, NAT する前後でこまかくル ールが指定できるという意味では ipfw(ng_ipfw) 使う方が安心 かもしれませんね。なんせ外部から到着したパケットはかならず 自分のネットワーク以外のところから来ますから, それ以外は却 下と設定できる。そういう意味では NATされてる前提で ifpw に ルール仕込むのはなかなか骨が折れます。 4. 実際の運用方法について 今のところ ng_nat(4) でよきに計らってくれる仕組みが無いので, 自前でしこしこしてやる必要があります。まぁそれだけだと不親切 なので, とりあえず rcNG script でも書いてみようかと。 とりあえず下記のような形で設定・運用します。 - /etc/rc.conf - - - - - - - - - - - - - - - - - - - - - - - ng_nat_enable="YES" ng_nat_nodes="nat1 nat2" # ng_nat_nat1_aliasaddr="192.168.0.1" ng_nat_nat1_in="1000" ng_nat_nat1_out="1100" ng_nat_nat1_mask="0xff" ng_nat_nat1_flags="0x4" ng_nat_nat1_interface="fxp0" # ng_nat_nat2_aliasaddr="192.168.1.1" ng_nat_nat2_in="1200" ng_nat_nat2_out="1300" ng_nat_nat2_mask="" ng_nat_nat2_flags="" ng_nat_nat2_interface="fxp1" - /etc/rc.conf - - - - - - - - - - - - - - - - - - - - - - - rcNG script は下記のようになります。 - /etc/rc.d/ng_nat - - - - - - - - - - - - - - - - - - - - - #!/bin/sh # # $FreeBSD$ # # PROVIDE: ng_nat # REQUIRE: ipfw # BEFORE: nsswitch # KEYWORD: nojail . /etc/rc.subr . /etc/network.subr name="ng_nat" rcvar=`set_rcvar` start_precmd="ng_nat_precmd" start_cmd="ng_nat_start" stop_cmd="ng_nat_stop" ng_nat_precmd() { if ! kldstat -q -m ng_ipfw > /dev/null 2>&1; then if ! kldload ng_ipfw; then warn unable to load ng_ipfw module. return 1 fi fi if ! kldstat -q -m ng_nat > /dev/null 2>&1; then if ! kldload ng_nat; then warn unable to load ng_nat module. return 1 fi fi return 0 } ng_nat_start() { local displayed=false local node echo -n " ng_nat(" for node in ${ng_nat_nodes}; do if ${displayed}; then echo -n ", ${node}" else displayed=: echo -n "${node}" sysctl net.inet.ip.fw.one_pass=0 >/dev/null fi eval ng_nat_in=\${ng_nat_${node}_in} eval ng_nat_out=\${ng_nat_${node}_out} eval ng_nat_aliasaddr=\${ng_nat_${node}_aliasaddr} eval ng_nat_interface=\${ng_nat_${node}_interface} eval ng_nat_flags=\${ng_nat_${node}_flags} eval ng_nat_mask=\${ng_nat_${node}_mask} ngctl mkpeer ipfw: nat ${ng_nat_out} out ngctl name ipfw:${ng_nat_out} nat_${node} ngctl connect ipfw: nat_${node}: ${ng_nat_in} in ngctl msg nat_${node}: setaliasaddr ${ng_nat_aliasaddr} if [ x"${ng_nat_flags}" != x"" -a x"${ng_nat_mask}" != x"" ]; then ngctl msg nat_${node}: setmode "{ flags=${ng_nat_flags} mask=${ng_nat_mask} }" fi ipfw -q add ${ng_nat_in} netgraph ${ng_nat_in} ipv4 from any to any in via ${ng_nat_interface} ipfw -q add ${ng_nat_out} netgraph ${ng_nat_out} ipv4 from any to any out via ${ng_nat_interface} done echo -n ")" } ng_nat_stop() { echo -n " ng_nat" for node in ${ng_nat_nodes}; do eval ng_nat_in=\${ng_nat_${node}_in} eval ng_nat_out=\${ng_nat_${node}_out} ipfw -q delete ${ng_nat_in} ${ng_nat_out} ngctl shutdown nat_${node}: done } load_rc_config $name run_rc_command "$1" - /etc/rc.d/ng_nat - - - - - - - - - - - - - - - - - - - - - まぁこれで基本的なことは全部できるようになります。とは言え いくらか問題があって要注意。 a. 本来なら /etc/rc.d/natd のハンドリングと同じく /etc/rc.d/ipfw から /etc/rc.d/ng_nat が呼ばれる必要がある。firewall_enable の状態によらず, ng_nat_enable="YES" によって ipfw が有 効になってしまう。ipfw を組み込んでない場合, 突然通信で きなくなる可能性があるので注意。 b. a 項と絡む話であるが rcNG script としては邪道な方法でオ ーダ調整を行っている。環境によっては効果が無いかも知れ ない。すくなくとも /etc/rc.d/ipfw よりも後には起動する と思われ。。 (※BEFORE の設定と矛盾する場合どうなるか知らない) c. ng_nat_*_in と ng_nat_*_out で指定する数字は ipfw のル ール番号と一致させている。ポリシー的要素もあるが, パラ メータ増やすのが嫌なので。 d. flags, mask の指定は ng_nat(4) 見るべし。とりあえず mask は 0xff にしておけばディフォルトいじらなくてすむかな。 ちなみにここの処理は strtol 関数使ってて, 進数自動判別 なので, 10 進数, 8 進数, 16 進数が指定できる。まぁビッ ト演算して 16 進数使ってねってところで。どういう風に計 算しなきゃアカンかは特に明記しない。がんばれ。 あと ng_nat(4) に現在の設定値参照できないのはちょっと…。 e. 6.2-RELEASE では flags, mask の指定ができない。6-STABLE でも最近(2007/07/25 以降)になってからなので注意。 f. 上記で書いたけど, 設定あっても意味がないものがあるので 注意。 g. /etc/defaults/rc.conf の仕込みを行ってないので, 少なく とも ng_nat_enable="NO" は必須。 h. 気が向いたら PR 書くつもり。でも先にマニュアル書かない と…。最近きついっす:-(。 Written by 重村法克