PF型ファイアウォール ーネイティブメソッド編ー



関連ソースコード
WinPacketFiltering\PacketFiltering.java
WinPacketFiltering_PacketFiltering.h
PacketFiltering.cpp


 ここではマルチプラットフォームを目的とするJavaでは扱えない、ネイティブ(特定の環境固有)な部分をJavaから制御する方法を説明します。 Javaでは実行はJVMが行うのでJVMを越えた、例えばC言語でのメモリ管理のようなあまり強力(危険性のあるよう)なことは出来ないことが多いで す。そこで、そういったことは代わりにC言語にやってもらおう、というのがJNIと呼ばれる、JavaにC言語を組み込む仕組みです。

 JNIを使うときは通常のJavaとはコンパイル方法が違います。もちろんCコンパイラも必要になります。
このページが参考になるでしょう。 http://www.hellohiro.com/native.htm

Windows環境でのパケットフィルタリングのライブラリ


 パケットフィルタリングにはパケットを送受信するデバイスに直接アクセスして制御する必要がありますが、今回はWindowsに標準搭載されているライ ブラリを用います。まずは次のページを見てください。

Packet Filtering Reference
Packet Filtering Reference

 このページで紹介されているPacket Filtering Functions、Packet Filtering Structures、Packet Filtering Enumerated Typesの内、Packet Filtering FunctionsのみをJNIとして実装します。
 ちなみにこれらを使うとC言語でパケットフィルタリングが出来ますが、今回はJavaをメインにしているのでPacket Filtering Structures(構造体群)とPacket Filtering Enumerated Types(定数群)はJavaの方に移植します。
 C言語のみでのパケットフィルタリングの方法はこちらのページで紹介されています。
http://ruffnex.oc.to/kenji/text/fire_a/


フィルタリングに必要な機能


今回はできるだけJavaでフィルタリングを行うのを目的としていますので、なるべくC言語は使わないようにします。C言語で用意しないといけないの が次の機能です。

・フィルタリングの開始
・フィルタリングの停止

この2つは最低必要な機能です。しかし前述したPacket Filtering Referenceでは、フィルタ定義やフィルタを組み合わせたインタフェースハンドルはPVOID型というポインタで管理する為にJavaに渡すことが 出来ません。ですのでJavaでハンドルなどを作成、管理し、フィルタリング実行時にそれをC言語で扱うように変換するという方法をとりました。よって そのための関数がいくつか存在します。


Cで実装するNativeメソッドの準備


 JNIではC言語部分を記述する前にJavaで必要な関数をNativeメソッドとして宣言しておく必要があります。
まずはそれをJavaファイルに記述します。

/*182*/     // (Native) フィルタリングインタフェースの作成
/*183*/ private static native String PfCreateInterface(
/*184*/ int dwName,
/*185*/ // PFFORWARD_ACTION
/*186*/ boolean inAction,
/*187*/ // PFFORWARD_ACTION
/*188*/ boolean outAction,
/*189*/ boolean bUseLog,
/*190*/ boolean bMustBeUnique
/*191*/ );
/*192*/
/*193*/ // (Native) フィルタをインバウンド・インタフェースに加える
/*194*/ private static native String PfAddFiltersToInterfaceIN(
/*195*/ int addrType,
/*196*/ byte[] SrcAddr,
/*197*/ byte[] SrcMask,
/*198*/ byte[] DstAddr,
/*199*/ byte[] DstMask,
/*200*/ int srcPort,
/*201*/ int dstPort,
/*202*/ int ceilingSrcPort,
/*203*/ int ceilingDstPort,
/*204*/ int protocol
/*205*/ );
/*206*/
/*207*/ // (Native) フィルタをアウトバウンド・インタフェースに加える
/*208*/ private static native String PfAddFiltersToInterfaceOUT(
/*209*/ int addrType,
/*210*/ byte[] SrcAddr,
/*211*/ byte[] SrcMask,
/*212*/ byte[] DstAddr,
/*213*/ byte[] DstMask,
/*214*/ int srcPort,
/*215*/ int dstPort,
/*216*/ int ceilingSrcPort,
/*217*/ int ceilingDstPort,
/*218*/ int protocol
/*219*/ );
/*220*/
/*221*/ // (Native) フィルタリングインタフェースをIPアドレスにバインド
/*222*/ private static native String PfBindInterfaceToIPAddress(
/*223*/ int pfatType,
/*224*/ byte[] IPAddress
/*225*/ );
/*226*/
/*227*/ // (Native) フィルタリングインタフェースのアンバインド
/*228*/ private static native String PfUnBindInterface();
/*229*/
/*230*/ // (Native) フィルタリングインタフェースの除去
/*231*/ private static native String PfDeleteInterface();

 これらのメソッドが今回Cで実装する関数となります。各メソッドがprivateとして分けているのは、最終的にJavaでは直接ネイティブ部分にアク セスするのを、フィルタリングの開始と停止に集約する為とCのコード中で、提供されたAPIの関数単位に機能を分割する為です。つまりフィルタリングの開 始ではインバウンド用、アウトバウンド用インタフェースの作成からそれぞれにフィルタの登録、そしてフィルタリングの実行を行い、フィルタリングの停止で はフィルタリングを一時中断し、そのままCで管理するメモリ上のインタフェースの除去を行います。
 Javaファイルを用意できたらコンパイルしCのヘッダファイルを作成します。


C言語で記述するネイティブ関数

 ヘッダファイルが作成できたら、ヘッダファイルに生成された関数のプロトタイプ宣言部分をコピーし、中身を記述していきます。その前に必要となるラ イブラリをインクルードしておきます。

/*  1*/ #include "WinPacketFiltering_PacketFiltering.h"
/* 2*/ #include <stdio.h>
/* 3*/ #include <stdlib.h>
/* 4*/ #include <windows.h>
/* 5*/ #include <fltdefs.h>
/* 6*/
/* 7*/ #pragma comment(lib, "iphlpapi.lib")

1行目はJavaファイルから生成さ れたヘッダファイルです。
2行目はデバッグ用にprintf関数を使っているのでインクルードしています。
3行目はmalloc()とfree()用です。
4行目は FormatMessage関数用です。
5行目は Windowsフィルタリングライブラリがここに入っています。
jni.hはヘッダファイルの方で自動でインクルードされるので改めてインクルードする必要はありません。
7行目のiphlpapi.libはフィルタリングのAPIの為のWindowsライブラリですのでコンパイル時に必要になり ます。プログラム中でリンクしておくと楽です。こ のライブラリファイルはBorland C++ CompilerにはないのでMicrosoft Platform SDKをダウンロードし、このファイルだけコピーしておきましょう。

/*  9*/ #define DEBUG 0
この宣言はデバッグ用です。0を1にするとコンソール上にデバッグ用の情報が出ます。

/* 11*/ void getError(DWORD, LPVOID);
/* 12*/ void JBYTEtoPBYTE(jbyte*, PBYTE, int);
/* 13*/ void showFilter(PF_FILTER_DESCRIPTOR);
/* 14*/ void ByteArrayForkCopy(PBYTE, PBYTE, int);
11行目は、フィルタリングAPIを使ったときはDWORD(U LONG)型で関数の成否が返されるので、
返される結果がエラーかどうかを判定し、エラーならエラーメッセージを返します。
12行目はjavaのjbyte配列をCのPBYTE配列に値をコピーします。(int型→u char型)
13行目はデバッグ用でフィルタを文字列表現にしてコンソール上に表示します。
14行目はbyte型配列の中身をコピーします。Cでは配列のまるごとコピーは行えないので要素を一つずつコピーする必要があります。それをこの関数で行 います。

/* 19*/ // インタフェースハンドルの作成
/* 20*/ JNIEXPORT jstring JNICALL Java_WinPacketFiltering_PacketFiltering_PfCreateInterface
/* 21*/ (JNIEnv *env, jclass cls,
/* 22*/ jint dwName,
/* 23*/ jboolean inAction_i,
/* 24*/ jboolean outAction_i,
/* 25*/ jboolean bUseLog,
/* 26*/ jboolean bMustBeUnique)
/* 27*/ {
/* 28*/ PFFORWARD_ACTION inAction, outAction;
/* 29*/ DWORD result;
/* 30*/ LPVOID lpMsgBuf;
/* 31*/
/* 32*/ if(inAction_i==JNI_TRUE) inAction = PF_ACTION_DROP;
/* 33*/ else inAction = PF_ACTION_FORWARD;
/* 34*/ if(outAction_i==JNI_TRUE) outAction = PF_ACTION_DROP;
/* 35*/ else outAction = PF_ACTION_FORWARD;
/* 36*/
/* 37*/ if(DEBUG) printf("PfCreateInterface : %d, %d, %d, %d, %d, I=%d\n", // デバッグ
/* 38*/ dwName, inAction, outAction, bUseLog, bMustBeUnique, &iHandle); // デバッグ
/* 39*/
/* 40*/ result = PfCreateInterface(dwName, inAction, outAction,
/* 41*/ bUseLog, bMustBeUnique, &iHandle);
/* 42*/
/* 43*/ if(DEBUG) printf("PfCreateInterface : result = %d\n",result); // デバッグ
/* 44*/
/* 45*/ if(result==NO_ERROR)
/* 46*/ {
/* 47*/ return env->NewStringUTF(sNO_ERROR);
/* 48*/ }
/* 49*/ else
/* 50*/ {
/* 51*/ getError(result, &lpMsgBuf);
/* 52*/ return env->NewStringUTF((const char* )lpMsgBuf);
/* 53*/ }
/* 54*/ } // PfCreateInterface END

インタフェースハンドルを新しく作ります。引数のinAction_i、outAction_iの値によってハンドルのタイプを決定させます。
32〜34行目でtrueを渡すと原則禁止、falseでは原則許可としてあります。
引数のbUseLog, bMustBeUniqueを40行目のAPI関数に渡していますが、この2つの意味は不明です。基本的にbUseLogにはfalse、 bMustBeUniqueにはtrueを与えておいて問題はありません。
45〜53行目ではAPI関数の返り値を検査しています。NO_ERRORはAPI中で定義されていますが、(int)0のことです。0以外ならエラー コードが返されますのでそれをgetError関数で文字化し、そのエラーメッセージをJavaのString型で返します。成功時もそれに合わせて String型としています。

/* 56*/ // インバウンド用ハンドルにフィルタを追加
/* 57*/ JNIEXPORT jstring JNICALL Java_WinPacketFiltering_PacketFiltering_PfAddFiltersToInterfaceIN
/* 58*/ (JNIEnv *env, jclass cls,
/* 59*/ jint AddrType,
/* 60*/ jbyteArray SrcAddr,
/* 61*/ jbyteArray SrcMask,
/* 62*/ jbyteArray DstAddr,
/* 63*/ jbyteArray DstMask,
/* 64*/ jint SrcPort,
/* 65*/ jint DstPort,
/* 66*/ jint CeilingSrcPort,
/* 67*/ jint CeilingDstPort,
/* 68*/ jint Protocol)
/* 69*/ {
/* 70*/ PF_FILTER_DESCRIPTOR filter;
/* 71*/ DWORD result;
/* 72*/ LPVOID lpMsgBuf;
/* 73*/ PBYTE adrs;
/* 74*/ int array_size = 0;
/* 75*/ jbyte* Sadrs;
/* 76*/
/* 77*/ filter.dwFilterFlags = 0;
/* 78*/ filter.dwRule = 0;
/* 79*/ filter.fLateBound = 0;
/* 80*/
/* 81*/ if(AddrType==4)
/* 82*/ {
/* 83*/ array_size = 4;
/* 84*/ filter.pfatType = PF_IPV4;
/* 85*/ adrs = (PBYTE)malloc(sizeof(BYTE)*array_size);
/* 86*/ }
/* 87*/ else if(AddrType==6)
/* 88*/ {
/* 89*/ array_size = 16;
/* 90*/ filter.pfatType = PF_IPV6;
/* 91*/ adrs = (PBYTE)malloc(sizeof(BYTE)*array_size);
/* 92*/ }
/* 93*/
/* 94*/ Sadrs = env->GetByteArrayElements(SrcAddr, 0);
/* 95*/ JBYTEtoPBYTE(Sadrs, adrs, array_size);
/* 96*/ filter.SrcAddr = (PBYTE)malloc(sizeof(BYTE)*array_size);
/* 97*/ ByteArrayForkCopy(adrs, filter.SrcAddr, array_size);
/* 98*/ Sadrs = env->GetByteArrayElements(SrcMask, 0);
/* 99*/ JBYTEtoPBYTE(Sadrs, adrs, array_size);
/*100*/ filter.SrcMask = (PBYTE)malloc(sizeof(BYTE)*array_size);
/*101*/ ByteArrayForkCopy(adrs, filter.SrcMask, array_size);
/*102*/ Sadrs = env->GetByteArrayElements(DstAddr, 0);
/*103*/ JBYTEtoPBYTE(Sadrs, adrs, array_size);
/*104*/ filter.DstAddr = (PBYTE)malloc(sizeof(BYTE)*array_size);
/*105*/ ByteArrayForkCopy(adrs, filter.DstAddr, array_size);
/*106*/ Sadrs = env->GetByteArrayElements(DstMask, 0);
/*107*/ JBYTEtoPBYTE(Sadrs, adrs, array_size);
/*108*/ filter.DstMask = (PBYTE)malloc(sizeof(BYTE)*array_size);
/*109*/ ByteArrayForkCopy(adrs, filter.DstMask, array_size);
/*110*/
/*111*/ filter.wSrcPort = SrcPort;
/*112*/ filter.wDstPort = DstPort;
/*113*/ if(CeilingSrcPort == -1) filter.wSrcPortHighRange = SrcPort;
/*114*/ else filter.wSrcPortHighRange = CeilingSrcPort;
/*115*/ if(CeilingDstPort == -1) filter.wDstPortHighRange = DstPort;
/*116*/ else filter.wDstPortHighRange = CeilingDstPort;
/*117*/
/*118*/ switch(Protocol)
/*119*/ {
/*120*/ case 1: filter.dwProtocol = FILTER_PROTO_ANY; break;
/*121*/ case 2: filter.dwProtocol = FILTER_PROTO_ICMP;
/*122*/ filter.wSrcPort = FILTER_ICMP_TYPE_ANY;
/*123*/ filter.wDstPort = FILTER_ICMP_TYPE_ANY;
/*124*/ filter.wSrcPortHighRange = FILTER_ICMP_TYPE_ANY;
/*125*/ filter.wDstPortHighRange = FILTER_ICMP_TYPE_ANY; break;
/*126*/ case 3: filter.dwProtocol = FILTER_PROTO_TCP; break;
/*127*/ case 4: filter.dwProtocol = FILTER_PROTO_UDP; break;
/*128*/ }
/*129*/
/*130*/ if(DEBUG) // デバッグ
/*131*/ {
/*132*/ printf("PfAddFiltersToInterfaceIN : ");
/*133*/ showFilter(filter);
/*134*/ printf("i=%d\n", &iHandle);
/*135*/ }
/*136*/
/*137*/ result = PfAddFiltersToInterface(iHandle, 1, &filter, 0, NULL, NULL);
/*138*/ free(adrs);
/*139*/ free(filter.SrcAddr);
/*140*/ free(filter.SrcMask);
/*141*/ free(filter.DstAddr);
/*142*/ free(filter.DstMask);
/*143*/
/*144*/ if(DEBUG) printf("PfAddFiltersToInterfaceIN : result = %d\n",result); // デバッグ
/*145*/
/*146*/ if(result==NO_ERROR)
/*147*/ {
/*148*/ return env->NewStringUTF(sNO_ERROR);
/*149*/ }
/*150*/ else
/*151*/ {
/*152*/ getError(result, &lpMsgBuf);
/*153*/ return env->NewStringUTF((const char* )lpMsgBuf);
/*154*/ }
/*155*/ } // PfAddFiltersToInterfaceIN END
インバウンド用のハンドルにフィルタを追加する関数です。アウトバウンド用のPfAddFiltersToInterfaceOUT関数もありますが中身 は同じです。
70行目のPF_FILTER_DESCRIPTOR型がCでのフィルタ型です。
77〜79行目で与えているパラメータは固定としています。
81〜92行目ではフィルタのハンドルタイプを指定しています。ハンドルに2種類のアドレスタイプのフィルタをセットできますが、フィルタリング実行時に どちらかを指定するので、選ばれなかった方のタイプは無視されます。
94〜96行目はJavaのbyte配列からCのbyte配列に変換し、それをフィルタの送信元IPアドレスパラメータに代入しています。
111〜116行目ではフィルタのポート番号を設定しています。HighRangeポートはポート番号の上限で、1024番から65535番ポートまでを 指定するならここに65535、対応するwSrcPortかwDstPortに1024を代入します。全てのポートを指定するならwSrcPortか wDstPortに0を代入すればいいです。また、フィルタをIPレベルで用いる場合はこれらの値は無視されます。
118〜127行目はフィルタのプロ トコルを指定します。プロトコルはIPレベル、ICMP、TCP、UDPの4つです。ICMPを選ぶ場合はポート番号に特別な値を代入しておく必要があり ます。

/*258*/ // IPアドレスをインタフェースハンドルにバインド
/*259*/ JNIEXPORT jstring JNICALL Java_WinPacketFiltering_PacketFiltering_PfBindInterfaceToIPAddress
/*260*/ (JNIEnv *env, jclass cls,
/*261*/ jint AddrType,
/*262*/ jbyteArray IPAddress_i)
/*263*/ {
/*264*/ PBYTE IPAddress;
/*265*/ int array_size = 0;
/*266*/ jbyte* SIPAddress;
/*267*/ DWORD result;
/*268*/ LPVOID lpMsgBuf;
/*269*/ PFADDRESSTYPE pfatType = NULL;
/*270*/
/*271*/ if(AddrType==4)
/*272*/ {
/*273*/ array_size = 4;
/*274*/ pfatType = PF_IPV4;
/*275*/ IPAddress = (PBYTE)malloc(sizeof(BYTE)*array_size);
/*276*/ }
/*277*/ else if(AddrType==6)
/*278*/ {
/*279*/ array_size = 16;
/*280*/ pfatType = PF_IPV6;
/*281*/ IPAddress = (PBYTE)malloc(sizeof(BYTE)*array_size);
/*282*/ }
/*283*/
/*284*/ SIPAddress = env->GetByteArrayElements(IPAddress_i, 0);
/*285*/ JBYTEtoPBYTE(SIPAddress, IPAddress, array_size);
/*286*/
/*287*/ if(DEBUG) // デバッグ
/*288*/ {
/*289*/ printf("PfBindInterfaceToIPAddress : %d, %d.%d.%d.%d i=%d\n",
/*290*/ pfatType, IPAddress[0], IPAddress[1],
/*291*/ IPAddress[2], IPAddress[3], &iHandle);
/*292*/ }
/*293*/
/*294*/ result = PfBindInterfaceToIPAddress(iHandle, pfatType, IPAddress);
/*295*/ free(IPAddress);
/*296*/
/*297*/ if(DEBUG) printf("PfBindInterfaceToIPAddress : result = %d\n",result); // デバッグ
/*298*/
/*299*/ if(result==NO_ERROR)
/*300*/ {
/*301*/ return env->NewStringUTF(sNO_ERROR);
/*302*/ }
/*303*/ else
/*304*/ {
/*305*/ getError(result, &lpMsgBuf);
/*306*/ return env->NewStringUTF((const char* )lpMsgBuf);
/*307*/ }
/*308*/ } // PfBindInterfaceToIPAddress END
指定したIPアドレスにインタフェースハンドルをバインドします。この関数によってフィルタリングが開始されます。バインドするIPアドレスにはローカル ホストのものを指定します。

/*310*/ // インタフェースハンドルのアンバウンド
/*311*/ JNIEXPORT jstring JNICALL Java_WinPacketFiltering_PacketFiltering_PfUnBindInterface
/*312*/ (JNIEnv *env, jclass cls)
/*313*/ {
/*314*/ DWORD result;
/*315*/ LPVOID lpMsgBuf;
/*316*/
/*317*/ result = PfUnBindInterface(iHandle);
/*318*/
/*319*/ if(DEBUG) printf("PfUnBindInterface : result = %d\n",result);
/*320*/
/*321*/ if(result==NO_ERROR)
/*322*/ {
/*323*/ return env->NewStringUTF(sNO_ERROR);
/*324*/ }
/*325*/ else
/*326*/ {
/*327*/ getError(result, &lpMsgBuf);
/*328*/ return env->NewStringUTF((const char* )lpMsgBuf);
/*329*/ }
/*330*/ } // PfUnBindInterface END
バインドされたハンドルをアンバウンドします。この時点ではまだハンドルは 残っているので再バウンドすれば同じルールでフィルタリングを再開できます。

/*332*/ // インタフェースハンドルの削除
/*333*/ JNIEXPORT jstring JNICALL Java_WinPacketFiltering_PacketFiltering_PfDeleteInterface
/*334*/ (JNIEnv *env, jclass cls)
/*335*/ {
/*336*/ DWORD result;
/*337*/ LPVOID lpMsgBuf;
/*338*/
/*339*/ result = PfDeleteInterface(iHandle);
/*340*/
/*341*/ if(DEBUG) printf("PfDeleteInterface : result = %d\n",result); // デバッグ
/*342*/
/*343*/ if(result==NO_ERROR)
/*344*/ {
/*345*/ return env->NewStringUTF(sNO_ERROR);
/*346*/ }
/*347*/ else
/*348*/ {
/*349*/ getError(result, &lpMsgBuf);
/*350*/ return env->NewStringUTF((const char* )lpMsgBuf);
/*351*/ }
/*352*/ } // PfDeleteInterface END
作成したハンドルをメモリ上から除去します。


コンパイル

Cファイルが完成したらコンパイルを行います。この時コンパイルオプションでdllファイルが作成されるようにします。dllファイルはJavaの実行 ディレクトリかC:\WINDOWS\system32ディレクトリに配置しておきましょう。

メニューに戻る inserted by FC2 system