はじめに
Windowsのコマンドラインからnetstat -aを実行すると、現在TCP/IPの接続や待ち受けを行っているIPアドレスおよびポート番号の一覧が表示できることは、みなさんご存知だと思います。そして、WindowsXPでは、パラメータに-oを付けることで、その接続を使用しているプロセスの番号も表示されます。本稿では、この情報をプロセス名およびアイコンでビジュアルに表示するプログラムを紹介します。
なお、これと同様のプログラムは、すでにSysinternalsにおいて、TCPView v2.4としてソースコードとともに紹介されています。しかし、Sysinternalsで紹介されているものは、バイナリでは、WindowsNT/2000でもプロセスの情報が表示できているのに、ソースコードでは、WindowsXPでしか表示できません。
また、同様の処理を行う各種のプログラムの情報は、既にインターネットでたくさん公開されておりますが、やはりWindowsXP以外で、プロセスIDを取得できるものは見かけないようです。これは、WindowsXP以前のOSにプロセス番号を取得できるAPIが存在しないことが大きな要因であると思われます。
本稿では、各OSで使用可能なAPIとカーネルオブジェクトを参照することで、WindowsNT/2000でもプロセス番号を取得することができる手法を解説します。
対象読者
- WindowsでC++を使用してネットワーク関連のプログラムを作成される方。
- Windowsのカーネルオブジェクトや仮想メモリ等の内容に興味をお持ちの方。
必要な環境
- サンプルプログラムは、Windows98/NT/2000/XPで動作します(※Windows98では、プロセスの情報は表示できません)。
- サンプルコードは、C++Builder6(コマンドライン版は、C++Builder6 および VC7)でコンパイルが可能です。
TCP/UDPの状態を表示する方法
TCP/UDPの現在の状態は、「iphlpapi.dll」で提供されるAPIで取得が可能であり、以下の3種類のAPIが利用可能です。
GetTcp(Udp)Table(Windows98/NT/2000/XP)GetExtendedTcp(Udp)Table(WindowsXP SP2)AllocateAndGetTcp(Udp)ExTableFromStack(WindowsXP)<非公開>
以下に、それぞれのAPIについて説明します。
GetTcp(Udp)Table
GetTcpTableおよびGetUdpTableは古くから「iphlpapi.dll」で提供されているAPIであり、Windows98以降のすべてのOSで利用が可能です。GetTcpTableでは、MIB_TCPTABLE構造体を取得し、最終的にMIB_TCPROW構造体の配列を得ることで情報を取得します。
以下のリストが情報を取得するためのサンプルですが、最初にサイズを0に指定して一度呼び出しを行い、必要なバッファのサイズを取得しています。また、取得したデータのうち、ステータスがLISTENの場合、リモート側のIPアドレスには0が入っていますが、ポート番号には無効な値が入ってしまっているようですので、0に初期化しています。なお、最終的に取得できるMIB_TCPROW構造体のメンバーを見ていただければ分かりますが、このAPIでプロセスIDを取得することは、残念ながらできません。
MIB_TCPTABLE *tcp; //TCPテーブルの取得 DWORD Size=0; //第1パラメータにNULLをおいて、必要サイズを DWORD Size に取得する if(ERROR_INSUFFICIENT_BUFFER == GetTcpTable(NULL,&Size,TRUE)){ tcp = (PMIB_TCPTABLE) new char[Size]; if(NO_ERROR == GetTcpTable(tcp,&Size,TRUE)){ // 取得したデータ数は、dwNumEntriesに格納されている // PMIB_TCPEXTABLE tcp から TData *Dataへのコピー for(unsigned int i=0; i<tcp->dwNumEntries && i<TABLE_MAX; i++){ // LISTEN 状態のリモート側のポート番号は // 無効な値が入っているようです if(tcp->table[i].dwRemoteAddr==0) tcp->table[i].dwRemotePort=0; // 取得したデータの処理をここで行います。 } } delete [] tcp }
サンプルプログラムの中では、「Enum_GetTable.cpp」でこのAPIを使用していますので、詳しくはそちらをご参照ください。
GetExtendedTcp(Udp)Table
GetExtendedTcpTableおよびGetExtendedUdpTableは、WindowsXP SP2以降の「iphlpapi.dll」で新しく提供されたAPIです。GetExtendedTcpTableでは、第5パラメータの列挙型TCP_TABLE_CLASSの指定によって取得できるデータが変わりますが、今回のサンプルのような目的で、すべてのステータスの接続情報とプロセス番号を取得する場合は、TCP_TABLE_OWNER_PID_ALLを指定します。TCP_TABLE_OWNER_PID_ALLを指定した場合、第1パラメータに指定したバッファには、MIB_TCPTABLE_OWNER_PID構造体が返され、最終的にMIB_TCPROW_OWNER_PID構造体の配列を得ることができます。このMIB_TCPROW_OWNER_PIDのメンバにはプロセス番号があります。
Microsoftから提供されているドキュメントによると、第4パラメータにはIPv4で使用する場合、定数AF_INET4を指定するように記述されていますが、ご利用の環境でこの定義が無い場合は、従来のAF_INETで問題ありません。
以下のリストがGetExtendedTcpTableを使用したサンプルですが、最初にバッファのサイズを取得したり無効なリモート側のポート番号を初期化したりする手法は、GetTcpTableの時と同じです。
MIB_TCPTABLE_OWNER_PID *tcp; DWORD Size=0; //第1パラメータにNULLをおいて、必要サイズを DWORD Size に取得する if(ERROR_INSUFFICIENT_BUFFER == GetExtendedTcpTable(NULL,&Size,TRUE, AF_INET,TCP_TABLE_OWNER_PID_ALL,0)){ tcp = (PMIB_TCPTABLE_OWNER_PID) new char[Size]; if(NO_ERROR == GetExtendedTcpTable( tcp,&Size,TRUE,AF_INET,TCP_TABLE_OWNER_PID_ALL,0)){ // 取得したデータ数は、dwNumEntriesに格納されている for(unsigned int i=0; i<tcp->dwNumEntries && i<TABLE_MAX; i++){ // LISTEN 状態のリモート側のポート番号は // 無効な値が入っているようです if(tcp->table[i].dwRemoteAddr==0) tcp->table[i].dwRemotePort=0; // 取得したデータの処理をここで行います。 } } delete [] tcp; }
サンプルプログラムの中では、「Enum_GetExTable.cpp」でこのAPIを使用していますので、詳しくはそちらをご参照ください。なお、同モジュールでは、WindowsXP SP2以外で動作させた場合にもエラーが発生しないように、「iphlpapi.dll」から動的にリンクして使用しています。
| カテゴリ | 関数名 | 説明 | 98 | Me | NT(SP4) | 2KPro | XP |
| アダプタ管理 | GetAdapterIndex | アダプタ名からインデックスを取得 | ○ | ○ | |||
| GetAdaptersAddresses | アダプタのアドレスを取得 | ○ | |||||
| GetAdaptersInfo | アダプタ情報の取得(IPアドレス・HDCP・WINSサーバ等) | ○ | ○ | ○ | ○ | ||
| GetPerAdapterInfo | 指定したインタフェースのアダプター情報の取得(DNS一覧など) | ○ | ○ | ||||
| GetUniDirectionalAdapterInfo | IPアドレス情報の取得 | ○ | ○ | ||||
| ARP関連 | CreateIpNetEntry | ARPテーブルの取得 | ○ | ○ | ○ | ○ | ○ |
| CreateProxyArpEntry | PARPのエントリーを作成 | ○ | ○ | ||||
| DeleteIpNetEntry | ARPテーブルからエントリを削除 | ○ | ○ | ○ | ○ | ○ | |
| DeleteProxyArpEntry | PARPテーブルからエントリを削除 | ○ | ○ | ||||
| FlushIpNetTable | 指定したインタフェースのARPテーブルの全削除 | ○ | ○ | ||||
| GetIpNetTable | IPアドレスとMACアドレスの関連を取得 | ○ | ○ | ○ | ○ | ○ | |
| SendARP | ARPリクエストの送信 | ○ | ○ | ||||
| SetIpNetEntry | ARPテーブルの修正 | ○ | ○ | ○ | ○ | ○ | |
| インターフェース管理 | GetFriendlyIfIndex | 旧インデックス( uses only the lower 24bits)の取得 | ○ | ○ | ○ | ○ | ○ |
| GetIfEntry | インターフェース情報の取得(パケット数など) | ○ | ○ | ○ | ○ | ○ | |
| GetIfTable | インターフェース情報の取得(パケット数など) | ○ | ○ | ○ | ○ | ○ | |
| GetInterfaceInfo | インターフェース名の取得 | ○ | ○ | ○ | ○ | ||
| GetNumberOfInterfaces | インターフェース番号の取得 | ○ | ○ | ○ | ○ | ○ | |
| SetIfEntry | インターフェース情報の設定(パケット数など) | ○ | ○ | ○ | ○ | ○ | |
| IP及びICMP関連 | GetIcmpStatistics | ICMP状態の取得 | ○ | ○ | ○ | ○ | ○ |
| GetIpStatistics | IP状態の取得 | ○ | ○ | ○ | ○ | ○ | |
| IcmpCloseHandle | IcmpCreateFileでオープンされたハンドルをクローズ | ○ | ○ | ||||
| IcmpCreateFile | ICMPエコー要求のためのハンドルをオープン | ○ | ○ | ||||
| IcmpParseReplies | ICMPバッファから応答をカウント | ○ | ○ | ||||
| IcmpSendEcho | ICMPエコー要求を送信 | ○ | |||||
| IcmpSendEcho2 | ICMPエコー要求を送信 | ○ | ○ | ||||
| SetIpStatistics | IPフォワーディングの切換え | ○ | ○ | ○ | ○ | ○ | |
| SetIpTTL | デフォルトTTLの設定 | ○ | ○ | ○ | ○ | ○ | |
| IPアドレス管理 | AddIPAddress | IPアドレスの追加 | ○ | ○ | |||
| DeleteIPAddress | IPアドレスの削除 | ○ | ○ | ||||
| GetIpAddrTable | IPアドレステーブルの取得 | ○ | ○ | ○ | ○ | ○ | |
| IpReleaseAddress | IPアドレスの開放(DHCP) | ○ | ○ | ○ | ○ | ||
| IpRenewAddress | IPアドレスの更新(DHCP) | ○ | ○ | ○ | ○ | ||
| ネットワーク構成 | GetNetworkParams | ネットワークパラメータ取得(ホスト名・DNSなど) | ○ | ○ | ○ | ○ | |
| 通知 | NotifyAddrChange | IPアドレステーブルに変化があった時のファンクション設定 | ○ | ○ | |||
| NotifyRouteChange | ルーティングテーブルに変化があった時のファンクション設定 | ○ | ○ | ||||
| ルーティング | CreateIpForwardEntry | ルーティングテーブルの作成 | ○ | ○ | ○ | ○ | ○ |
| DeleteIpForwardEntry | ルーティングテーブルの削除 | ○ | ○ | ○ | ○ | ○ | |
| EnableRouter | IPフォワーディングの有効化 | ○ | ○ | ||||
| GetBestInterface | 指定IPへの最適インターフェースを検索 | ○ | ○ | ○ | ○ | ||
| GetBestRoute | 指定IPへの最適ルートを取得 | ○ | ○ | ○ | ○ | ||
| GetIpForwardTable | ルーティングテーブルの取得 | ○ | ○ | ○ | ○ | ○ | |
| GetRTTAndHopCount | ラウンドトリップタイム(RTT) 及びホップ数の取得 | ○ | ○ | ○ | ○ | ○ | |
| SetIpForwardEntry | ルーティングテーブルの設定 | ○ | ○ | ○ | ○ | ○ | |
| UnenableRouter | IPフォワーディングの無効化 | ○ | ○ | ||||
| TCP及びUDP | GetExtendedTcpTable | TCPテーブルの取得(拡張版) | ※SP2 | ||||
| GetExtendedUdpTable | UDPテーブルの取得(拡張版) | ※SP2 | |||||
| GetOwnerModuleFromTcp6Entry | IPv6 TCPエンドポイントをバインドしたモジュール取得 | ※SP2 | |||||
| GetOwnerModuleFromUdpEntry | IPv4 UDPエンドポイントをバインドしたモジュール取得 | ※SP2 | |||||
| GetOwnerModuleFromUdp6Entry | IPv6 UDPエンドポイントをバインドしたモジュール取得 | ※SP2 | |||||
| GetTcpStatistics | TCPステータス取得 | ○ | ○ | ○ | ○ | ○ | |
| GetTcpTable | TCPテーブルの取得 | ○ | ○ | ○ | ○ | ○ | |
| SetTcpEntry | TCPコネクションのステータスを設定 | ○ | ○ | ○ | ○ | ○ | |
| GetUdpStatistics | UDPステータス取得 | ○ | ○ | ○ | ○ | ○ | |
| GetUdpTable | UDPテーブルの取得 | ○ | ○ | ○ | ○ | ○ |
AllocateAndGetTcp(Udp)ExTableFromStack
AllocateAndGetTcpExTableFromStackおよびAllocateAndGetUdpExTableFromStackは、Windows XP以降の「iphlpapi.dll」でエクスポートされている非公開のAPIです。Windows XP以降のnetstatで-oオプションを指定してプロセス番号が取得できるのは、このAPIが使用されているからのようです。
AllocateAndGetTcpExTableFromStackは、ドキュメント化されていないため、ここでプロトタイプについて紹介しておきます(CodeBreakers-Journal 『Invisibility on NT boxes - How to become unseen on Windows NT』より)。
DWORD WINAPI AllocateAndGetTcpExTableFromStack( OUT PMIB_TCPTABLE_EX * pTcpTableEx, IN BOOL bOrder, IN HANDLE hAllocHeap, IN DWORD dwAllocFlags, IN DWORD dwProtocolVersion; );
第1パラメータは、GetExtendedTcpTableでの場合と同じ構造体のアドレスです。第2パラメータは、検索結果をソートするかどうかのフラグ、第3パラメータは、プロセスからヒープメモリを取得するためのハンドルです。第5パラメータは、プロトコルバージョンですのでAF_INET(2)を指定してください。実は、第4パラメータについては良くわからないのですが、0で動作しているようです。
以下のリストがAllocateAndGetTcpExTableFromStackを使用したサンプルです。
PMIB_TCPEXTABLE tcp; AllocateAndGetTcpExTableFromStack(&tcp,TRUE,GetProcessHeap(),2,2); // 取得したデータ数は、dwNumEntriesに格納されている for(unsigned int i=0;i<tcp->dwNumEntries && i<TABLE_MAX; i++){ // LISTEN 状態のリモート側のポート番号は // 無効な値が入っているようです if(tcp->table[i].dwRemoteAddr==0) tcp->table[i].dwRemotePort=0; // 取得したデータの処理をここで行います。 }
サンプルプログラムの中では、「Enum_Undoc.cpp」でこのAPIを使用していますので、詳しくはそちらを参照してください。なお、当然ですが、非公開APIであるためLoadLibraryおよびGetProcAddressで「iphlpapi.dll」から動的にリンクしないと使用できません。
カーネルオブジェクト参照によるTCP接続情報の取得
Windows2000/NTでは、最初に述べたように、プロセス番号を取得するAPIが存在しませんので、はじめにGetTcp(Udp)Tableを使用して接続状態の一覧を取得した後、そのローカルIPアドレスおよびポート番号を頼りにプロセス番号を検索することになります。
IPアドレスおよびポート番号からプロセス番号を検索するには、次のステップを踏みます。
- ステップ1 TCP(UDP)デバイスのハンドル取得
- ステップ2
ZwQuerySystemInformationによるシステムハンドルの取得 - ステップ3 TCP(UDP)ドライバのアドレスを検索
- ステップ4 他プロセスのTCP(UDP)デバイスハンドルを検索
- ステップ5 TDI Drivers用のコマンドを使用して、接続情報(アドレス・ポート番号)を取得する
それでは、各ステップについて順番に見ていきましょう。
ステップ1 TCP(UDP)デバイスのハンドル取得
現在システム上で使用されている全プロセスのTCP(UDP)の情報はデバイスハンドルを頼りに検索します。このため、最初に検索のキーとしてカレントプロセスで同デバイスをオープンしてその情報を取得します。
TCP(UDP)のデバイス名は、「\\Device\\Tcp」「\\Device\\Udp」と表現されます。ここで、通常のようにデバイスドライバのハンドルオープンとしてCreateFileを使用したいところですが、これは失敗してしまいます。ユーザモードのプログラムからこのデバイスドライバを直接利用することに何らかの制限が加えられているようです。そこで、本サンプルプログラムではネイティブAPIであるZwOpenFileを使用してオープンしています。
次のリストに示されるOpenDevice関数に、デバイス名「\\Device\\Tcp」を与えると、内部でUNICODEに変換してZwOpenFileによりハンドルを取得します。
HANDLE OpenDevice(PCWSTR name)
{
IO_STATUS_BLOCK iosb;
HANDLE h;
UNICODE_STRING Name;
// デバイス名をUNICODE文字列に変換する
RtlInitUnicodeString(&Name,name);
OBJECT_ATTRIBUTES oa = {sizeof oa,0,
&Name,0x00000040L/*OBJ_CASE_INSENSITIVE*/};
// デバイスのオープン
ZwOpenFile(&h,SYNCHRONIZE,&oa,&iosb,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0);
return h; // 取得したハンドルを返す
}
ステップ2 ZwQuerySystemInformationによるシステムハンドルの取得
次の作業は、システム上のすべてのハンドルを取得する作業です。ZwQuerySystemInformationは、システムに関する各種の情報を取得するネイティブAPIです。問い合わせの対象として、第1パラメータにSystemHandleInformation(16)を指定すると各プロセスが開いているハンドルの一覧として、SYSTEM_HANDLE_INFORMATION構造体の配列を取得することができます。
typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG ProcessId;//プロセスID // ハンドルが参照するオブジェクトを識別する番号 UCHAR ObjectTypeNumber; // ハンドルの属性 UCHAR Flags; // 0x01 == PROTECT_FROM_CLOSE 0x02 == INHERIT USHORT Handle; // ハンドルの数値 PVOID Object; // ハンドルが参照するオブジェクトのアドレス // ハンドル作成時にオブジェクトに設定されたアクセス権 ACCESS_MASK GrantedAccess; } SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
次のリストは、ZwQuerySystemInformationによるSYSTEM_HANDLE_INFORMATIONの配列を取得している例です。戻り値がSTATUS_INFO_LENGTH_MISMATCHの間、バッファのサイズを増やしながらループして、最終的に必要なサイズを求めています。
int n = 0x1000; PLONG p = new ULONG[n]; // 最初に0x10000バイト確保する while(ZwQuerySystemInformation(16/*SystemHandleInformation*/, p,n * sizeof *p,0)==0xC0000004L/*STATUS_INFO_LENGTH_MISMATCH*/){ // バッファのサイズが不足している場合、サイズを倍にして再確保する delete [] p; p = new ULONG[n*=2]; } h = PSYSTEM_HANDLE_INFORMATION(p+1);
ステップ3 TCPドライバのアドレス検索
ステップ2で取得した全プロセスのハンドル情報には、ステップ1でオープンしたカレントプロセスのTCP(UDP)デバイスのハンドルの情報も当然含まれているはずです。そこで、取得した一覧の中からプロセス番号(カレントプロセスID)とハンドル番号が一致するデータを検索します。目的の情報が検索できたら、そのオブジェクトアドレスからハンドル情報を取得します。ハンドル情報はFILE_OBJECTとして表現されていますが、このFILE_OBJECTの先頭部分のみをコピーします。コピーしたFILE_OBJECTにはデバイスオブジェクトへのポインタが含まれていますが、TCP(UDP)デバイスを使用している全てのハンドルは、最終的にこのデバイスオブジェクトを使用しているはずですので、このアドレスを記録しておいて後に他プロセスのTCP(UDP)情報を検索する時に使用することにします。
ULONG Pid = (ULONG)GetCurrentProcessId(); for(ULONG i = 0;i < *p;i++){ // 取得した全ハンドル情報をループする if(h[i].ProcessId == Pid){ // カレントプロセスのIDと同じかどうか if(h[i].Handle == (USHORT)hTcp){ // TCPハンドルと同じかどうか // FILE_OBJECTの先頭部分をコピーする PmemCopy-> Copy((ULONG)(h[i].Object),(ULONG)&FO,sizeof(FO)); // FILE_OBJECTのDeviceObjectを記録しておく TcpObject = FO.DeviceObject; } } }
ステップ4 他プロセスのTCP(UDP)デバイスハンドルを検索
ステップ3で取得したデバイスオブジェクトのアドレスをキーとして、ステップ2で取得した全プロセスのシステムハンドル情報からTCP(UDP)デバイスを使用しているハンドルを検索します。
for(ULONG i = 0;i < *p;i++){ // 取得した全ハンドル情報をループする // 対象プロセスのFILE_OBJECTをコピーする FILE_OBJECT_HEAD FO; PmemCopy->Copy((ULONG)(h[i].Object),(ULONG)&FO,sizeof(FO)); // デバイスオブジェクトがステップ3で保存した // デバイスオブジェクトと同じかどうかを比較する if(FO.DeviceObject == TcpObject){ // //FILE_OBJECTのFsContext2が、TDI_TRANSPORT_ADDRESS_FILE //若しくはTDI_CONNECTION_FILEの場合、 //接続情報を保持したハンドルである if(FO.FsContext2==PVOID(1/*TDI_TRANSPORT_ADDRESS_FILE*/) || FO.FsContext2==PVOID(2/*TDI_CONNECTION_FILE*/)){ // 目的のハンドルに対する処理 } } }
サンプルプログラムでは、他のプロセスのオブジェクトをコピーするために物理メモリをコピーする
TPmemCopyというクラスを定義しており、このクラスの中(コンストラクタ)でZwOpenSectionを使用して「\Device\PhysicalMemory」をオープンしています、APIとしてはこれのラッパ関数であるNtOpenSectionや、OpenFileMappingが同様の目的で使用可能であるはずなのですが、どちらも、「\Device\PhysicalMemory」を指定すると失敗してしまいます。これは恐らく少しでも危険な処理を回避するため、いくつかのデバイス名でのオープンを制限しているからだと思われます。ステップ5 TDI Drivers用のコマンドを使用して、接続情報を取得する
ステップ4までで、TCP(UDP)デバイスを使用している他プロセスのハンドル取得が完了しましたが、いよいよこのハンドルを元に接続状態の取得を行います。
カレントプロセスでのハンドルオープン
SYSTEM_HANDLE_INFORMATION構造体のメンバであるハンドルは、当該プロセス上でのハンドル番号になっています。Windowsでは、ハンドル番号は、それぞれのプロセスごとに管理されており、他のプロセスでオープンしたハンドルを他のプロセスから使用することはできません。そこで、API関数のDuplicateHandleを使用して、カレントプロセスで改めてハンドルをオープンします。
HANDLE hDup = NULL; HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, h[i].ProcessId); DuplicateHandle(hProcess,HANDLE(h[i].Handle), GetCurrentProcess(), &hDup, 0,FALSE, DUPLICATE_SAME_ACCESS); CloseHandle(hProcess); if(hDup!=NULL){ // オープンしたハンドルを使用した処理 CloseHandle(hDup); }
TDIコマンドによる情報取得
カレントプロセスで有効なハンドルとしてオープンできたTCP(UDP)デバイスは、DeviceIoControlによってTDIドライバーとしてのコマンドが利用可能です。
ここでは、IPアドレス等の情報を取得するためにTDI_QUERY_ADDRESS_INFOをパラメータとして指定してIOCTL_TDI_QUERY_INFORMATIONを使用しました。なお、TDI_QUERY_ADDRESS_INFOの場合、TDI_ADDRESS_INFO構造体が返されるとドキュメントには記載されているのですが、うまくTRANSPORT_ADDRESS構造体にIPアドレスが乗らなかったため、サンプルでは取得バッファの12~13バイトをポート番号、14~17バイト目をIPアドレスとしてコピーしました。
DWORD Size=0;
TDI_REQUEST_QUERY_INFORMATION reqaddr =
{{0},0x00000003/*TDI_QUERY_ADDRESS_INFO*/};
UCHAR Buffer[128];
if(DeviceIoControl(
hDup,IOCTL_TDI_QUERY_INFORMATION,&reqaddr,sizeof(reqaddr),
Buffer,sizeof(Buffer),&Size,NULL)){
unsigned char Ip[4];
short int Port;
memcpy(&Port,&Buffer[12],2);
memcpy(Ip,&(Buffer[14]),4);
}
それぞれのTCPデバイスハンドルで使用しているローカルのIPアドレスとポート番号が判明すれば、それを、最初にGetTcpTableで取得した一覧と比較することで、どのプロセスが使用しているものかが分かります。同一クライアントの中で多数のポートが利用されていると思いますが、同一アドレスで同一ポートを使用できるのは、1つのプロセスしかあり得ませんので、この検索が可能になるわけです。
サンプルプログラムの中では、「GetId.cpp」でプロセス番号を取得する一連のステップをとおして使用していますので、詳しくはそちらを参照してください。
SE_DEBUG_NAME特権
最後になってしまいましたが、本稿で作成したサンプルプログラムは、他のプロセスのハンドルを取得するために特別なアクセス権を設定しています。マイクロソフトのHOWTOに詳細が公開されています。
サンプルプログラムの中では、「Unit1.cpp」のSetSeDebug関数でこの処理を実行していますので、詳しくはそちらを参照してください。
まとめ
本稿では、TCP(UDP)の接続状態を表示するサンプルを通して、IP Helperで利用可能なAPIと、カーネルオブジェクト経由で情報を取得する手法について書きました。ネットワーク関連のプログラムを作成する際の参考として、わずかながらでもお役に立てれば幸いです。
参考資料
- デバイスドライバからの情報取得 microsoft.public.win32.programmer.networks/portuser.cpp
- TDI Drivers について
- TDI_QUERY_INFORMATION
- PCAUSA NDIS / TDIの各種情報及びツールの公開
- The Code Project 「Getting active TCP/UDP connections on a box」
- Platform SDK: IP Helper
- WindowsNT/2000 ネイティブAPIリファレンス Nebbett,Gary 著 日向 俊二 訳 ISBN4-89471-244-X
- Advanced Windows 改訂第4版 Jeffrey Richter 著 (株)ロングテール/長尾高弘 訳 ISBN4-7561-3805-5

