< Chap 12: Index | Winsock 2 Main | Phonebook & User Credentials >
What do we have in this chapter 12 part 1?
|
|
So far, this book has described the Winsock API available in Microsoft Windows that allows you to develop applications capable of communicating over a local network. This chapter describes an important service named Remote Access Service (RAS), which allows users to connect their computer to a remote network such as an ISP or a corporate network. Once connected, you can use the network functions described throughout this book as though your computer were connected directly to a remote network.
RAS Client
All Microsoft Windows platforms feature a RAS client, which allows you to connect your computer from a remote location to another computer network featuring a remote access server component. Typically, a RAS client will do this by using a serial communication device such as a modem that connects to a telephone line and calls the remote network by dialing a telephone number. Because of this, the RAS client is sometimes referred to as a dial-up networking (DUN) client. RAS also supports connecting to a remote network by tunneling connections securely over an IP network such as the Internet, which is known as Virtual Private Networking (VPN). On the remote network, you must have a RAS server awaiting your DUN or VPN connections. A RAS client is capable of establishing a communication link with several types of remote access servers. RAS does this by using industry standard serial framing and IP tunneling protocols. The following protocols are serial framing, where data communication proceeds over a serial device such as a modem:
RAS uses the following IP tunneling protocols where data communication proceeds over an existing IP connection:
The framing and tunneling protocols describe how data is transmitted over a RAS communication link and dictate which network communication protocols (such as TCP/IP or IPX) can communicate over the link. If a RAS server supports one of the framing protocols defined in the previous list, a RAS client can establish a connection. All Windows 95, Windows 98, Windows Me, and Windows NT platforms feature a RAS server component capable of supporting the serial framing protocols listed. Windows Me and all Windows NT platforms also support the IP tunneling protocols. |
Once a connection between a RAS client and server is established, network protocol stacks (depending on the framing or tunneling protocol used) can communicate over the RAS connection to the remote network as if the computers were connected directly over a LAN. Of course, the data communication rate of most RAS connections is typically slower than for a direct LAN connection.
When a RAS server accepts a serial framing or an IP tunneling connection, it first establishes communication with your client by negotiating one of the framing or tunneling protocols in the previous list. Once the protocol connection is established, the RAS server attempts to authenticate the user making the connection. The RAS API functions this chapter describes allow a RAS client to specify a user name, a password, and domain logon credentials to the RAS server. When a Windows NT–based RAS server receives this information, it validates these logon credentials using Windows NT domain security access control. Note that the RAS server does not log your client on to a Windows NT domain; instead, it uses the client credentials to verify that a user is allowed to make a RAS connection. The RAS connection process is not the same as the Windows NT domain logon process. After a RAS connection is successfully established, your computer can log on to a Windows NT domain. On Windows 95, Windows 98, Windows Me, Windows XP, and Windows .NET Server, RAS can automatically log a machine on to a domain after a RAS connection is authenticated through options available in a phonebook entry, as we will discuss later in this chapter.
For serial communications, RAS relies on the Telephony Application Programming Interface (TAPI) to set up and control serial communication devices such as modems on your computer. TAPI controls the hardware settings of these dialing devices. When you set up a RAS connection using a modem, TAPI turns on the modem and sends dialing information from RAS to the modem. As a result, RAS views modems as simple TAPI modem ports that are capable of dialing and making a phone connection to a remote server. As you will see later in this chapter, some of the RAS API functions refer to TAPI modem ports when you set up RAS connection information.
This chapter will explain how you can use RAS programmatically to establish remote network communication. We will begin by describing the header and library files you need to build your application. Next, we will describe the basics of dialing, how you actually establish a remote connection over a serial device. Then we'll describe how you can set up RAS phonebook entries to define detailed communication properties of a RAS connection. Once we've explained the basics of setting up communication, we'll show you how to manage established connections. Finally, we'll describe how to set up a VPN connection.
When you develop a RAS application, you need to include the following header and library files to build your application:
RASERROR.H lists quite a few predefined error codes. In this file, you will notice an error description string associated with each error code used in RAS. RAS features a useful function named RasGetErrorString() that allows you to programmatically retrieve the error strings associated with specific RAS error codes. RasGetErrorString() is defined as:
DWORD RasGetErrorString(UINT uErrorValue, LPTSTR lpszErrorString, DWORD cBufSize);
The uErrorValue parameter receives a specific RAS error code returned from a RAS function. The lpszErrorString parameter is an application-supplied buffer that will receive the error string associated with the error code in uErrorValue. You should make your buffer large enough to hold an error string; otherwise, this function will fail with error ERROR_INSUFFICIENT_BUFFER. We recommend setting your buffer size to at least 256 characters, which should accommodate any RAS error string available today. The final parameter, cBufSize, is the size of the buffer you supplied as lpszErrorString.
When you compile and build your application, you will find that some of the data structures the RAS functions use have extra data fields included or excluded, based on the value of the define WINVER. However, the Windows CE SDK does not define WINVER, so this information does not apply to Windows CE. RAS data structures also have a dwSize field that you must set to the byte size of the RAS structure you are using. This affects the behavior of the RAS functions that use these structures because they are targeted for a specific platform. The following WINVER rules apply to the Windows 95, Windows 98, Windows Me, and Windows NT platforms:
RAS does not lend itself well to having a single executable that can run on all platforms because the RAS data structures will be sized differently during program compilation based on WINVER values. Through careful programming, it is possible to support all platforms (except Windows CE, of course) using a single executable. However, we highly recommend targeting a specific platform when you build your RAS applications.
When a RAS client application is ready to make a connection to a remote network, it must call the RasDial() function. RasDial() is quite complex, offering many call parameters that are used for dialing, authenticating, and establishing a remote connection to a RAS server. RasDial() is defined as:
DWORD RasDial(
LPRASDIALEXTENSIONS lpRasDialExtensions,
LPCTSTR lpszPhonebook,
LPRASDIALPARAMS lpRasDialParams,
DWORD dwNotifierType,
LPVOID lpvNotifier,
LPHRASCONN lphRasConn
);
Based on values of the lpvNotifier parameter, RasDial can execute in two operating modes: synchronous and asynchronous. In synchronous mode, RasDial() blocks until it either completes a connection or fails to do so. In asynchronous mode, RasDial() completes a connection immediately, allowing your application to perform other actions while connecting.
If the lpvNotifier parameter of RasDial() is set to NULL, RasDial() will operate synchronously. When the lpvNotifier parameter is NULL, the dwNotifierType parameter is ignored. Calling RasDial() synchronously is the easiest way to use this function; however, you won't be able to monitor the connection progress like you can in asynchronous mode, which we will describe in a moment. The following code sample demonstrates how to call RasDial() synchronously:
RASDIALPARAMS RasDialParams;
HRASCONN hRasConn;
DWORD Ret;
// Always set the size of the RASDIALPARAMS structure
RasDialParams.dwSize = sizeof(RASDIALPARAMS);
hRasConn = NULL;
// Setting this field to an empty string will allow
// RasDial to use default dialing properties
lstrcpy(RasDialParams.szEntryName, "");
lstrcpy(RasDialParams.szPhoneNumber, "867-5309");
lstrcpy(RasDialParams.szUserName, "jenny");
lstrcpy(RasDialParams.szPassword, "mypassword");
lstrcpy(RasDialParams.szDomain, "mydomain");
// Call RasDial synchronously (the fifth parameter is set to NULL)
Ret = RasDial(NULL, NULL, &RasDialParams, 0, NULL, &hRasConn);
if (Ret != 0)
{
printf("RasDial failed: Error = %d\n", Ret);
}
The sample calls RasDial() by filling fields of the lpRasDialParams parameter. The lpRasDialParams parameter is a RASDIALPARAMS structure pointer that defines dialing and user authentication parameters that the RasDial() function uses to establish a remote connection. It's defined as:
typedef struct _RASDIALPARAMS {
DWORD dwSize;
TCHAR szEntryName[RAS_MaxEntryName + 1];
TCHAR szPhoneNumber[RAS_MaxPhoneNumber + 1];
TCHAR szCallbackNumber[RAS_MaxCallbackNumber + 1];
TCHAR szUserName[UNLEN + 1];
TCHAR szPassword[PWLEN + 1];
TCHAR szDomain[DNLEN + 1] ;
#if (WINVER >= 0x401)
DWORD dwSubEntry;
DWORD dwCallbackId;
#endif
} RASDIALPARAMS;
The fields of RASDIALPARAMS provide the basics for setting up a RAS connection and are described as:
The previous sample fills in a RASDIALPARAMS with a number to dial, user name, password, and domain information for a RAS connection. The RASDIALPARAMS structure is then passed into the RasDial() function, which will synchronously make the remote connection.
Calling RasDial() asynchronously is a lot more complicated than calling this function in synchronous mode, but it offers greater flexibility when establishing a connection. If the lpvNotifier parameter of RasDial() is not set to NULL, RasDial() will operate asynchronously, the call returns immediately but the connection proceeds. Calling RasDial() asynchronously is the preferred method for making a RAS connection because you can monitor the connection's progress. The lpvNotifier parameter can be either a pointer to a function that is called from RasDial() when a connection activity occurs in RasDial() or a window handle that receives progress notification via Windows messages. The dwNotifierType parameter of RasDial determines the type of function or window handle that is passed into lpvNotifier. Table 15-1 describes the values that you can specify in dwNotifierType.
|
Table 15-1 RasDial Asynchronous Notification Methods
|
|
|
Notifier Type |
Meaning |
|
0 |
The lpvNotifier parameter causes RasDial to use the RasDialFunc function pointer to manage connection events. |
|
1 |
The lpvNotifier parameter causes RasDial to use the RasDialFunc1 function pointer to manage connection events. |
|
2 |
The lpvNotifier parameter causes RasDial to use the RasDialFunc2 function pointer to manage connection events. |
|
0xFFFFFFFF |
The lpvNotifier parameter makes RasDial send a window message during connection events. |
Table 15-1 also describes the three callback function prototypes you supply to RasDial() in the lpvNotifier parameter that is called for receiving notification of connection events: RasDialFunc(), RasDialFunc1(), and RasDialFunc2(). The first one, RasDialFunc(), is prototyped as:
VOID WINAPI RasDialFunc(
UINT unMsg,
RASCONNSTATE rasconnstate,
DWORD dwError
);
The unMsg parameter receives the type of event that has occurred. Currently, this event can be only WM_RASDIALEVENT, which means that this parameter is not useful. The rasconnstate parameter receives the connection activity that the RasDial() function is about to start. Table 15-2 defines the possible connection activities. The dwError parameter receives a RAS error code if one of the connection activities experiences failure.
|
Table 15-2 Asynchronous RasDial() Operating States
|
||
|
Activity |
State |
Description |
|
RASCS_OpenPort |
Running |
A communication port is about to be opened. |
|
RASCS_PortOpened |
Running |
The communication port is open. |
|
RASCS_ConnectDevice |
Running |
A device is about to be connected. |
|
RASCS_DeviceConnected |
Running |
The device has connected successfully. |
|
RASCS_AllDevicesConnected |
Running |
A physical link has been established. |
|
RASCS_Authenticate |
Running |
The RAS authentication process has started. |
|
RASCS_AuthNotify |
Running |
An authentication event has occurred. |
|
RASCS_AuthRetry |
Running |
The client has requested another authentication attempt. |
|
RASCS_AuthCallback |
Running |
The server has requested a callback number. |
|
RASCS_AuthChangePassword |
Running |
The client has requested to change the password on the RAS account. |
|
RASCS_AuthProject |
Running |
The protocol projection is starting. (We'll describe RAS protocol projections later.) |
|
RASCS_AuthLinkSpeed |
Running |
The link speed is being calculated. |
|
RASCS_AuthAck |
Running |
An authentication request is being acknowledged. |
|
RASCS_ReAuthenticate |
Running |
The authentication process after a callback is starting. |
|
RASCS_Authenticated |
Running |
The client has completed the authentication successfully. |
|
RASCS_PrepareForCallback |
Running |
The line is about to disconnect to prepare for a callback. |
|
RASCS_WaitForModemReset |
Running |
The client is waiting for the modem to reset before preparing for a callback. |
|
RASCS_WaitForCallback |
Running |
The client is waiting for an incoming call from the server. |
|
RASCS_Projected |
Running |
The protocol projection is complete. |
|
RASCS_StartAuthentication |
Running |
User authentication is being started or retried. (This applies to Windows 95, Windows 98, and Windows Me only.) |
|
RASCS_CallbackComplete |
Running |
The client has been called back. (This applies to Windows 95, Windows 98, and Windows Me only.) |
|
RASCS_LogonNetwork |
Running |
The client is logging on to a remote network. (This applies to Windows 95, Windows 98, and Windows Me only.) |
|
RASCS_SubEntryConnected |
Running |
A subentry of a multilink phonebook entry has connected. The dwSubEntry parameter of RasDialFunc2() will contain an index of the subentry connected. |
|
RASCS_SubEntryDisconnected |
Running |
A subentry of a multilink phone- book entry has disconnected. The dwSubEntry parameter of RasDialFunc2() will contain an index of the subentry disconnected. |
|
RASCS_RetryAuthentication |
Paused |
RasDial() is awaiting new user credentials. |
|
RASCS_CallbackSetByCaller |
Paused |
RasDial() is awaiting a callback number from the client. |
|
RASCS_PasswordExpired |
Paused |
RasDial() expects the user to supply a new password. |
|
RASCS_InvokeEapUI |
Paused |
On Windows NT platforms, RasDial() is awaiting a custom user interface to obtain user authentication information. |
|
RASCS_Connected |
Terminal |
The RAS connection succeeded and is active. |
|
RASCS_Disconnected |
Terminal |
The RAS connection failed or is inactive. |
Table 15-2 shows the three operating states associated with connection activities in an asynchronous RasDial() call: running, paused, and terminal. The running state indicates that the RasDial() call is still in progress, and each running-state activity offers progress status information.
The paused state indicates that RasDial() needs more information to establish the connection. By default, the paused state is disabled. On Windows NT–based operating systems, you can enable paused state notification by setting the RDEOPT_PausedStates option flag in the lpRasDialExtensions structure parameter of RasDial. When a paused state activity occurs, it indicates one of the following conditions:
These activities pertain to information supplied in the RASDIALPARAMS structure described earlier in this chapter. When a paused state activity occurs, RasDial() will notify your callback function (or window procedure). If the paused state is disabled, RAS will send an error to your notification function and RasDial() will fail. If enabled, the RasDial() function will be in a paused state that allows your application to supply the necessary information through a RASDIALPARAMS structure. When RasDial() is paused, you can resume by calling it again with the original call's connection handle (lphRasConn) and notification function (lpvNotifier), or you can simply end the paused operation by calling RasHangUp() (described later in this chapter). If you resume the paused connection, you will have to supply the necessary user input via the RASDIALPARAMS structure passed to the resumed RasDial() call.
Do not resume the paused state by calling RasDial() directly from a notification handler function such as RasDialFunc(). RasDial is not designed to handle this situation, so you should resume RasDial directly from your application thread.
The final state, terminal, indicates that the RasDial() connection has either succeeded or failed. It can also indicate that the RasHangUp() function closed the connection.
Now that you have a basic understanding of how you can monitor the connection of an asynchronous RasDial() call, we'll demonstrate how to set up a simple program that calls RasDial() asynchronously. The following code shows this procedure.
void main(void)
{
DWORD Ret;
RASDIALPARAMS RasDialParams;
HRASCONN hRasConn;
// Fill in the RASDIALPARAMS structure with call parameters
// as was done in the synchronous example
...
if ((Ret = RasDial(NULL, NULL, &RasDialParams, 0, &RasDialFunc, &hRasConn)) != 0)
{
printf("RasDial failed with error %d\n", Ret);
return;
}
// If RasDial succeeds, it will complete immediately,
// leaving you the chance to perform other tasks while RasDial is processing
...
}
// Callback function RasDialFunc()
void WINAPI RasDialFunc(UINT unMsg, RASCONNSTATE rasconnstate, DWORD dwError)
{
char szRasString[256]; // Buffer for error string
if (dwError)
{
RasGetErrorString((UINT)dwError, szRasString, 256);
printf("Error: %d - %s\n",dwError, szRasString);
return;
}
// Map each of the RasDial states and display on the
// screen the next state that RasDial is entering
switch (rasconnstate)
{
case RASCS_ConnectDevice:
printf ("Connecting device...\n");
break;
case RASCS_DeviceConnected:
printf ("Device connected.\n");
break;
// Add other connection activities here
...
default:
printf ("Unmonitored RAS activity.\n");
break;
}
}
RAS also features a stand-alone function named RasConnectionNotification() that allows your application to determine when an asynchronous RAS connection has been created or terminated. RasConnectionNotification() is defined as:
DWORD RasConnectionNotification(
HRASCONN hrasconn,
HANDLE hEvent,
DWORD dwFlags
);
The hrasconn parameter is the connection handle returned from RasDial(). The hEvent parameter is an event handle that your application creates using the CreateEvent() function. The dwFlags parameter can be set to a combination of several activity flags. The most useful ones are:
Note that these flags function the same way as the connection activity flags described in Table 15-2. If any of these activities occur during your connection, your event will become signaled. Your application should use operating system wait functions, such as WaitForSingleObject(), to determine when the object becomes signaled.
Closing a connection established by RasDial() is simple. All you have to do is call RasHangUp(), which is defined as:
DWORD RasHangUp(HRASCONN hrasconn);
The hrasconn parameter is a handle that is returned from RasDial(). Although this function is easy to use, you have to consider how connections are managed internally in RAS. A serial connection uses a modem port, and it takes time for the port to reset internally when a connection shuts down. Therefore, you should wait until the port connection closes completely. To do this, you can call RasGetConnectStatus() to determine when your connection is reset. RasGetConnectStatus() is defined as:
DWORD RasGetConnectStatus(HRASCONN hrasconn, LPRASCONNSTATUS lprasconnstatus);
The hrasconn parameter is a handle that is returned from RasDial(). The lprasconnstatus parameter is a RASCONNSTATUS structure that receives the current connection status. A RASCONNSTATUS structure is defined as:
typedef struct _RASCONNSTATUS
{
DWORD dwSize;
RASCONNSTATE rasconnstate;
DWORD dwError;
TCHAR szDeviceType[ RAS_MaxDeviceType + 1 ];
TCHAR szDeviceName[ RAS_MaxDeviceName + 1 ];
} RASCONNSTATUS;
These fields are defined as:
We recommend that you check the state of your connection until you receive the RASCS_Disconnected activity status. Obviously, you might have to call RasGetConnectStatus() several times until the connection is reset. Once the connection is reset, you can exit your application or make another connection.
Now that we have described the basics of setting up a RAS connection, we will describe how to set up phonebook entries that allow you to set up advanced communication properties for establishing a RAS connection.
Useful References
< Chap 12: Index | Winsock 2 Main | Phonebook & User Credentials >