Solaris でファイアーウォールを作ろう/其の1目次†概要†最近では自宅で ADSL などを利用して常時接続を実現されている方も多いのではないかと思います。常時接続の場合モデムでの接続と違い、ダイナミックADSLサービス等を利用して自宅サーバを公開できるなどのメリットがありますが、その反面、常にインターネットに自宅のマシンを公開していることになり、ハッカーからの攻撃されたらどうしよう?と考えると夜も眠れません(私だけ?)そのために、ブロードバンドルーターには簡易ファイアーウォール機能がついているものがほとんどです。しかし、あくまで簡易ファイアーウォールなので、ちょっとこったルール設定したいと思っても、そのようなルールが作れません。企業用のファイアーウォールソフト(CheckPoint 社の FireWall-1等)では、考えられるさまざまな条件のルールが設定できますが、高くてとても手が出ません。というわけで、今回は「自分の望みのフィルタリングができる」を目標にファイアーウォールを作ってみたいと思います。
以前に DLPI を利用したパケットモニタープログラムを紹介しましたが、今度はこれをさらに推し進めて自作のファイアーウォールに挑戦しみます。 ご注意†ここで紹介しているプログラムは、カーネルモジュールです。そのため、プログラム上に問題が
あった場合にはシステムが panic し、場合によってはファイルシステムに重大な障害を引き起
こす可能性があります。 ストリームの概要†以前にも書きましたが Solaris では各ネットワークプロトコルが STREAMS(ストリーム)機構 というしくみで作られています。TCP やIP などのプロトコル STREAMS 機構の共通のプログラミング規則に沿って作られており、各プロトコルは STREAMS module (ストリームモジュール)という形で実装されています。
STREAMS については Sun のドキュメントページにあるSTREAMS Programming Guide が参考になると思います。残念ながら英語版しかありません。日本語化してくれると大変ありがたいのですが・・。 プログラムのイメージ†上図のとおり、ネットワークから来るデータは、デバイスドライバ -> IPモジュール -> TCPモジュールという風に進んでいきます。ここで、デバイスドライバ と IPモジュールの間自分で作ったモジュールを挿入できれば、IP にデータを渡すも破棄するも自由ということになります。つまりファイアーウォールもどきのことができます。 さて、肝心の追加する自作 STREAMS モジュールですが、これは Sun のページでいくつかサンプルのコードが掲載されているので、これを元に作っていきます。簡単なものなら意外に短いコードでできます。 以下のコードで実現しているのは、ソースコード内で指定した特定の IP アドレス(172.29.73.88)から来たパケットを強制的に破棄し、syslog にロギングするというものです。とりあえず最初のテストプログラムなので実用性は低い(無い?)かも知れませんが、このパケットの破棄はカーネルレベルで行われるため、アプリケーションも、はたまた TCP モジュールさえもこの IP アドレスからくるデータを受け取ることはできません。立派にファイアーウォールです。(笑) STREAMS モジュールをつくる上必ずで必要となる「エントリーポイント」について説明します。これは、自分が作ったモジュールを KERNEL が利用できるようにするための決まり事のようなものです。KERNEL に対してこの約束事を守ってさえいれば、モジュールそのものの中身の処理は自分で勝手に作れます。
これらは絶対必要です。・・・が下記のソースのように大して書くことはありません。重要なのは、それぞれの中で使われている modlinkage という構造体です。この構造体はモジュールの特性を決めるさまざまな情報へのリンク情報が入っていて、kernel はこれをたどってモジュールがどのような特徴をもっていて、どのような呼び出しをサポートしているのかを知ることができるので、きわめて重要です。 ソース上では分かりにくいかも知れませんが、modlinkage 構造体は以下のようにさまざまな構造体へのリンクを形成しています。 modlinkage (modlinkage構造体) | +--- modlstrmod (modlstrmod構造体) | +--- mod_strmodops | +--- fw_fmodsw (fmodsw構造体) | +--- fwallinfo (streamtab構造体) | +--- rinit(qinit構造体) | | | +--- fwall_rput() | +--- fwall_open() | +--- fwall_close() | +--- minfo | +--- winit (qinit構造体) | +--- fwall_rput() +--- minfo 途中の構造体群の説明は省きます。man page や Sun のページ を参照してください。
open、close のルーチンはこのモジュールでは特に何もやってません。open された instance 毎の固有の情報を格納する q_ptr という構造体へのポインタをセットしたりするのですが、このモジュールでは利用していないので NULL をセットしています。 ソースコード†1 /* Simple filter 2 * /usr/local/bin/gcc -D_KERNEL fwall.c -c 3 * ld -dn -r fwall.o -o fwall 4 */ 5 6 /* added headers */ 7 #include <sys/modctl.h> 8 9 /* sample headers */ 10 #include <sys/types.h> 11 #include <sys/param.h> 12 #include <sys/stream.h> 13 #include <sys/stropts.h> 14 #include <sys/ddi.h> 15 #include <sys/sunddi.h> 16 17 /* header's for network */ 18 #include <netinet/in.h> 19 #include <sys/types.h> 20 #include <sys/socket.h> 21 #include <sys/stropts.h> 22 #include <sys/dlpi.h> 23 #include <fcntl.h> 24 #include <sys/signal.h> 25 #include <sys/stream.h> 26 #include <net/if.h> 27 #include <netinet/if_ether.h> 28 #include <netinet/in_systm.h> 29 #include <netinet/tcp.h> 30 #include <netinet/ip.h> 31 32 #define REJECTADDR 0xac1d4958 /* 172.29.73.88 */ 33 34 35 static int fwall_open (queue_t*, dev_t*, int, int, cred_t*); 36 static int fwall_rput (queue_t*, mblk_t*); 37 static int fwall_wput (queue_t*, mblk_t*); 38 static int fwall_close (queue_t*, int, int, cred_t*); 39 40 static struct module_info minfo = 41 { 0xdefe, "fwall", 1, INFPSZ, 512, 128 }; 42 43 static struct qinit rinit = { 44 fwall_rput, NULL, fwall_open, fwall_close, NULL, &minfo, NULL}; 45 46 static struct qinit winit = { 47 fwall_wput, NULL, NULL, NULL, NULL, &minfo, NULL}; 48 49 struct streamtab fwmdinfo={ 50 &rinit, &winit, NULL, NULL}; 51 52 static struct fmodsw fw_fmodsw ={ 53 "fwall", &fwmdinfo, D_NEW | D_MP |D_MTQPAIR }; 54 55 struct modlstrmod modlstrmod ={ 56 &mod_strmodops, "simple module for test", &fw_fmodsw }; 57 58 static struct modlinkage modlinkage ={ 59 MODREV_1, (void *)&modlstrmod, NULL }; 60 61 _init() 62 { 63 return (mod_install(&modlinkage)); 64 } 65 66 _info(modinfop) 67 struct modinfo *modinfop; 68 { 69 return (mod_info(&modlinkage, modinfop)); 70 } 71 72 _fini(void) 73 { 74 return (mod_remove(&modlinkage)); 75 } 76 77 78 static int fwall_open (queue_t* q, dev_t *dev, int oflag, int sflag, cred_t *cred) 79 { 80 if (sflag != MODOPEN) 81 return EINVAL; 82 q->q_ptr = WR(q)->q_ptr = NULL; 83 qprocson(q); 84 return (0); 85 } 86 87 static int fwall_close (queue_t *q, int flag, int sflag, cred_t *cred) 88 { 89 qprocsoff(q); 90 q->q_ptr = WR(q)->q_ptr = NULL; 91 return(0); 92 } 93 94 static int 95 fwall_wput(queue_t *q, mblk_t *mp) 96 { 97 putnext(q, mp); 98 return (0); 99 } 100 101 static int 102 fwall_rput(queue_t *q, mblk_t *mp) 103 { 104 struct ip *ip; 105 106 if (mp->b_datap->db_type == M_DATA) { 107 u_char *rptr = mp->b_rptr; 108 int dlen = mp->b_wptr - mp->b_rptr; 109 char msg[100]; 110 111 ip = (struct ip *)&rptr[0]; 112 113 sprintf(msg, "%d.%d.%d.%d -> %d.%d.%d.%d Len:%d Protocol:%d", 114 ip->ip_src._S_un._S_un_b.s_b1, ip->ip_src._S_un._S_un_b.s_b2, ip->ip_src._S_un._S_un_b.s_b3, ip->ip_src._S_un._S_un_b.s_b4, 115 ip->ip_dst._S_un._S_un_b.s_b1, ip->ip_dst._S_un._S_un_b.s_b2, ip->ip_dst._S_un._S_un_b.s_b3, ip->ip_dst._S_un._S_un_b.s_b4, 116 ip->ip_len, ip->ip_p); 117 cmn_err(CE_CONT, "%s",msg); 118 119 if( ip->ip_src._S_un._S_addr == REJECTADDR){ 120 freemsg(mp); 121 cmn_err(CE_CONT, "Packet Dropped"); 122 return(0); 123 } 124 125 } /* if M_DATA */ 126 127 putnext(q, mp); 128 return (0); 129 } ソースファイル fwall.c 使い方†コンパイル&インストール†コンパイルは本当は kernel モジュールなので Sun の compiler を使ったほうがよいと思うのですが、そんなもの無いので gcc を使いました。あと、私のマシンが sun4m マシンなので、作るモジュールは 32bit モジュールです。最近の sun4u マシンを使って 64bit kernel をあげている人は 64bit モジュールを作る必要があります。Sun の sparc compiler などであれば -xarch=v9 というオプションを使えば、64bit モジュールができるはずです。gcc の version 3.2.2 では -m64 というオプションで 64bit モジュールを作成できます。(其の2 を参照してください。) # /usr/local/bin/gcc fwall.c -D_KERNEL -c # /usr/ucb/ld -dn -r fwall.o -o fwall あとは、出来上がった fwall モジュールを しかるべきところにコピーしておきます。 # /bin/cp fwall /kernel/strmod/fwall sun4u マシンで 64bit kernel をつかっていて、64bit モジュールを作成した場合にはコピーする path がちょっと変わります。 # /bin/cp fwall /kernel/strmod/sparcv9/fwall ファイアーウォールモジュールをロードする†上でインストールしたモジュールをロードします。これは modload コマンドを使います。モジュールが正しい位置にコピーされていれば、モジュールの path を指定する必要はありません。 # /usr/sbin/modload fwall 続いて、ロードしたモジュールをデバイスドライバ と IPモジュールの間に挿入します。これは ifconfig コマンドを使います。最後の @2 というのはモジュールの追加位置です。 普通は @2 で大丈夫です。 # ifconfig le0 modinsert fwall@2 モジュールが追加されているかどうかを確認します。 # ifconfig le0 modlist 0 arp 1 ip 2 fwall <------ le(デバイスドライバ)と IP モジュールの間にいます! 3 le 比較のため、追加される前は以下のように見えます。 # ifconfig le0 modlist 0 arp 1 ip 2 le 上記手順がちょっと面倒くさいので Makefile を作って一気に行えるようにしました。 # make # make install ・・で、コンパイル、ロード、モジュールの追加まで一気に行います。 # make uninstall ・・ で、モジュールの取り外し、アンロード、アンインストールを一気に行います。これで元の状態にもどります(のはず!) 1 CC = /usr/local/bin/gcc 2 PRODUCTS = fwall 3 AUTOPUSH = /etc/autopush 4 ECHO = /bin/echo 5 CP = /bin/cp 6 RM = /bin/rm 7 LD = /usr/ucb/ld 8 RM = /bin/rm 9 CAT = /bin/cat 10 AWK = /bin/awk 11 MODLOAD = /usr/sbin/modload 12 MODUNLOAD = /usr/sbin/modunload 13 MODINFO = /usr/sbin/modinfo 14 15 16 all: $(PRODUCTS) 17 18 clean: 19 rm -f fwall fwall.o 20 21 fwall: fwall.c 22 $(CC) fwall.c -D_KERNEL -c 23 $(LD) -dn -r fwall.o -o fwall 24 25 install: 26 -$(CP) fwall /kernel/strmod/fwall 27 $(MODLOAD) fwall 28 ifconfig le0 modinsert fwall@2 29 30 uninstall: 31 ifconfig le0 modremove fwall@2 32 -$(MODUNLOAD) -i `$(MODINFO) | $(AWK) '/fwall/{ print $1 }'` 33 -$(RM) /kernel/strmod/fwall これで、あなたのマシンにも自家製ファイアーウォールが導入されました(笑)。 このコードでは 32 行目で指定している 172.29.73.88 というアドレスからきた packet を破棄します。 32 #define REJECTADDR 0xac1d4958 /* 172.29.73.88 */ アドレスは比較が簡単なように16進で表現しました。ここを貴方の「拒否したい」アドレスに変更すれば、このモジュールにてそのアドレスからのパケットは破棄されます。 113 sprintf(msg, "%d.%d.%d.%d -> %d.%d.%d.%d Len:%d Protocol:%d", 114 ip->ip_src._S_un._S_un_b.s_b1, ip->ip_src._S_un._S_un_b.s_b2, 115 ip->ip_dst._S_un._S_un_b.s_b1, ip->ip_dst._S_un._S_un_b.s_b2, 116 ip->ip_len, ip->ip_p); 117 cmn_err(CE_CONT, "%s",msg); 次回はもーちょっと機能を充実したものに挑戦してみます。 |