WsControl() Revealed

by Tom Sanfilippo (tsanfilippo at earthlink dot net)

Description

WsControl() is used to get interface info in the Windows 95 program winipcfg.exe. Apparently it can also be used to set IP config information as well. I have not tested setting, but with the info below, you should be able to do a lot of query-only calls. In theory you can also trace the "set" calls made by winipcfg.exe and deduce some of the input parameters for those calls. This is left as an exercise for the reader.

In the mode used in winipcfg.exe, WsControl() basically gives you access to SNMP query info, such as route table entries, interface list entries, adapter description entries, etc. (Pretty exciting!!) However, like others, I needed it for a Windows 95 application that cannot require Winsock 2. If you can require Winsock 2, you are probably better off using WSIoctl() or GetInterfaceInfo() to get the info provided by WsControl() (at least as much as is exposed by its use in winipcfg.exe).

How It Was Done

I used a hook DLL to hook and dump all the parameters, then I looked for patterns in the input and output. Then I wrote a test program to test out the theoretical signatures. I showed this to Thomas Divine (tdivine at pcausa dot com), and he recognized that the parameters I came up with were similar to parameters in a WSHelper sample in the NT4 DDK. I then took that sample and was able to deduce even more parameters. See the section entitled "Parameter Deduction Notes" for more info.

Why Was It Done

Well, the official line from Microsoft is that if you need this information programmatically from a Windows 95 application without Winsock 2, then you should have your application run winipcfg.exe in batch mode (yes there is a batch mode), and pipe the output to a file, then read the file into your application. Being that this would be almost as much fun as embedding a Tcl interpreter, I though it would be more interesting to see how they did it.

Required Headers

You need a few header files to make this code compile:

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

[Ed. The author referenced the Windows NT 4.0 DDK for these headers, but that is no longer available. You’ll have to look on MSDN to see if you can find a current DDK that still has these files.]

The WsControl() Call

The WsControl() function looks something like this:

                int WsControl(
                    DWORD protocol,
                    DWORD action,
                    LPVOID pRequestInfo,
                    LPDWORD pcbRequestInfoLen,
                    LPVOID pResponseInfo,
                    LPDWORD pcbResponseInfoLen
                );
Return Value

WsControl() returns 0 on success. Otherwise a non-zero value is returned. It might return STATUS_BUFFER_TOO_SMALL if the output buffer length (*pcbResponseInfoLen) is too small. It doesn’t seem to generate errors that can be retrieved by WSAGetLastError().

Paramters

pTcpRequestInfo is of type PTCP_REQUEST_QUERY_INFORMATION_EX, or of type PTCP_REQUEST_SET_INFORMATION_EX. For the calls I traced in winipcfg.exe, the inputs common to all query calls were:

                protocol = IPPROTO_TCP;
                action = WSCTL_TCP_QUERY_INFORMATION;

Not all output structures could be found for the queries made in winipcfg.exe. As a result, I had to make some up. The made up ones are defined in the sample code below. Note: you may be able to deduce more of the parameter definitions by looking at the SNMP, WSAIoctl(), or IP Helper data structures in the current Platform SDK.

Again, many thanks are due to Thomas Divine for pointing out the updated wshsmple in the NT4 DDK. The headers in that sample allowed the input parameters to finally be discovered.

Sample Code

The sample code below works on both Windows 95 and Windows 98. The sample emulates all the queries done by winipcfg.exe as it starts up.


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 at earthlink dot net)
// December 12, 1999

// Thanks are due to Thomas F. Divine (tdivine at pcausa dot 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.


<< BSD Sockets Compatibility
CSocket Considered Harmful >>
Updated Sun Jan 18 2015 04:24 MST   Go to my home page