EternalWindows
LSP / エントリの列挙

LSPを特定のプロトコル上にインストールするためには、 まずはそのプロトコルについての情報を取得しなければなりません。 WSCEnumProtocolsを呼び出せば、システムに存在する全てのプロトコルを列挙できるため、 その中から適切なものを選択することになります。

int WSPAPI WSCEnumProtocols(
  LPINT lpiProtocols,
  LPWSAPROTOCOL_INFOW lpProtocolBuffer,
  LPDWORD lpdwBufferLength,
  LPINT lpErrno
);

lpiProtocolsは、列挙したいプロトコルの値を格納したNULLで終了する配列を指定します。 全てのプロトコルを列挙したい場合は、NULLを指定します。 lpProtocolBufferは、WSAPROTOCOL_INFOW構造体の配列を受け取るバッファを指定します。 lpdwBufferLengthは、lpProtocolBufferのサイズを格納した変数のアドレスを指定します。 lpProtocolBufferがNULLの場合は、必要なサイズが返ります。 lpErrnoは、エラーコードを受け取る変数のアドレスを指定します。 エラーが発生していない場合は、NO_ERRORが格納されます。 戻り値は、バッファに格納されたプロトコル数です。

WSCEnumProtocolsで列挙できる情報は、WSAEnumProtocolsという関数でも列挙することができます。 ただし、LSPのインストールする場合はできるだけWSCEnumProtocolsを呼び出した方がよいでしょう。 WSAEnumProtocolsの場合であれば、WSAStartupの呼び出しが事前に必要になりますし、 後述するダミーエントリも列挙されないからです。 WSAPROTOCOL_INFOW構造体は、次のように定義されています。

typedef struct _WSAPROTOCOL_INFO {
  DWORD            dwServiceFlags1;
  DWORD            dwServiceFlags2;
  DWORD            dwServiceFlags3;
  DWORD            dwServiceFlags4;
  DWORD            dwProviderFlags;
  GUID             ProviderId;
  DWORD            dwCatalogEntryId;
  WSAPROTOCOLCHAIN ProtocolChain;
  int              iVersion;
  int              iAddressFamily;
  int              iMaxSockAddr;
  int              iMinSockAddr;
  int              iSocketType;
  int              iProtocol;
  int              iProtocolMaxOffset;
  int              iNetworkByteOrder;
  int              iSecurityScheme;
  DWORD            dwMessageSize;
  DWORD            dwProviderReserved;
  WCHAR            szProtocol[WSAPROTOCOL_LEN+1];
} WSAPROTOCOL_INFO, *LPWSAPROTOCOL_INFO;

dwServiceFlags1は、プロトコルが提供しているサービスを表す定数が格納されます。 dwServiceFlags2からdwServiceFlags4は、予約されているため0が格納されます。 dwProviderFlagsは、プロトコルカタログについて定義する定数が格納されます。 ProviderIdは、このエントリのGUIDが格納されます。 dwCatalogEntryIdは、プロトコルのエントリIDが格納されます。 ProtocolChainは、WSAPROTOCOLCHAIN構造体へのアドレスが格納されます。 iVersionは、プロトコルのバージョンを表す識別子が格納されます。 iAddressFamilyは、アドレスファミリが格納されます。 iMaxSockAddrは、アドレス情報の最大値が格納されます。 iMinSockAddrは、アドレス情報の最小値が格納されます。 iSocketTypeは、ソケットタイプが格納されます。 iProtocolは、実装するプロトコルの値が格納されます。 iProtocolMaxOffsetは、iProtocolに指定されるかもしれない最大値が格納されます。 iNetworkByteOrderは、バイトオーダーを表す定数が格納されます。 iSecuritySchemeは、セキュリティスキームのタイプが格納されます。 dwMessageSizeは、データ送信における最大サイズが格納されます。 dwProviderReservedは、予約されているため0が格納されます。 szProtocolは、プロトコルを表す文字列が格納されます。

WSCEnumProtocolsで返される情報には、特定のプロトコルだけでなく、LSPの情報も含まれることになっています。 つまり、LSPのインストールを実行した後に、WSCEnumProtocolsを呼び出した場合、 インストールしたLSPの情報も返されることになります。 こうした事から、WSCEnumProtocolsで列挙できる情報は、プロトコルと呼ばれずにエントリ(またはカタログエントリ)と呼ばれることがあります。 取得したエントリが、LSPに関するものか特定のプロトコルに関するものかを判断するには、 WSAPROTOCOLCHAIN構造体のメンバを確認します。

typedef struct _WSAPROTOCOLCHAIN {
  int   ChainLen;
  DWORD ChainEntries[MAX_PROTOCOL_CHAIN];
} WSAPROTOCOLCHAIN, *LPWSAPROTOCOLCHAIN;

ChainLenは、プロトコルチェーンの長さを指定します。 ChainEntriesは、チェーンのエントリのIDを指定します。

ChainLenの値 説明
0 (LAYERED_PROTOCOL) ChainLenが0である場合、そのプロトコルはレイヤードプロトコルである。 ChainLenが0であるため、ChainEntriesは初期化されない。
1 (BASE_PROTOCOL) ChainLenが1である場合、そのプロトコルはベースプロトコルである。 ベースプロトコルは、TCPやUDPなど特定のプロトコルを実装しており、 通常LSPはこのプロトコルの上にインストールすることになる。
1より大きい ChainLenが1より大きい場合、そのプロトコルはLSPのエントリである。 LSPはプロトコルチェーンを形成しており、ChainEntries[0]にレイヤードプロトコルのエントリIDを格納し、 ChainEntries[1]以降に、下位に存在するプロトコルのエントリIDを格納する。

レイヤードプロトコルについて理解するために、1つの例を挙げて考えてみましょう。 TCPとUDPの通信を検出するLSPを開発するとします。 この場合、実装したLSPを次のように分けてインストールすることになります。

この図で少し分かりにくいのは、LSPという1つのDLLをインストールしてその中でTCPとUDPの処理を行っているのにも関わらず、 エントリ上では分割して存在しているという点です。 そこで、これらのエントリを1つのエントリとして考えるために存在するのが、レイヤードプロトコルです。 レイヤードプロトコルが存在することで、次のように分かりやすい図が完成します。

この図はあくまでイメージであり、実際に存在するエントリを抽象化しているだけですから、 本当に正しいのは最初に示した図です。 レイヤードプロトコルは便宜的な存在であり、ダミーエントリと呼ばれることもありますが、 いくつかの場面では役に立つことがあります。 たとえば、実際のエントリのChainEntries[0]にはレイヤードプロトコルのエントリIDが格納されているため、 LSP内部でレイヤードプロトコルのエントリIDを取得できれば、 関連するエントリを列挙することができるようになります。

上記の話から分かるように、LSPのインストールというのは、少なくとも2回実行する必要があります。 1回目はレイヤードプロトコルとしてのインストールであり、 2回目は特定のプロトコル上へのインストールです。 ただし、この上というのは、正確には前方です。 これは、各種エントリがオーダーと呼ばれるものによって順番が決められており、 このオーダーの先頭に存在するエントリから、考慮されることになっているからです。 たとえば、Winsockのsocket関数が呼ばれた場合、 ws2_32.dllは指定された情報をサポートするエントリを選択しなければなりませんが、 この確認対象となる順番はオーダーを基にしています。 適切なエントリが発見された場合は、以降のエントリが考慮されないため、 自作のエントリをオーダーの先頭に移動させることで、 ws2_32.dllからの呼び出しを最初に検出するようにします。 そして、DLL内部で必要な処理を行った後、下位(後方)に存在するエントリの関数を呼び出すことになります。 オーダーに関する問題を考慮した場合、インストールに必要な作業は次の3つとなります。

1, ダミーエントリ(レイヤードプロトコル)をインストールする。
2, プロトコルチェーンを形成した実際のエントリをインストールする。
3, インストールによって作成された実際のエントリをオーダーの先頭に移動させる。

実際のエントリをインストールする場合は、ChainEntries[0]にダミーエントリのエントリIDが必要になるため、 最初にダミーエントリのインストールを行っておく必要があります。 また、オーダーの位置を指定してインストールを実行することはできないため、 インストールが終了してから、明示的にオーダーを変更することになります。 Windows Vistaから登場したWSCInstallProviderAndChainsという関数を呼び出す場合は、 上記3つの作業が全て関数内部で行われます。

今回のプログラムは、WSCEnumProtocolsを呼び出して、システムに存在するエントリを列挙します。 左のリストボックスに列挙されたエントリ名を選択すると、右のリストボックスに詳細が表示されます。

#include <winsock2.h>
#include <ws2spi.h>

#pragma comment(lib, "ws2_32.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND               hwndListBoxLeft = NULL;
	static HWND               hwndListBoxRight = NULL;
	static LPWSAPROTOCOL_INFO lpEntryList = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		int   i;
		int   nError;
		int   nEntryCount;
		DWORD dwSize;

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY|WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		WSCEnumProtocols(NULL, NULL, &dwSize, &nError);
		lpEntryList = (LPWSAPROTOCOL_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
		nEntryCount = WSCEnumProtocols(NULL, lpEntryList, &dwSize, &nError);
	
		for (i = 0; i < nEntryCount; i++)
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)lpEntryList[i].szProtocol);

		return 0;
	}
	
	case WM_COMMAND: {
		int   i;
		int   nIndex;
		TCHAR szBuf[256];
		WCHAR szBufW[256];
		WCHAR szGuid[256];

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;
		
		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);
		
		nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);
		
		StringFromGUID2(lpEntryList[nIndex].ProviderId, szGuid, sizeof(szGuid));
		wsprintf(szBufW, L"GUID : %s", szGuid);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBufW);
		
		wsprintf(szBuf, TEXT("エントリID : %d"), lpEntryList[nIndex].dwCatalogEntryId);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, TEXT("プロトコルチェーン : %d"), lpEntryList[nIndex].ProtocolChain.ChainLen);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		for (i = 0; i < lpEntryList[nIndex].ProtocolChain.ChainLen; i++) {
			wsprintf(szBuf, TEXT("ChainEntries[%d] : %d"), i, lpEntryList[nIndex].ProtocolChain.ChainEntries[i]);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}

		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 2, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (lpEntryList != NULL)
			HeapFree(GetProcessHeap(), 0, lpEntryList);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WSCEnumProtocolsはオーダー上先頭に存在するエントリから返すことになっているため、 このプログラムはオーダーを確認するという目的でも利用することができます。 WSCEnumProtocolsの呼び出しは、WM_CREATEで行われています。

WSCEnumProtocols(NULL, NULL, &dwSize, &nError);
lpEntryList = (LPWSAPROTOCOL_INFO)HeapAlloc(GetProcessHeap(), 0, dwSize);
nEntryCount = WSCEnumProtocols(NULL, lpEntryList, &dwSize, &nError);

WSCEnumProtocolsの第1引数にNULLを指定した場合、全てのエントリが列挙されるようになります。 第2引数は、WSAPROTOCOL_INFO構造体を受け取るバッファを指定しますが、 1回目の呼び出しではバッファを用意できないため、 第2引数にNULLを指定することで必要なサイズを取得します。 そして、取得したサイズ分だけバッファを確保し、 2回目の呼び出しでバッファを初期化することになります。 WSCEnumProtocolsの戻り値は、バッファに格納されたエントリの数であるため、 忘れず取得しておきます。


戻る