Linuxを用いた三つのネットワークを繋ぐルータ


前書き

今回、ISDN回線上でBizIP8というサービスを利用して、29ビットマスクの固定IPを取得し、自宅でサーバを運用する事にした。しかし、この29ビットマスクのネットワーク(長いので以後この文書では「29ビットネット」と略させていただく)だけではなく、192.168.0.0/24のローカルネットも接続しなければならない。IP8つじゃうちのマシンたちをすべて繋げるには全然足りないのだ。

そんなわけで以下の三つのネットワークをどうにかして接続しなくてはならない。

その方法として俺が思い付くのは次の三つだ。

DMZ法

The Internet
   |__________
             |
          router A(The Internet side: 192.168.1.1)
             |    (DMZ side: 192.168.1.1)
             |
             | DMZ(29bit net)
             |--------- server A(192.168.1.2)
             |--------- server B(192.168.1.3)
             ....................
             |
          router B(DMZ side: 192.168.1.6)
             |    (local net side: 192.168.0.1)
             |
             |--------- client A(192.168.0.2)
             |--------- client B(192.168.0.3)
             |--------- client C(192.168.0.4)
             ....................

おそらくこれが一番まともな方法であろう。

NAT法

The Internet
   |__________
             |
          router (The Internet side: 192.168.1.0)
             |   (local net side: 192.168.0.1)
             |
             |--------- server A(real address: 192.168.0.2)
             |                  (virtual address: 192.168.1.2)
             |
             |--------- server B(real address: 192.168.0.3)
             |                  (virtual address: 192.168.1.3)
             ..................
             |
             |--------- client A(192.168.0.4)
             |--------- client B(192.168.0.5)
             |--------- client C(192.168.0.6)
             ..................

これは非常に賢い方法だ。29ビットネットを仮想的な存在にし、ルータのインターネット側アドレスをネットワークアドレス(192.168.1.0)にしてしまい、その他のIPへのアクセスがあればNATによってローカルネットに置いてあるサーバへ転送してしまう。ポート単位で転送すれば必要なサービスだけ公開することができる。しかも実体はローカルネットにあるので、同一ネットワークにあるクライアントホストからは自由にアクセスできる。Windowsユーザならサーバにsambaを立ててコンテンツ管理したって安全だ。しかもブロードキャストアドレスまで全部使い切れるので無駄が無い。

ターミナス法

The Internet
   |
   |(The Internet side: 192.168.1.1)
 router (local net side: 192.168.0.1)-----------+
   |(29bit net side: 192.168.1.1)               |
   |                                            |
   | 29bit net                                  | local net
   |                                            |
   |--------- server A(192.168.1.2)             |---- client A(192.168.0.2)
   |--------- server B(192.168.1.3)             |---- client B(192.168.0.3)
   ....................                         |---- client C(192.168.0.4)
                                                |---- client D(192.168.0.5)
                                                ...............

これが今回、俺が行った方法だ。わかりにくいかもしれないが、routerは三つのインターフェイスを持ち、それぞれThe Internet、local net、29bit netに繋がっている。この三つのネットワークを結ぶターミナル駅としてルータに頑張ってもらおうというわけだ。DMZ法のようにすっきりしてもなく、NAT法のように賢くもない。だがDMZ法ほど機材はいらず、NAT法ほど変則的ではない。繋がってるネットワークの一つが192.168.0.0/24のローカルネットであるということを除けば、古式床しいルータのあり方であろう。

また、上記三つの構成名は俺が適当に考えたものであって、正式な呼び方は他にあるかもしれないので注意。

PPPとネットワークの接続

今回用意したのは以下の通り。

NICが三枚あるのは、将来的にADSLやFTTHが使えるようになった時のためだ。また予備のインターフェイスとしても使えるだろう。シリアルはttyS0にTAを繋げ、ttyS1はシリアルコンソールにした。なぜかというとルータの設定をするということで、うっかり間違った操作をしてまったく通信できなくなったりすると困るからだ。

シリアルコンソール

シリアルコンソールの設定は実に簡単で、/etc/inittabの、

#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
#T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100

この二行のうち、シリアルコンソールにしたいほうのコメント(#)を外して、'init q'とコマンドを打ってやるだけだ。俺はttyS1をシリアルコンソールにしたいので下の方のコメントを外し、ついでに速度も115200に上げておいた。

T1:23:respawn:/sbin/getty -L ttyS1 115200 vt100

と、こんな感じ。接続する時は、

$ cu -l ttyS0 -s 115200

とこんな感じだ。接続を切る時は画面に「~.」と打ち込んでエンターしてやればいい。

cuコマンドはuucpパッケージに入ってる。しかし、uucpなどという俺のような最近ネットワークを始めた人間には無縁もいい所の遺物をインストールなどしたくない。変わりのものがあるならぜひ欲しいので 教えて欲しい。minicomというツールを使えばいいのではあるが、どうにもこのソフトは気に入らないのである。やはりcuのようにさくっと繋げられるほうがいい。

また、/etc/lilo.confにもシリアルコンソールの設定を書いてやれば、liloが起動した時点からシリアルコンソールにアクセスできる。万が一のトラブルを考えるとやっておいた方がいいだろう。共通の設定を書く所に、

serial=1,115200n8

と書いておく。1というのはもちろんttyS1の事だ。コンマの後ろは速度で、n8というのはよくわからないのだがつけておけばいいらしい。(参照:Remote Serial Console HOWTO)
そしてロードするカーネルの設定の所にも、

image=/vmlinuz
        label=Linux
        read-only
        append="console=ttyS1,115200n8"

と、このように引数を与えてやるようにしておく。

また、いざというときのためというならrootでログインできないと意味が無いのだが、rootでログインできるttyは/etc/securettyに書かれたものだけになる。デフォルトではシリアルコンソールは書かれてないので、

# serial console
ttyS1

という感じで追加してやる。

PPP

世の中便利になった物で、pppconfigというツールでサクサク設定できる。ちなみにNEC Aterm IT60の初期化コマンドは「ATQ0V1X4\$N1=1」になる。$の前にはバックスラッシュをいれてやらないとダメみたいだ。普通のPPPならこれで特に問題は無いだろうけど、今回はちと特殊。BizIP8のようなサービスはそのまま繋ぐとppp0にネットワークアドレス(上の例で言うと192.168.1.0)が割り振られてしまう。これが困ったもので、デフォルトルートの自動設定が効かなくなったり、29ビットネットに繋がってるNICのIPへのアクセスにフィルタがかけられなくなったりする。なのでAdvanced OptionにIPアドレスをセットする欄があるので、そこに「192.168.1.1:」と突っ込んでおく。最後のコロンを忘れずに。うちの環境だけかも知れないが、この欄は書き直すと前回書いたものを消してくれない。/etc/ppp/peers/providerにそのまま書かれてるだけなので、手で書いた方がいいかもしれない。あとはOS起動時にPPP接続するように、

# touch /etc/ppp/ppp_on_boot

としておく。

NIC

これはまったく簡単で、普通にLANを繋ぐように/etc/network/interfacesに書くだけだ。

# /etc/network/interfaces -- configuration file for ifup(8), ifdown(8)

# The loopback interface
auto lo
iface lo inet loopback

# The first network card - this entry was created during the Debian installation

# (network, broadcast and gateway are optional)
auto eth0
iface eth0 inet static
        address 192.168.0.1
        netmask 255.255.255.0
        network 192.168.0.0
        broadcast 192.168.0.255

auto eth1
iface eth1 inet static
        address 192.168.1.1
        netmask 255.255.255.248
        network 192.168.1.0
        broadcast 192.168.1.7

eth0がローカルネット、eth1が29ビットネットに繋がる。しつこいかもしれないがeth1の設定はあくまで例である。eth0をローカルネットにしたのはそれなりに理由があったりする。Linuxは基本的にNICを認識した順番にeth0、eth1、eth2と番号を振っていくようなので、一番重要なローカルネットとの接続をeth0としたわけだ。これなら最悪NICが1枚しか動かない状態になってもルータへはアクセスできる(イーサケーブル繋ぎかえないといけないけど)。全滅したにしてもシリアルコンソールがある。二重三重のバックアップというわけだ。

そしてルータ機能をオンにするために、

# echo 1 > /proc/sys/net/ipv4/ip_forward

としておくのだが、/etc/network/optionsにip_forward=yesとしておくだけでもいいらしい。うちの場合、このファイルの中身はこんなふうになっている。

ip_forward=yes
spoofprotect=yes
syncookies=yes

パケットフィルタリング

さて、こうして接続が完了したのだが、さすがにフィルタもかけずに運用する程の度胸は無い。とくに29ビットネットでもNFS等を利用したいと考えてるので、開けておくポートは必要最小限にしておきたい。また、なにもフィルタをかけないと29ビットネットからローカルネットへ普通にパケットが通っちゃったりなんかするので精神衛生上よろしくないし、IP偽装等の手段に対する脆弱性が出るんじゃないかと思えて来たりもする。

もっとも、iptablesというのは非常に簡単に扱えるツールで、一個の浮動IPをもらってLAN全体をIPマスカレードするというような、家庭用ルータ的な使い方をするのはすぐにできる。そのへんは 「iptablesクイックスタート」 という文書を(酔狂で)書いてみたので、ちょっと見てみて欲しい。参考になるように余計な物もあるが、実にシンプルにできることがわかると思う。

さて、今回の要求はけっこう複雑だ。

ゲーム云々はせっかく固定IPがあるのだからフルNATしちゃってもいいんだけど、やはり数少ない固定IPだからばんばん使うというわけにもいかない。やはりここはルータのIPを使ってポートフォワードしたいところ。しかし、ポートフォワードをするためにはFORWARDチェインの通過を許可しなくてはならない。例えば192.168.0.2でウェブサーバが動いてて、ルータの29ビットネット側のIPが192.168.1.1として、192.168.0.2のウェブサーバをルータのIPで公開しようとする。その単純な設定はこうだ。

# iptables -A nat-in -p tcp --dport 80 -j ACCEPT
# iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT \
        --to-destination 192.168.0.2
# iptables -A nat-in -m state --state ESTABLISHED,RELATED -j ACCEPT \
        --modprobe=/sbin/modprobe
# iptables -A INPUT -j nat-in
# iptables -A FORWARD -j nat-in

こうすると、

$ telnet 192.168.1.1 80

これで通るのは当り前としても、

$ telnet 192.168.0.2 80

これでも通っちゃうのである。また、FORWARDチェインに入る前に宛先が変換されるので、Dstアドレスを指定する分けには行かない。つまり、80番ポートを許可するとローカルネット内のすべてのマシンの80番ポートへアクセスできてしまうのだ。 FORWARDを許可してるのだから当り前と言えば当り前なのだが、いかんせん気持ち悪いし、万が一29ビットネットのサーバが乗っ取られたさい、セキュリティ上かなり好ましくない状態になる。さらに世の中にはIP偽装とかいう手段もあるそうで、インターネット側からローカルIPを名乗ったパケットが入って来る事もあるらしい。インターフェイスがローカルネット側とppp0だけなら、ppp0から入って来るローカルネットを名乗るパケットを叩き落すだけですむので問題ないのだが……

ということでしばらく悩んでたのだが、mangleテーブルというのを使うとうまくいくことがわかった。

# iptables -A nat-in -p tcp --dport 80 -m mark --mark 80 -j ACCEPT
# iptables -t nat -A PREROUTING -p tcp -d 192.168.1.1 --dport 80 -j DNAT \
        --to-destination 192.168.0.2
# iptables -t mangle -A PREROUTING -p tcp -d 192.168.1.1 --dport 80 -j MARK \
        --set-mark 80
# iptables -A nat-in -m state --state ESTABLISHED,RELATED -j ACCEPT \
        --modprobe=/sbin/modprobe
# iptables -A INPUT -j nat-in
# iptables -A FORWARD -j nat-in

こうやってパケットにマークをつけてやり、そのマークを目印に通過を許可する。これでやっとこうまくいった。

さて、パケットフィルタリングの設定はシェルスクリプトを書いてしまうととても楽だ。試行錯誤しながらフィルタを作ることが多いので、いちいち全部打ち込んでたら日が暮れる。また初期化も簡単に済ませられるのがいい。下にうちで使ってるスクリプトを紹介しておく。いちおうコメントをたくさん書いておくので、参考になれば幸いだ。ツッコミどころがあったら 教えて欲しい

#!/bin/sh

############################## set variables ##################################
IPTABLES=`which iptables`
MODPROBE=`which modprobe`
M29BIT_NET='192.168.1.0/29'
LOCAL_NET='192.168.0.0/24'
LOCAL_AREA="10.0.0.0/8,127,0.0.0/8,172.16.0.0/12,192.168.0.0/32,$M29BIT_NET"
MYADDR='192.168.1.1'
CLIENT_A='192.168.0.1'

################################# initialize ##################################
# システムが最初から持ってるチェインを初期化する
$IPTABLES -t filter -F FORWARD
$IPTABLES -t nat -F POSTROUTING
$IPTABLES -t nat -F PREROUTING
$IPTABLES -F INPUT
$IPTABLES -F FORWARD
$IPTABLES -F OUTPUT

# ユーザー定義チェインを初期化して削除する
myfilters='isdn-in,forward-in,wan-out,wan-in,local-in,nat-in,logdrop'
for filter in `echo $myfilters | awk '{ gsub(/,/, "\n"); print }'`; do
    $IPTABLES -F $filter;
    $IPTABLES -X $filter;
done

############################## logging and DROP ##############################$
# ログに取って叩き落すフィルタを作る。
# 叩き落すものは全部このフィルタに投げてやる
$IPTABLES -N logdrop
$IPTABLES -A logdrop -j LOG --log-level warning -m limit --modprobe=$MODPROBE
$IPTABLES -A logdrop -j DROP

################################ INPUT filter #################################
# ISDNのppp0のためのフィルタ
# めんどくさいので基本的にオープンにしてあるが、sshとpop3は外側から利用
# できないようにしておいた。
$IPTABLES -N isdn-in
$IPTABLES -A isdn-in -p tcp --dport ssh -j logdrop
$IPTABLES -A isdn-in -p tcp --dport 110 -j logdrop
$IPTABLES -A isdn-in -m state --state ESTABLISHED,RELATED -j ACCEPT \
        --modprobe=$MODPROBE
$IPTABLES -A isdn-in -j ACCEPT

################################# FORWARD filter ##############################
# 29ビットネットへ行くパケットのためのフィルタ
# ウェブとメール以外は届かないようにしておいた。
$IPTABLES -N forward-in
# allow http
$IPTABLES -A forward-in -p tcp --dport http -j ACCEPT
# allow smtp
$IPTABLES -A forward-in -p tcp --dport 25 -j ACCEPT
$IPTABLES -A forward-in -m state --state ESTABLISHED,RELATED -j ACCEPT \
        --modprobe=$MODPROBE
$IPTABLES -A forward-in -j logdrop

# ちょっとだけコラム 〜フィルタの順番〜
#
# 上のppp-inとforward-inのフィルタを見比べて欲しい。
# ppp-inは叩き落す接続を指定して、最後に残りは許可としている。
# 対してfoward-inは許可する接続を指定して、最後に残りは不許可としてる。
# iptablesは最初に引っかかったルールに沿ってフィルタするので、その後の
# ルールは適用されないのだ。
# だから最初に全部許可にしてしまうと後から不許可の設定をしても通ってしまうし、
# 同じように最初に不許可の設定をしたら後から許可しても無駄ななのだ。

############################### don't out to WAN ##############################
# WAN側(今回はppp0)に出てっちゃ困るパケットを指定しておく。
$IPTABLES -N wan-out
# ここから
$IPTABLES -A wan-out -p tcp --dport 135 -j logdrop
$IPTABLES -A wan-out -p udp --dport 135 -j logdrop
$IPTABLES -A wan-out -p tcp --dport 137:139 -j logdrop
$IPTABLES -A wan-out -p udp --dport 137:139 -j logdrop
$IPTABLES -A wan-out -p tcp --dport 445 -j logdrop
$IPTABLES -A wan-out -p udp --dport 445 -j logdrop
# ここまでは言わずと知れたMS-Windowsのバカパケット

# こちらは29ビットネットやローカルネット等宛のパケットが外に出ない
# ようにするために設定してある。いらないとは思うけど、念のためね。
for i in `echo $LOCAL_AREA | awk '{ gsub(/,/, "\n"); print }'`; do
    $IPTABLES -A wan-out -d $i -j logdrop
done

############################## don't in from WAN ##############################
# 対してこちらはWAN側から入って来ちゃ困るパケットを指定しておく
$IPTABLES -N wan-in
# バカパケットは当前はじく
$IPTABLES -A wan-in -p tcp --dport 135 -j logdrop
$IPTABLES -A wan-in -p udp --dport 135 -j logdrop
$IPTABLES -A wan-in -p tcp --dport 137:139 -j logdrop
$IPTABLES -A wan-in -p udp --dport 137:139 -j logdrop
$IPTABLES -A wan-in -p tcp --dport 445 -j logdrop
$IPTABLES -A wan-in -p udp --dport 445 -j logdrop

# さっきと同様、ローカルネット等のIPを名乗るパケットが外から入って
# こないようにするための設定。IP偽装対策だけど、いらなそうだなあ。
for i in `echo $LOCAL_AREA | awk '{ gsub(/,/, "\n"); print }'`; do
    $IPTABLES -A wan-in -s $i -j logdrop
done

########################### deny to localnet #################################
# 基本的にローカルネットへのアクセスは全面的に禁止にしたい。
# だがこちらからアクセスした接続の返事が来ないと困るのでこうしておく。
$IPTABLES -N local-in
$IPTABLES -A local-in -m state --state ESTABLISHED,RELATED -j ACCEPT \
        --modprobe=$MODPROBE
$IPTABLES -A local-in -j logdrop

############################## basic policy ###################################
# router on
echo 1 > /proc/sys/net/ipv4/ip_forward

# set policy
# ポリシーの設定ってよくわからないのだが、基本的に通るようにしておいた方が
# いいような気が。殺すとめんどくさいことになりそうなので。
$IPTABLES -P INPUT ACCEPT
$IPTABLES -P OUTPUT ACCEPT
$IPTABLES -P FORWARD ACCEPT

# set ip masquerade
$IPTABLES -t nat -A POSTROUTING -s $LOCAL_NET -d ! $LOCAL_NET -j MASQUERADE

############################### IP forwardings ################################
# 例のポートフォワード問題。
$IPTABLES -N nat-in
# http port forward to yuki
$IPTABLES -A nat-in -p tcp --dport 80 -m mark --mark 80 -j ACCEPT
$IPTABLES -t nat -A PREROUTING -p tcp -d $MYADDR --dport 80 -j DNAT \
        --to-destination $CLIENT_A:80 
$IPTABLES -t mangle -A PREROUTING -p tcp -d $MYADDR --dport 80 -j MARK \
        --set-mark 80

# ホントはSNATも指定しないといけないんだけど、めんどくさいので代わりに。
$IPTABLES -A nat-in -m state --state ESTABLISHED,RELATED -j ACCEPT \
        --modprobe=$MODPROBE

############################### apply filter ##################################
#まずはWAN関係を当てる
# deny go to WAN inside packets
$IPTABLES -A FORWARD -o ppp0 -j wan-out

# deny camouflage IP access
$IPTABLES -A FORWARD -i ppp0 -j wan-in

# allow from localnet
# ローカルネットと29ビットネットからのアクセスを許可。
# 29ビットネットからのフォワードは許可しない。
$IPTABLES -A INPUT -s $LOCAL_NET -j ACCEPT
$IPTABLES -A INPUT -s $M29BIT_NET -j ACCEPT
$IPTABLES -A FORWARD -s $LOCAL_NET -j ACCEPT

# ISDN側から入って来るパケットにフィルタをかける
# from isdn incoming packets filter
$IPTABLES -A INPUT -i ppp0 -j isdn-in

# ポートフォワードの設定を有効にする
# 具体的にはフォワードされたパケットの通過を許可する。
# IP forwarding
$IPTABLES -A INPUT -j nat-in
$IPTABLES -A FORWARD -j nat-in

# ローカルネットへのアクセスを禁じる
# deny access to localnet
$IPTABLES -A FORWARD -s ! $LOCAL_NET -d $LOCAL_NET -j local-in

# WAN側から入って来る29ビットネットへのパケットにフィルタをかける
# from isdn forwarding packeets filter
$IPTABLES -A FORWARD -i ppp0 -j forward-in

以上、これで設定は完了だ。あとは外部からポートスキャン等して正しく動作してるか確かめておこう。

あとはこの設定が再起動してもまた当てられるように、

# /etc/init.d/iptables save active

としておく。

ということで現在、元気に三つのネットワークを繋ぐルータが稼働中だ。Linuxでこのような設定をしてる人は、ウェブを検索してもなかなか見付からなかった。つたない文書ではあるが、誰かの役に立つことを祈るばかりだ。


Sugano "狐志庵" Yoshihisa(E) <koshian@misao.gr.jp>
Back to HOME