Winsock Programmer's FAQ
第7章: 論説記事: WsControl() 内部解析

WsControl() 内部解析

Tom Sanfilippo

解説

WsControl() は、Windows 95 のプログラム winipcfg.exe の中で、インターフェース情報を取得するために使われ ているものです。同様に IP コンフィグ情報を設定するためにも使われ ているようです。私は設定についてはテストしていませんが、以下に述 べる情報によって、数多くの情報取得の呼び出しが可能になるでしょう。 理論的には、winipcfg.exe によって行なわれる "set" 呼び出しをトレー スすることによって、それらの呼び出しに必要な入力パラメタを導き出 すことも可能です。これは読者への宿題としておきましょう。

winipcfg.exe が使用しているモードでは基本的に、 WsControl() によって SNMP 問い合わせ情報にアクセス することができます。例えばルーティングテーブルエントリ、インター フェースリストのエントリ、アダプタの説明などの情報があります(す ごいでしょう!!)。ただし、これが必要となるのは Winsock 2 の使用が 許されない Windows 95 アプリケーションのみです。Windows 98、 Windows NT、Windows 2000 上や、Winsock 2 が使用できる時は、 WsControl() から得られる情報を取得する場合、おそら くWSIoctl()GetInterfaceInfo() を使っ たほうが幸せになれるでしょう(少なくとも winipcfg.exe で使われて いて判明した範囲においては)。

どのように解析を行ったのか

まずフックDLLを使って全てのパラメータをダンプして、その入力と 出力のパターンを探し出しました。そしてその理論上のパターンを検証 するテストプログラムを書きました。この結果を Thomas Divine氏に見せたと ころ、私が見つけ出したパラメータは、NT4 DDK に入っているWSHelper サンプルのパラメータに似ていることに彼は気がつきました。私はその サンプルを見てみて、さらにたくさんのパラメータを探り出すことがで きました。詳細については「パラメータ解析ノート」というタイトルの 章を参照して下さい。

なぜ解析を行ったのか

えーとですね、Microsoft からの公式な情報では、この情報を Winsock 2 無しの Windows 95 アプリケーションからプログラム的に必 要としている場合、あなたのアプリケーションから winipcfg.exe をバッ チモードで実行し(そう、バッチモードがあるんです)、その出力をファ イルに渡して、そのファイルをアプリケーションから読み出す、という ことをしなければならないのです。そんなことをするのは Tcl インター プリタを埋め込むのとほとんど同じくらい楽しそうな作業なので、私は winipcfg.exe がどうやっているのかを調べるほうが面白そうだと思っ たからなのです。

DDKを取得する

必要なヘッダファイルを準備するには、NT4 DDK の最新バージョン を入手する必要があります(プログラムが Windows 95/98 用のもので あってもです)。関連するヘッダファイルは次の通りです:

                \ddk\src\network\inc\tdiinfo.h
                \ddk\src\network\wshsmple\smpletcp.h

これらのヘッダファイルが再配布可能なのかどうかは私は良く分か りませんが、この文章を書いている時点では、Microsoft から NT4 DDK をダウンロー ド することができます。

ダウンロードバージョンのヘッダファイルでも問題なく動作するは ずです。NT4 DDK 全体を引っ張り出したくなければ、上記のヘッダファ イルをプロジェクトのディレクトリにコピーしてください。

WsControl() 呼出し

WsControl()関数はこんな感じになっています:

                int WsControl(
                    DWORD protocol,
                    DWORD action,
                    LPVOID pRequestInfo,
                    LPDWORD pcbRequestInfoLen,
                    LPVOID pResponseInfo,
                    LPDWORD pcbResponseInfoLen
                );
返却値

WsControl()は処理が成功すると 0 を返却します。そ うでない場合は 0 以外の値が返却されます。出力バッファ長 (*pcbResponseInfoLen) が小さすぎる場合には STATUS_BUFFER_TOO_SMALL が返却されることもあります。 WSAGetLastError() で取得できるエラーは生成されない ようです。

パラメータ

pTcpRequestInfoPTCP_REQUEST_QUERY_INFORMATION_EX 型か、あるいは PTCP_REQUEST_SET_INFORMATION_EX型です。winipcfg.exe をトレースした呼出しにおいては、全ての問い合わせ呼出しに共通の入 力は以下のようになっています:

                protocol = IPPROTO_TCP;
                action = WSCTL_TCP_QUERY_INFORMATION;

winipcfg.exe で行われる問い合わせに対する出力構造体が全て存在 しているわけではありません。ですので私がいくつか作っています。私 が作ったものは以下のサンプルコードの中で定義されています。注記: 最新の プラットフォームSDKから、SNMP、WSAIoctl()、 IP Helper データ構造体を分析することで、さらに他のパラメータ定義を 解析することも可能です。

ここでもう一度、NT4 DDK の最新の wshsmple を示してくださった Thomas Divine 氏に深く 感謝いたします。このサンプルのヘッダによって、最終的に入力パラメ タを解析することができたのです。

サンプルコード

以下のサンプルコードは Windows 95 と Windows 98 の両者で動作 します。このサンプルは、起動すると winipcfg.exe が行う問い合わせ を全て真似して実行します。

wsctl.cpp

// wsctl.cpp

// Demonstrates possible calling params for the undocumented wsock32.dll
// api "WsControl", which is used by winipcfg.exe on win95/98.  wsock32.dll
// is the 32-bit thunk to old winsock implementations (I think).

// Gets ip info for the current interface list.

// Tom Sanfilippo
// tsanfilippo@earthlink.net
// December 12, 1999

// Thanks are due to Thomas F. Divine <tdivine@pcausa.com>
// for pointing out the updated wshsmple in the NT4DDK.
// The headers in that sample allowed the input parameters
// to finally be discovered.

#include <windows.h>
#include <stdio.h>

#include <winsock.h>
#include <crtdbg.h>

#include "tdiinfo.h"    // from recent NT4DDK "\ddk\src\network\inc\tdiinfo.h"
#include "smpletcp.h"   // from recent NT4DDK "\ddk\src\network\wshsmple\smpletcp.h"

typedef int (__stdcall * WsControlProc) (DWORD, DWORD, LPVOID, LPDWORD,
                                         LPVOID, LPDWORD);
typedef int (__stdcall * WSAGetLastErrorProc) (void);
typedef int (__stdcall * WSAStartupProc) (WORD wVersionRequested,
                                          LPWSADATA lpWSAData);
typedef int (__stdcall * WSACleanupProc) (void);

WsControlProc WsControl = NULL;
WSAGetLastErrorProc WSAGetLastError1 = NULL;
WSAStartupProc WSAStartup1 = NULL;
WSACleanupProc WSACleanup1 = NULL;

#define WSCTL_TCP_QUERY_INFORMATION 0
#define WSCTL_TCP_SET_INFORMATION   1   //??

#define IP_MIB_ROUTETABLE_ENTRY_ID              0x101

#pragma pack(push,1)

typedef struct IPRouteEntry {
    ulong ire_addr;
    ulong ire_index;            //matches if_index in IFEntry and iae_index in IPAddrEntry
    ulong ire_metric;
    ulong ire_unk1;             //??
    ulong ire_unk2;             //??
    ulong ire_unk3;             //??
    ulong ire_gw;
    ulong ire_unk4;             //??
    ulong ire_unk5;             //??
    ulong ire_unk6;             //??
    ulong ire_mask;
    ulong ire_unk7;             //??
} IPRouteEntry;

#pragma pack(pop)

int main(int argc, char **argv)
{
    int result = 0;
    HMODULE hModule;
    WSADATA WSAData;

    hModule = LoadLibrary("wsock32.dll");
    if (!hModule) {
        fprintf(stderr, "LoadLibrary failed for wsock32.dll (%ld)\n",
                GetLastError());
        return EXIT_FAILURE;
    }

    WsControl = (WsControlProc) GetProcAddress(hModule, "WsControl");
    if (!WsControl) {
        fprintf(stderr, "GetProcAddress failed for WsControl (%ld)\n",
                GetLastError());
        FreeLibrary(hModule);
        return EXIT_FAILURE;
    }

    WSAGetLastError1 =
        (WSAGetLastErrorProc) GetProcAddress(hModule, "WSAGetLastError");
    if (!WSAGetLastError1) {
        fprintf(stderr,
                "GetProcAddress failed for WSAGetLastError (%ld)\n",
                GetLastError());
        FreeLibrary(hModule);
        return EXIT_FAILURE;
    }

    WSAStartup1 = (WSAStartupProc) GetProcAddress(hModule, "WSAStartup");
    if (!WSAStartup1) {
        fprintf(stderr, "GetProcAddress failed for WSAStartup (%ld)\n",
                GetLastError());
        FreeLibrary(hModule);
        return EXIT_FAILURE;
    }

    WSACleanup1 = (WSACleanupProc) GetProcAddress(hModule, "WSACleanup");
    if (!WSACleanup1) {
        fprintf(stderr, "GetProcAddress failed for WSACleanup (%ld)\n",
                GetLastError());
        FreeLibrary(hModule);
        return EXIT_FAILURE;
    }

    result = WSAStartup1(MAKEWORD(1, 1), &WSAData);
    if (result) {
        fprintf(stderr, "WSAStartup failed (%ld)\n", result);
        FreeLibrary(hModule);
        return EXIT_FAILURE;
    }

    TCP_REQUEST_QUERY_INFORMATION_EX tcpRequestQueryInfoEx;

    memset(&tcpRequestQueryInfoEx, 0, sizeof(tcpRequestQueryInfoEx));
    tcpRequestQueryInfoEx.ID.toi_entity.tei_entity = GENERIC_ENTITY;
    tcpRequestQueryInfoEx.ID.toi_entity.tei_instance = 0;
    tcpRequestQueryInfoEx.ID.toi_class = INFO_CLASS_GENERIC;
    tcpRequestQueryInfoEx.ID.toi_type = INFO_TYPE_PROVIDER;
    tcpRequestQueryInfoEx.ID.toi_id = ENTITY_LIST_ID;

    DWORD tcpRequestBufSize = sizeof(tcpRequestQueryInfoEx);

    //this probably allocates too much space; not sure if MAX_TDI_ENTITIES
    //represents the max number of entities that can be returned or, if it
    //is the highest entity value that can be defined.
    DWORD entityIdsBufSize = MAX_TDI_ENTITIES * sizeof(TDIEntityID);

    TDIEntityID *entityIds = (TDIEntityID *) calloc(entityIdsBufSize, 1);

    result = WsControl(IPPROTO_TCP,
                       WSCTL_TCP_QUERY_INFORMATION,
                       &tcpRequestQueryInfoEx,
                       &tcpRequestBufSize, entityIds, &entityIdsBufSize);

    if (result) {
        fprintf(stderr, "%s(%d) WsControl failed (%ld)\n", __FILE__,
                __LINE__, WSAGetLastError1());
        WSACleanup1();
        FreeLibrary(hModule);
        return EXIT_FAILURE;
    }

    //...after the call we compute:
    DWORD entityCount = entityIdsBufSize / sizeof(TDIEntityID);

    DWORD i;
    DWORD ifCount = 0;

    //print out the interface info for the generic interfaces
    for (i = 0; i < entityCount; i++) {

        if (entityIds[i].tei_entity == IF_ENTITY) {

            ++ifCount;

            //see if the iterface supports snmp mib-2 info
            memset(&tcpRequestQueryInfoEx, 0,
                   sizeof(tcpRequestQueryInfoEx));
            tcpRequestQueryInfoEx.ID.toi_entity = entityIds[i];
            tcpRequestQueryInfoEx.ID.toi_class = INFO_CLASS_GENERIC;
            tcpRequestQueryInfoEx.ID.toi_type = INFO_TYPE_PROVIDER;
            tcpRequestQueryInfoEx.ID.toi_id = ENTITY_TYPE_ID;

            ULONG entityType;
            DWORD entityTypeSize = sizeof(entityType);

            result = WsControl(IPPROTO_TCP,
                               WSCTL_TCP_QUERY_INFORMATION,
                               &tcpRequestQueryInfoEx,
                               &tcpRequestBufSize,
                               &entityType, &entityTypeSize);

            if (result) {
                fprintf(stderr, "%s(%d) WsControl failed (%ld)\n",
                        __FILE__, __LINE__, WSAGetLastError1());
                WSACleanup1();
                FreeLibrary(hModule);
                return EXIT_FAILURE;
            }

            if (entityType == IF_MIB) { // Supports MIB-2 interface.

                //get snmp mib-2 info
                tcpRequestQueryInfoEx.ID.toi_class = INFO_CLASS_PROTOCOL;
                tcpRequestQueryInfoEx.ID.toi_id = IF_MIB_STATS_ID;

                //note: win95 winipcfg use 130 for MAX_IFDESCR_LEN while
                //ddk\src\network\wshsmple\SMPLETCP.H defines it as 256
                //we are trying to dup the winipcfg parameters for now
                DWORD ifEntrySize = sizeof(IFEntry) + 128 + 1;
                IFEntry *ifEntry = (IFEntry *) calloc(ifEntrySize, 1);

                result = WsControl(IPPROTO_TCP,
                                   WSCTL_TCP_QUERY_INFORMATION,
                                   &tcpRequestQueryInfoEx,
                                   &tcpRequestBufSize,
                                   ifEntry, &ifEntrySize);

                if (result) {
                    fprintf(stderr, "%s(%d) WsControl failed (%ld)\n",
                            __FILE__, __LINE__, WSAGetLastError1());
                    WSACleanup1();
                    FreeLibrary(hModule);
                    return EXIT_FAILURE;
                }

                //print interface index and description
                *(ifEntry->if_descr + ifEntry->if_descrlen) = 0;
                fprintf(stdout, "IF Index %lu  %s\n", ifEntry->if_index,
                        ifEntry->if_descr);

            }
        }
    }

    //find the ip interface
    for (i = 0; i < entityCount; i++) {

        if (entityIds[i].tei_entity == CL_NL_ENTITY) {

            //get ip interface info
            memset(&tcpRequestQueryInfoEx, 0,
                   sizeof(tcpRequestQueryInfoEx));
            tcpRequestQueryInfoEx.ID.toi_entity = entityIds[i];
            tcpRequestQueryInfoEx.ID.toi_class = INFO_CLASS_GENERIC;
            tcpRequestQueryInfoEx.ID.toi_type = INFO_TYPE_PROVIDER;
            tcpRequestQueryInfoEx.ID.toi_id = ENTITY_TYPE_ID;

            ULONG entityType;
            DWORD entityTypeSize = sizeof(entityType);

            result = WsControl(IPPROTO_TCP,
                               WSCTL_TCP_QUERY_INFORMATION,
                               &tcpRequestQueryInfoEx,
                               &tcpRequestBufSize,
                               &entityType, &entityTypeSize);

            if (result) {
                fprintf(stderr, "%s(%d) WsControl failed (%ld)\n",
                        __FILE__, __LINE__, WSAGetLastError1());
                WSACleanup1();
                FreeLibrary(hModule);
                return EXIT_FAILURE;
            }

            if (entityType == CL_NL_IP) {   // Entity implements IP.

                //get ip snmp info
                tcpRequestQueryInfoEx.ID.toi_class = INFO_CLASS_PROTOCOL;
                tcpRequestQueryInfoEx.ID.toi_id = IP_MIB_STATS_ID;

                IPSNMPInfo ipSnmpInfo;
                DWORD ipSnmpInfoSize = sizeof(ipSnmpInfo);

                result = WsControl(IPPROTO_TCP,
                                   WSCTL_TCP_QUERY_INFORMATION,
                                   &tcpRequestQueryInfoEx,
                                   &tcpRequestBufSize,
                                   &ipSnmpInfo, &ipSnmpInfoSize);

                if (result) {
                    fprintf(stderr, "%s(%d) WsControl failed (%ld)\n",
                            __FILE__, __LINE__, WSAGetLastError1());
                    WSACleanup1();
                    FreeLibrary(hModule);
                    return EXIT_FAILURE;
                }

                //print ip snmp info
                fprintf(stdout, "IP NumIfs: %lu\n", ipSnmpInfo.ipsi_numif);
                fprintf(stdout, "IP NumAddrs: %lu\n",
                        ipSnmpInfo.ipsi_numaddr);
                fprintf(stdout, "IP NumRoutes: %lu\n",
                        ipSnmpInfo.ipsi_numroutes);

                //get ip address list
                tcpRequestQueryInfoEx.ID.toi_id =
                    IP_MIB_ADDRTABLE_ENTRY_ID;

                DWORD ipAddrEntryBufSize = sizeof(IPAddrEntry) * ifCount;
                IPAddrEntry *ipAddrEntry = (IPAddrEntry
                                            *) calloc(ipAddrEntryBufSize,
                                                      1);

                result = WsControl(IPPROTO_TCP,
                                   WSCTL_TCP_QUERY_INFORMATION,
                                   &tcpRequestQueryInfoEx,
                                   &tcpRequestBufSize,
                                   ipAddrEntry, &ipAddrEntryBufSize);

                if (result) {
                    fprintf(stderr, "%s(%d) WsControl failed (%ld)\n",
                            __FILE__, __LINE__, WSAGetLastError1());
                    WSACleanup1();
                    FreeLibrary(hModule);
                    return EXIT_FAILURE;
                }

                //print ip address list
                DWORD j;
                for (j = 0; j < ifCount; j++) {

                    unsigned char *addr = (unsigned char
                                           *) &ipAddrEntry[j].iae_addr;
                    unsigned char *mask = (unsigned char
                                           *) &ipAddrEntry[j].iae_mask;

                    fprintf(stdout, "IF Index %ld  "
                            "Address %ld.%ld.%ld.%ld  "
                            "Mask %ld.%ld.%ld.%ld\n",
                            ipAddrEntry[j].iae_index,
                            addr[0], addr[1], addr[2], addr[3],
                            mask[0], mask[1], mask[2], mask[3]);
                }

                //get route table
                tcpRequestQueryInfoEx.ID.toi_id =
                    IP_MIB_ROUTETABLE_ENTRY_ID;

                DWORD ipRouteEntryBufSize = sizeof(IPRouteEntry) *
                    ipSnmpInfo.ipsi_numroutes;
                IPRouteEntry *ipRouteEntry = (IPRouteEntry
                                              *)
                    calloc(ipRouteEntryBufSize, 1);

                result = WsControl(IPPROTO_TCP,
                                   WSCTL_TCP_QUERY_INFORMATION,
                                   &tcpRequestQueryInfoEx,
                                   &tcpRequestBufSize,
                                   ipRouteEntry, &ipRouteEntryBufSize);

                if (result) {
                    fprintf(stderr, "%s(%d) WsControl failed (%ld)\n",
                            __FILE__, __LINE__, WSAGetLastError1());
                    WSACleanup1();
                    FreeLibrary(hModule);
                    return EXIT_FAILURE;
                }

                //print route table
                for (j = 0; j < ipSnmpInfo.ipsi_numroutes; j++) {

                    unsigned char *addr = (unsigned char
                                           *) &ipRouteEntry[j].ire_addr;
                    unsigned char *gw = (unsigned char
                                         *) &ipRouteEntry[j].ire_gw;
                    unsigned char *mask = (unsigned char
                                           *) &ipRouteEntry[j].ire_mask;

                    fprintf(stdout,
                            "Route %ld.%ld.%ld.%ld  "
                            "IF %ld  "
                            "GW %ld.%ld.%ld.%ld  "
                            "Mask %ld.%ld.%ld.%ld  "
                            "Metric %ld\n",
                            addr[0], addr[1], addr[2], addr[3],
                            ipRouteEntry[j].ire_index,
                            gw[0], gw[1], gw[2], gw[3],
                            mask[0], mask[1], mask[2], mask[3],
                            ipRouteEntry[j].ire_metric);
                }
            }
        }
    }

    WSACleanup1();
    FreeLibrary(hModule);
    return EXIT_SUCCESS;
}

//end of code

[Ed. Tom included a bunch of notes from tracing calls winipcfg.exe made. For various reasons, I've made them available in a separate file.]

Copyright © 1999 by Tom Sanfilippo. All rights reserved.


<< Winsock と BSD ソケットとの互換性 CSocket はなぜ有害か? >>
Last modified: $Id: wscontrol.html,v 1.6 2002/11/09 20:40:33 ksk Exp $ Go to the original FAQ page
< Go to the main FAQ page << Go to the Home Page