EternalWindows
RAS / PPPとIPアドレス

ダイヤルアップ接続と常時接続を比較した場合、プロバイダより割り当てられるIPアドレスが 接続の度に異なるという点は1つの大きな相違といえるところです。 プロバイダは、クライアントからのダイヤルアップ接続を受け付けるために、 IPアドレスを割り当てた機器をいくつか用意していますが、 実際に接続されることになる機器は現在、他のクライアントに利用されていないものが 選択されるため、基本的にIPアドレスは接続する度に変わることになります。 これは、永続的に存在することが求められるWebサーバーなどでは不利な点ですが、 IPアドレスで一定でないことから、不正アクセスの格好になる恐れが少ないという利点もあります。

ダイヤルアップでプロバイダと接続するときに用いられるプロトコルは、 主としてPPP(Point to Point Protocol)です。 この過程でプロバイダはクライアントを認証し、IPアドレスを割り当てることになります。 より厳密には、ユーザーの認証にはRADIUS(Remote Authentication Dial-In User Service)という 別のプロトコルが使われ、これでプロバイダとIAS(Internet Authentication Service)が 通信し合うことでユーザーが認証されることになっています。 Radiusというプレフィックスを持つIASE(Internet Authentication Service Extensions)関数を DLLに実装すれば、IAS(RADIUSサーバー)の動作を追跡することができます。

多くのアプリケーションは、PPPやRADIUSなどの詳細はそれほど必要としませんが、 割り当てられたIPアドレスに関しては、取得できた方が応用が広がるものと思われます。 システムは、クライアントとサーバー間で行われた情報のネゴシエーション(交渉)を リモートアクセス投影という情報としてRASハンドルに関連付けているため、 これを参照することでIPアドレスを取得することができます。 次に示すRasGetProjectionInfoは、リモートアクセス投影を取得します。

DWORD RasGetProjectionInfo(
  HRASCONN hrasconn,
  RASPROJECTION rasprojection,
  LPVOID lpprojection,
  LPDWORD lpcb
);

hrasconnは、RAS接続のハンドルを指定します。 rasprojectionは、利用したプロトコルを示すRASPROJECTION列挙型の値を指定しますが、 主としてここにはRASP_PppIpを指定することになるでしょう。 PPPはデータリンク層のプロトコルであるため、 TCP/IP以外のネットワークアーキテクチャ(たとえば、IPX/SPX)からでも利用できるのですが、 現在のインターネットの標準はTCP/IPとなっています。 lpprojectionは、投影情報を受け取るバッファのアドレスを指定します。 rasprojectionにRASP_PppIpを指定したときは、RASPPPIP構造体のアドレスを指定します。 lpcbは、バッファのサイズを格納した変数のアドレスを指定します。

RAS接続のハンドルは、RasDialを呼び出したアプリケーションのみが得られるものではありません。 RasEnumConnectionsのようなRAS接続を列挙する関数を呼び出せば、 ダイヤルに詳しくないアプリケーションからでも情報を取得することができます。

DWORD RasEnumConnections(
  LPRASCONN lprasconn,
  LPDWORD lpcb,
  LPDWORD lpcConnections
);

lprasconnは、接続データを表すRASCONN構造体の配列を指定します。 配列の最初の要素は、dwSizeメンバに構造体のサイズを代入しておくことになります。 lpcbは、lprasconnが指すバッファのサイズを指定します。 関数から制御が返ると、必要なサイズが格納されます。 lpcConnectionsは、RAS接続の数が返ります。

今回のプログラムは、RasEnumConnectionsで取得したRAS接続を RasGetProjectionInfoに指定し、割り当てられたIPアドレスを取得します。

#include <windows.h>
#include <ras.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	DWORD     i;
	DWORD     dwBufferSize;
	DWORD     dwConnections;
	RASCONN   tmp;
	RASPPPIP  pppip;
	LPRASCONN lpConnections;

	tmp.dwSize   = sizeof(RASCONN);
	dwBufferSize = sizeof(RASCONN);
	RasEnumConnections(&tmp, &dwBufferSize, &dwConnections);

	lpConnections = (LPRASCONN)HeapAlloc(GetProcessHeap(), 0, dwBufferSize);
	lpConnections->dwSize = sizeof(RASCONN);
	RasEnumConnections(lpConnections, &dwBufferSize, &dwConnections);

	for (i = 0; i < dwConnections; i++) {
		pppip.dwSize = sizeof(RASPPPIP);
		dwBufferSize = sizeof(RASPPPIP);
		RasGetProjectionInfo(lpConnections->hrasconn, RASP_PppIp, &pppip, &dwBufferSize);
		
		MessageBox(NULL, pppip.szIpAddress, TEXT("割り当てられたIPアドレス"), MB_OK);
		lpConnections++;
	}
	
	HeapFree(GetProcessHeap(), 0, lpConnections);

	return 0;
}

複数のRAS接続があることを考えた場合、 まずは必要な接続を受け取れるだけのバッファを確保しなければなりません。 1回目のRasEnumConnectionsの呼び出しではdwBufferSizeの初期化に専念しますが、 第1引数にはサイズを初期化したRASCONN構造体のアドレスを指定しなければなりません。 これは、以前に説明したRasEnumEntriesと同じ要領です。 2回目の呼び出しでは取得したサイズを基に、 確保したバッファを第1引数に指定することになります。 次に、RasGetProjectionInfoの呼び出し部分を見てみます。

pppip.dwSize = sizeof(RASPPPIP);
dwBufferSize = sizeof(RASPPPIP);
RasGetProjectionInfo(lpConnections->hrasconn, RASP_PppIp, &pppip, &dwBufferSize);		

RasGetProjectionInfoも他のRAS関数と同じように、 事前に構造体のdwSizeメンバを初期化し、それをバッファのサイズにしておきます。 利用する構造体は、第2引数にRASP_PppIpを指定していることからRASPPPIP構造体となり、 szIpAddressメンバを参照することにより、割り当てられたIPアドレスを確認できます。 また、szServerIpAddressメンバからサーバーのIPアドレスが確認できます。 第1引数に指定するRAS接続のハンドルは、RASCONN構造体のhrasconnメンバから参照可能で、 他にエントリ名やデバイス名、ログオンセッションのLUIDなども参照することができます。 このLUIDを基にLsaGetLogonSessionDataでログオンセッションの情報すれば、 RAS接続を行ったユーザー名などを特定することができます。

RAS接続の検出

ネットワークの接続や切断を検出したいアプリケーションは、 ユーザーのダイヤルアップアクセスを考慮してRasConnectionNotificationの 使い方を知っておくと便利です。 この関数は、第3引数に指定したRASイベント(検出条件)を満たしたとき、 第2引数に指定したイベントオブジェクトをシグナル状態にするという特徴があるため、 WaitForSingleObjectのような待機関数から制御が返ったときには、 RAS接続の接続又は切断が行われたと解釈することができます。 次に、RasConnectionNotificationを利用した簡単なサンプルを示します。

#include <windows.h>
#include <ras.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	DWORD  dwResult;
	HANDLE hEvent;

	MessageBox(NULL, TEXT("RAS接続の切断を検出します。"), TEXT("OK"), MB_OK);

	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	RasConnectionNotification((HRASCONN)INVALID_HANDLE_VALUE, hEvent, RASCN_Disconnection);

	dwResult = WaitForSingleObject(hEvent, 5000);
	if (dwResult == WAIT_OBJECT_0)
		MessageBox(NULL, TEXT("RAS接続が切断されました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("RAS接続の切断を検出できませんでした。"), NULL, MB_ICONWARNING);

	CloseHandle(hEvent);

	return 0;
}

イベントオブジェクトは、RasConnectionNotificationで自動でシグナル状態になるため、 CreateEventの第2引数と第3引数は共にFALSEとなります。 RasConnectionNotificationの呼び出しによってイベントオブジェクトと 第3引数のRASイベントは関連付けられ、上記ではRASCN_Disconnectionとしていることから、 RAS接続が切断されたときにイベントオブジェクトはシグナル状態になります。 第1引数に既存のRAS接続を指定した場合は、その接続が切断されたときのみに イベントオブジェクトがシグナル状態になります。 第3引数にRASCN_Connectionを指定した場合は常にINVALID_HANDLE_VALUEを指定し、 待機関数から制御が返ったときにRasEnumConnectionsを呼び出すことで、 発生したRAS接続のハンドルを取得することができます。 このサンプルでは、アプリケーションが半永久的に待機するようなことを防ぐために、 待機関数では5秒というタイムアウト値を指定していますが、 実際の開発ではワーカースレッドなどを作成し、 そこでタイムアウト値INFINITEを指定した待機関数を呼び出すことになるでしょう。



戻る