Entête Rarp
par _SebF

1 - Définition du protocole
2 - Structure de l'entête
3 - Définition des différents champs
        3.1 - Hardware type
        3.2 - Protocol type
        3.3 - Hardware Address Length
        3.4 - Protocol Address Length
        3.5 - Operation
        3.6 - Sender Hardware Address
        3.7 - Sender Internet Address
        3.8 - Target Hardware Address
        3.9 - Target Internet Address
4 - Fonctionnement
5 - Serveur Rarp
6 - Discussion autour de la documentation
7 - Suivi du document

1 - Définition du protocole

Le protocole Rarp, signifiant Reverse Address Resolution Protocol, fonctionne en couche Internet du modèle TCP/IP correspondant à la couche 3 du modèle Osi. L'objectif de Rarp est de permettre de résoudre une adresse IP par l'intermédiaire de l'adresse physique correspondante d'un host distant. Le protocole Rarp apporte un mécanisme de « translation » pour résoudre ce besoin.

Vous trouverez tous les détails du protocole Rarp dans la RFC 903 "A Reverse Address Resolution Protocol".

2 - Structure de l'entête

Voici l'entête du protocole ARP dans le cadre spécifique d'Ip sur Ethernet.

TCPIP IPV6 VOIP VPN IP IPV4

TCPIP IPV6 VOIP VPN IP IPV4

3 - Définition des différents champs

3.1 - Hardware type

Ce champs est placé en premier afin d'indiquer quel est le format de l'entête Arp. Voici les différentes valeurs possibles.

- 01 - Ethernet (10Mb) [JBP]
- 02 - Experimental Ethernet (3Mb) [JBP]
- 03 - Amateur Radio AX.25 [PXK]
- 04 - Proteon ProNET Token Ring [Doria]
- 05 - Chaos [GXP]
- 06 - IEEE 802 Networks [JBP]
- 07 - ARCNET [JBP]
- 08 - Hyperchannel [JBP]
- 09 - Lanstar [TU]
- 10 - Autonet Short Address [MXB1]
- 11 - LocalTalk [JKR1]
- 12 - LocalNet (IBM PCNet or SYTEK LocalNET) [JXM]
- 13 - Ultra link [RXD2]
- 14 - SMDS [GXC1]
- 15 - Frame Relay [AGM]
- 16 - Asynchronous Transmission Mode (ATM) [JXB2]
- 17 - HDLC [JBP]
- 18 - Fibre Channel [Yakov Rekhter]
- 19 - Asynchronous Transmission Mode (ATM) [RFC2225]
- 20 - Serial Line [JBP]
- 21 - Asynchronous Transmission Mode (ATM) [MXB1]
- 22 - MIL-STD-188-220 [Jensen]
- 23 - Metricom [Stone]
- 24 - IEEE 1394.1995 [Hattig]
- 25 - MAPOS [Maruyama]
- 26 - Twinaxial [Pitts]
- 27 - EUI-64 [Fujisawa]
- 28 - HIPARP [JMP]

3.2 - Protocol type

Ce champs indique quel est le type de protocole couche 3 qui utilise Rarp. Voici la valeur propre à Ip.

- 0x0800 - IP

3.3 - Hardware Address Length

Ce champ correspond à la longueur de l'adresse physique. La longueur doit être prise en octets. Voici des exemples de valeurs courantes.

- 01 - Token Ring
- 06 - Ethernet

3.4 - Protocol Address Length

Ce champ correspond à la longueur de l'adresse réseau. La longueur doit être prise en octets. Voici des exemples de valeurs courantes.

- 04 - IP v4
- 16 - IP v6

3.5 - Operation

Ce champ permet de connaître la fonction du message et donc son objectif. Voici les différentes valeurs possibles. "There are two opcodes: 3 ('request reverse') and 4 ('reply reverse')."

- 03 - Request [RFC 903]
- 04 - Reply [RFC 903]

3.6 - Sender Hardware Address

Ce champ indique l'adresse physique de l'émetteur. Dans le cadre spécifique d'Ethernet, cela représente l'adresse Mac source.

3.7 - Sender Internet Address

Ce champ indique l'adresse réseau de l'émetteur. Dans le cadre spécifique de TCP/IP, cela représente l'adresse Ip de source.

 3.8 - Target Hardware Address

Ce champ indique l'adresse physique du destinataire. Dans le cadre spécifique d'Ethernet, cela représente l'adresse Mac destination. Si c'est une demande Arp, alors, ne connaissant justement pas cette adresse, le champs sera mis à 0.

3.9 - Target Internet Address

Ce champ indique l'adresse réseau du destinataire. Dans le cadre spécifique de TCP/IP, cela représente l'adresse Ip de destination.

4 - Fonctionnement

Rarp étant un protocole de niveau 3, il s'appui sur une entête Ethernet à 14 octets. On y retrouvera spécifiquement le flag "type de protocole" égale 0x8035. (0x0806 pour ARP)
 

5 - Serveur Rarp

Voici RARPD 1.15 qui est un serveur RARP. Fonctionnant sous NT/Win2K. En plus de l'exe, vous y trouverez la source en C.

/*
   RARPD.CPP:

   Free software Copyright (c) 1999-2003 Lew Perin
*/

/*
   Revision History:

   Version  Date      Reason
   -------  --------
   1.00     10/31/99  Forked from billgpc.cpp.
   1.01     11/27/99  Lazy automatic reinitialization of the RARP table when
                      it has been changed on disk, removal of lots of chatter
                      from the log and main window.
   1.02      1/17/00  Faster retrieval of IP address via qsort/bsearch.
   1.03       6/3/00  Bugs in automatic reinitialization, handle usage,
                      error messages fixed; thanks, Ury Jamshy!
   1.04      8/12/00  Fixed recognition of directory with embedded spaces
                      from command line; thanks, Jiri Medlen!
   1.05      9/17/00  Fixed TCP Registry navigation for Windows 2000.
   1.06      1/15/01  Made some minor type/constness changes to satisfy
                      modern C++ compilers.  Fixed bug that could
                      destroy adapterName when excluding subnets.
   1.07      5/17/01  In Win2K we now probe devices to see if they're
                      really there.
   1.08      6/18/01  Minor debug logging changes.
   1.09      7/23/01  Fixed bug recognizing Registry key for adapter
                      where one adapter is good and a subsequent one is
                      *almost* OK.
   1.10     12/18/01  In Win2K/XP we now no longer check first for direct
                      connection to Tcpip in checking for a useful adapter.
   1.10.1     3/3/02  We no longer assume a ("useless" non-physical) adapter
                      will have an Ndi\Interfaces subkey.  Temporarily, we
                      ignore whether a 2K/XP adapter is connected to Tcpip.
   1.10.2    3/20/02  Now using new driver version for 2K/XP compatibility.
                      Delay response slightly in loopback testing.
   1.11      4/13/03  Logic for subnet exclusion now considers DHCP-based
                      Registry subnet parameters too. 
  
1.12      4/20/03  Cleaned up logic for when checkStack() fails.
   1.13      4/29/03  Fixed getValue() length bug in getSubnetMask().
   1.14       6/6/03  Now require driver version 1.02
   1.15     10/12/03  Compute our IP address the Winsock way if the Registry
                      fails us.
*/

#if !defined(_MT) // Symantec seems to need this to believe we're multithreaded
//#define _MT
#endif

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <process.h>
#include <wincon.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>              // for sscanf; sorry!
#include <iostream.h>
#include <strstrea.h>
#include <fstream.h>
#include <iomanip.h>
#include <string.h>
#include <stdlib.h>
#include <winioctl.h>
#include <winsock.h>
#include <time.h>
#include "shared.h"
#include "resource.h"

#define VERSION "1.15"
#define TEST_VERSION 0          // if nonzero, annoying messagebox
#define WM_REALLY_CLOSE WM_APP

BOOL quiescing = FALSE;         // set by GUI, obeyed by threads

/*
  This class will create a list of pseudo-IP addresses for subnets from a
  RARPD command line and return them one by one with the overloaded array
  indexing operator, returning zero if you've gone beyond the last one.
*/

class SubnetHolder {
  size_t count;
  long* subnets;
public:
  SubnetHolder() { count = 0; }
  ~SubnetHolder() { if (count) delete[] subnets; }
  void init(char* acmdLine);
  long operator[] (size_t a) { return (a < count) ? subnets[a] : 0; }
};

void SubnetHolder::init(char* acmdLine)
{
  const char* sentinel = "/XS";
  if (count) {
    delete[] subnets;
    count = 0;
  }
  for (char* next = acmdLine;
       (next = strstr(next, sentinel)) != NULL;
       count++) {
    next += strlen(sentinel);
    long* newSubnets = new long[count + 1];
    newSubnets[count] = inet_addr(next);
    if (count) {
      memcpy(newSubnets, subnets, count * sizeof(long));
      delete[] subnets;
    }
    subnets = newSubnets;
  }
}


/*
  What OS are we running on?  Only NT's acceptable.
*/

enum OSType { W95, WNT };

OSType os = W95;
DWORD osMajorVersion, osMinorVersion;

UINT mbType = MB_APPLMODAL;

HDESK mainWindowDesktop = NULL; // used in NT desktop switching

/*
  Navigating the registry:
*/

// Table listing info on legal root keys:

struct {
  char* name;
  HKEY handle;
} rootKeys[] = {
  { "HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT },
  { "HKEY_CURRENT_USER", HKEY_CURRENT_USER },
  { "HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE },
  { "HKEY_USERS", HKEY_USERS },
  { "HKEY_DYN_DATA", HKEY_DYN_DATA },
  { NULL, NULL }
};

/*
   These are the names of the registry keys we use the most:
*/

char* tcpKeyName = "";          // filled in by configureForOS()

/*
   We'll often need to know if a key is a root key because if so we'd better
   not close its handle.
*/

BOOL isRootKey(HKEY h)
{
  for (int i = 0; rootKeys[i].name != NULL; i++) {
    if (rootKeys[i].handle == h) return TRUE;
  }
  return FALSE;
}

/*
   It's impossible to get a handle for a non-root key; you have to start
   with its root key and work outward.
*/

HKEY getRootKey(const char* apath)
{
  for (int i = 0; rootKeys[i].name != NULL; i++) {
    size_t rkiLen = strlen(rootKeys[i].name);
    if (!strncmp(apath, rootKeys[i].name, rkiLen)) {
      if ((apath[rkiLen] == '\0') || (apath[rkiLen] == '\\')) {
        return rootKeys[i].handle;
      }
    }
  }
  return NULL;
}

ofstream outFile;

enum {
  logLineLen = 500,             // Use this in ostrstreams for listbox string.
  msgLen = 500,                 // Use this for message boxes.
  ObjectNameLen = logLineLen / 2 // Use this for desktop names.
};

/*
  If we have two handles to NT desktops, the objects may be the same even
  if the handles are different.  That's what we test here.
*/

BOOL differentObjects(HANDLE ahandleA, HANDLE ahandleB)
{
  BOOL result = FALSE;
  char objectNameA[ObjectNameLen];
  char objectNameB[ObjectNameLen];
  DWORD ignoreLenNeeded;
  if ((GetUserObjectInformation(ahandleA, UOI_NAME, objectNameA,
                                ObjectNameLen, &ignoreLenNeeded)) &&
      (GetUserObjectInformation(ahandleB, UOI_NAME, objectNameB,
                                ObjectNameLen, &ignoreLenNeeded))) {
    result = strcmp(objectNameA, objectNameB);
  }
  else outFile << "Can't get object name" << GetLastError() << endl;
  return result;
}

/*
   Send the main window's listbox one line of text.  After we send the line
   we select it to make sure it's visible, and then we deselect it so it
   won't call attention to itself.  If we're in a desperate hurry, though -
   dump() is, due to its responsibility in promiscuous mode - we do without
   the visibility manipulation.  By the way, while this function would run
   faster using PostMessage() than with SendMessage(), the data wouldn't get
   through reliably.

   There's a kink here that was introduced when we made it possible to run,
   launched by an NT service at boot time, trying to grab a desktop we wouldn't
   normally have (WinLogon.)  Since we can't execute SetThreadDesktop once
   we already have a window, we need the ability to log messages without a
   main window, i.e. straight to rarpd.log.
*/

void logLine(HWND amainWindow, const char* aline, BOOL ahurry = FALSE)
{
  if (!amainWindow) {
    outFile << aline << endl;
    return;
  }
  static HWND dlg = HWND(INVALID_HANDLE_VALUE);
  if (dlg == INVALID_HANDLE_VALUE) dlg = GetDlgItem(amainWindow, IDC_LOGBOX);
  SendMessage(dlg, LB_ADDSTRING, 0, (LPARAM)aline);
  if (!ahurry) {
    LONG lastLine = SendMessage(dlg, LB_GETCOUNT, 0, 0) - 1;
    SendMessage(dlg, LB_SETCARETINDEX, WPARAM(lastLine), MAKELPARAM(FALSE, 0));
    UpdateWindow(dlg);
  }
}

/*
  This is a convenience function for the cases where we just have a string
  to put out together with Windows's wisdom on what might have happened.
  Just to be tidy, we strip the trailing CRLF in the Windows error message.
*/

void logLastError(HWND amainWindow, char* aline)
{
  char* errMsgBuf = "";
  char* msgBuf = new char[logLineLen];
  ostrstream msgOss(msgBuf, logLineLen);
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                NULL, GetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                (LPTSTR) &errMsgBuf, 0, NULL);
  char* crLoc = strchr(errMsgBuf, '\r');
  if (crLoc) *crLoc = '\0';
  msgOss << aline << ":  " << errMsgBuf << ends;
  logLine(amainWindow, msgBuf);
  LocalFree(errMsgBuf);
}

/*
  Running under NT, launched by a service, things get complicated.  Originally
  our aproach involved a window of 0 to go along with our
  MB_APPLMODAL | MB_SERVICE_NOTIFICATION style.  But we like to make the
  whole context of the rarp transaction, i.e. the log in our main window,
  available when things get bad enough for us to put up a message box.  So
  we devised a scheme that allowed us to switch desktops temporarily back
  to the one our main window lives in, and then restore the current input
  desktop once the message box is dismissed.  If this seems bizarre, consider
  that the situation really does arise when the following events
  transpire:
 
  - rarpd is launched by its service before anyone logs on and puts its
  main window where it can be seen, i.e. in the Winlogon desktop;

  - someone logs on before rarpd finishes, so the Default desktop becomes
  current and rarpc's main window becomes invisible;

  - rarpd learns of something that requires the user's attention and wants
  to put up a message box anchored by the main window.

  By the way, there's plenty of time for the second event in this sequence
  to interpose itself between the first and third when the third is a rarp
  failure, i.e. a timeout.
*/

int ourMessageBox(HWND hwnd, char* amsg, UINT atype)
{
  int result;
  ShowWindow(hwnd, SW_RESTORE);
  if (os == WNT) {
    HDESK newDesktop = OpenInputDesktop(0, FALSE, DESKTOP_SWITCHDESKTOP);
    if (!newDesktop) logLastError(hwnd, "Can't open input desktop");
    if (differentObjects(mainWindowDesktop,
                         GetThreadDesktop(GetCurrentThreadId()))) {
      if (!SetThreadDesktop(mainWindowDesktop)) {
        logLastError(hwnd, "SetThreadDesktop for main window");
      }
    }
    if ((mainWindowDesktop && newDesktop) &&
        differentObjects(mainWindowDesktop, newDesktop)) {
      if (SwitchDesktop(mainWindowDesktop)) {
        result = MessageBox(hwnd, amsg, "rarpd", atype);
        if (!result) logLastError(hwnd, "MessageBox");
        if (!SwitchDesktop(newDesktop)) {
          logLastError(hwnd, "Couldn't switch back to new desktop");
        }
        return result;
      }
      else logLastError(hwnd, "Couldn't switch to old desktop");
    } // else fall through
  } // end of NT-specific logic
  result = MessageBox(hwnd, amsg, "rarpd", atype);
  if (!result) logLastError(hwnd, "MessageBox");
  return result;
}

/*
   Two functions, alert() and fail(), centralize our error messages
   and ensure that when an error message goes up on the screen the listbox
   is visible so the user has some context for the message.  A third,
   yesOrNo(), does something similar when the user must decide.  These
   functions also make sure messages and responses get logged.
*/

/*
   Here we've reached a point where we can't go on.  This is a convenience
   function removing some Windows GUI clutter.  For those cases in which
   we think it's OK without alarming the user, there's an optional Boolean
   allowing this.
*/

void fail(HWND amainWindow, char* amsg, BOOL asilent = FALSE)
{
  if (!asilent) ourMessageBox(amainWindow, amsg, MB_ICONSTOP);
  logLine(amainWindow, "**Fatal error:");
  logLine(amainWindow, amsg);
  if (amainWindow) SendMessage(amainWindow, WM_CLOSE, 0, 0);
  else exit(-1);
}

/*
   Here we need to alert the user to something that might not be fatal.
*/

void alert(HWND amainWindow, char* amsg)
{
  ourMessageBox(amainWindow, amsg, MB_ICONEXCLAMATION);
  logLine(amainWindow, amsg);
}

/*
   Here we need to ask the user a yes-or-no question.
*/

int yesOrNo(HWND amainWindow, char* amsg)
{
  int result;
  result = ourMessageBox(amainWindow, amsg, MB_YESNO);
  logLine(amainWindow, "**Query:");
  logLine(amainWindow, amsg);
  logLine(amainWindow, ((result == IDYES) ? "[yes]" : "[no]"));
  return result;
}

/*
  This class will create a list RARP table entries from rarpd.tbl
  and return pointers to them one by one with the overloaded array
  indexing operator, returning NULL if you've gone beyond the last one.
*/

struct RarpTblEntry {
  UCHAR macAddress[MacAddressSize];
  UINT ipAddress;
};

int rarpTblEntryCmp(const void* aleft, const void* aright)
{
  const RarpTblEntry* left = (const RarpTblEntry*)aleft;
  const RarpTblEntry* right = (const RarpTblEntry*)aright;
  return memcmp(left->macAddress, right->macAddress, MacAddressSize);
}

/*
  Our RARP table class encapsulates a lazy reinitialization behavior in
  response to the table having changed on disk.  We only consider
  reinitializing the table when the caller is accessing the first entry
  in the table; then we check the file only if it has been at least a
  minute since the last check.  We reinitialize then if the file has been
  written since the last initialization.
*/

class RarpTbl {
  size_t count;
  RarpTblEntry* entries;
  HWND mainWindow;
  time_t timeOfLastFileCheck;
  FILETIME timeOfLastInit;
  void considerInit();
public:
  RarpTbl(HWND amainWindow) : mainWindow(amainWindow), count(0),
    timeOfLastFileCheck(0) {
      timeOfLastInit.dwLowDateTime = timeOfLastInit.dwHighDateTime = 0;
      init();
  }
  ~RarpTbl() { if (count) delete[] entries; }
  void init();
  RarpTblEntry* operator[] (size_t a) {
    if (a == 0) considerInit();
    return (a < count) ? &entries[a] : NULL;
  }
  RarpTblEntry* find(const unsigned char* akey) {
    considerInit();
    return (RarpTblEntry*)bsearch(akey, entries, count, sizeof(RarpTblEntry),
                                  rarpTblEntryCmp);
  }
};

void RarpTbl::considerInit()
{
  time_t now = time(NULL);
  if (UINT(now - timeOfLastFileCheck) > 60) {
    timeOfLastFileCheck = now;
    HANDLE fh = CreateFile("RARPD.TBL", GENERIC_READ,
                           0, // Not interested if it's being edited
                           NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (fh != INVALID_HANDLE_VALUE) {
      FILETIME timeLastWritten;
      BOOL gotFileTime = GetFileTime(fh, NULL, NULL, &timeLastWritten);
      CloseHandle(fh);
      if (gotFileTime) {
        if (CompareFileTime(&timeOfLastInit, &timeLastWritten) < 0) init();
      }
    }
  }
}

void RarpTbl::init()
{
  GetSystemTimeAsFileTime(&timeOfLastInit);
  ifstream inFile("RARPD.TBL", ios::in | ios::nocreate);
  logLine(mainWindow, "Initializing RARP table");
  if (!inFile.ipfx()) fail(mainWindow, "Can't open RARPD.TBL");
  if (count) {
    delete[] entries;
    count = 0;
  }
  char buf[200];
  for (size_t lineNo = 1; inFile.ipfx(); lineNo++) {
    RarpTblEntry next;
    next.ipAddress = INADDR_NONE;
    inFile.getline(buf, sizeof(buf));
    // logLine(mainWindow, buf);
    if (!isalnum(buf[0])) continue;
    char ipAddr[200];
    int digits[MacAddressSize];
    if (sscanf(buf, "%02x.%02x.%02x.%02x.%02x.%02x %s",
               digits, digits + 1, digits + 2, digits + 3, digits + 4,
               digits + 5, ipAddr) == 7) {
      for (size_t byteNo = 0; byteNo < MacAddressSize; byteNo++) {
        next.macAddress[byteNo] = UCHAR(digits[byteNo]);
      }
      next.ipAddress = inet_addr(ipAddr);
    }
    if (next.ipAddress == INADDR_NONE){
      strcat(buf, ": bad");
      logLine(mainWindow, buf);
      continue;
    }
    RarpTblEntry* newEntries = new RarpTblEntry[count + 1];
    memcpy(&newEntries[count], &next, sizeof(RarpTblEntry));
    if (count) {
      memcpy(newEntries, entries, count * sizeof(RarpTblEntry));
      delete[] entries;
    }
    entries = newEntries;
    count++;
  }
  qsort(entries, count, sizeof(RarpTblEntry), rarpTblEntryCmp);
}

/*
  Here, since we know we're running on NT, we do what's necessary to make
  ourselves useful should we be running launched by a service before logon.
  (This is after all the best way to run rarpd on NT.)  We see if we're
  already running on the input desktop and, if not, try to seize it.  If
  we do switch desktops we make sure our window won't be minimized, because
  in our experience a minimized window on the NT Winlogon desktop is a dead
  duck.
*/

void setNTDesktop(int& acmdShow, const OSVERSIONINFO& avInfo)
{
  char* msgBuf = new char[logLineLen];
  ostrstream msgOss(msgBuf, logLineLen);
  mbType = (avInfo.dwMajorVersion < 4) ? // Service notification
    MB_TASKMODAL | 0x00040000 : MB_TASKMODAL | 0x00200000;
  enum { ObjectNameLen = logLineLen / 2 };
  char oldDesktopName[ObjectNameLen];
  char newDesktopName[ObjectNameLen];
  DWORD ignoreLenNeeded;
  HWINSTA windowStation = GetProcessWindowStation();
  if (windowStation) {
    char stationName[ObjectNameLen];
    if (GetUserObjectInformation(windowStation, UOI_NAME, stationName,
                                 ObjectNameLen, &ignoreLenNeeded)) {
      msgOss << "Window station name: " << stationName << ends;
      logLine(0, msgBuf);
      msgOss.seekp(0);
    }
    else logLastError(0, "Can't get window station name");
  }
  else logLastError(0, "Can't get window station");
  HDESK oldDesktop = GetThreadDesktop(GetCurrentThreadId());
  if (oldDesktop) {
    if (GetUserObjectInformation(oldDesktop, UOI_NAME, oldDesktopName,
                                 ObjectNameLen, &ignoreLenNeeded)) {
      msgOss << "Old desktop name: " << oldDesktopName << ends;
      logLine(0, msgBuf);
      msgOss.seekp(0);
    }
    else logLastError(0, "Can't get old desktop name");
  }
  else logLastError(0, "Can't get thread desktop");
  HDESK newDesktop = OpenInputDesktop(0, TRUE, DESKTOP_CREATEWINDOW |
                                      DESKTOP_SWITCHDESKTOP);
  if (newDesktop) {
    if (GetUserObjectInformation(newDesktop, UOI_NAME, newDesktopName,
                                 ObjectNameLen, &ignoreLenNeeded)) {
      msgOss << "New desktop name: " << newDesktopName << ends;
      logLine(0, msgBuf);
      msgOss.seekp(0);
    }
    else logLastError(0, "Can't get new desktop name");
    if (strcmp(oldDesktopName, newDesktopName)) {
      if (SetThreadDesktop(newDesktop)){
        acmdShow = SW_SHOWNORMAL;
        mainWindowDesktop = newDesktop;
      }
      else logLastError(0, "SetThreadDesktop");
    }
    else {
      mainWindowDesktop = oldDesktop;
      logLine(0, "Already have the input desktop");
    }
  }
  else logLastError(0, "Can't open input desktop");
}

/*
  The handles for dealing with the NT RARP driver:
*/

SC_HANDLE scmHandle = NULL;
SC_HANDLE srvHandle = NULL;
HANDLE ntRarpHandle = INVALID_HANDLE_VALUE;
HINSTANCE ourInstance = HINSTANCE(INVALID_HANDLE_VALUE);

/*
  Normally the driver doesn't already exist as a service when we're called,
  but if it already exists that's OK.  We know the current directory has been
  set to the directory rarpd was loaded from.  The call to GetFileAttributes
  is necessary because CreateService will vacuously succeed even if it fails
  to find the driver.
*/

BOOL installNTDriver(SC_HANDLE ascmHandle)
{
  BOOL result = FALSE;
  char driverPath[logLineLen];
  *driverPath = 0;
  GetCurrentDirectory(logLineLen, driverPath);
  wsprintf(driverPath + strlen(driverPath), "\\RARP.SYS");

  if (GetFileAttributes(driverPath) != 0xffffffff) {
    SC_HANDLE srvHandle =
      CreateService(ascmHandle, "RARP", "RARP Support",
                    SERVICE_ALL_ACCESS,
                    SERVICE_KERNEL_DRIVER,
                    SERVICE_DEMAND_START,
                    SERVICE_ERROR_NORMAL,
                    driverPath,
                    NULL, NULL, NULL, NULL, NULL);
    if (srvHandle == NULL) {
      if (GetLastError() == ERROR_SERVICE_EXISTS) {
        logLine(0, "RARP.SYS already existed");
        result = TRUE;
      }
      else logLastError(0, "Couldn't install RARP.SYS");
    }
    else {
      logLine(0, "Created service for RARP.SYS");
      result = TRUE;
      CloseServiceHandle(srvHandle); // 0.54.2
    }
  }
  else logLine(0, "Can't find RARP.SYS");
  return result;
}

/*
  Here we wait for success (in which case we return TRUE) or either of two
  kinds of failure:
  - we get an error from StartService() other than an indication that the
    service database is still locked;
  - the user gives up.
  In either error condition we log an error message and return FALSE.
*/

BOOL APIENTRY startServiceDialogProc(HWND hDlg, UINT message, UINT wParam,
                                     LONG /* lParam */)
{
  switch (message)
    {
    case WM_INITDIALOG:
      {
        SetDlgItemText(hDlg, IDC_EDIT_WAIT_SCM,
                       "Windows NT has reported to rarpd that the "
                       "service database is locked.  This means that "
                       "rarpd's device driver cannot yet be started. "
                       "Normally this is "
                       "a temporary situation, especially when rarpd "
                       "is being used to configure a new workstation.\r\n"
                       "Unless you press the button below, rarpd will "
                       "continue trying to start its driver until it "
                       "succeeds.\r\n"
                       "If you press the button, rarpd will try to reach "
                       "the rarp server without the use of its device "
                       "driver.  You are strongly urged to wait.");
        SetTimer(hDlg, 0, 5000, NULL);
      }
      break;
    case WM_COMMAND:
      if (wParam == IDC_BUTTON_STOP) {
        SetLastError(ERROR_SERVICE_DATABASE_LOCKED);
        logLastError(0, "Couldn't start RARP service");
        return EndDialog(hDlg, FALSE);
      }
      break;
    case WM_TIMER:
      if (StartService(srvHandle, 0, NULL)) return EndDialog (hDlg, TRUE);
      else {
        DWORD error = GetLastError();
        if (error != ERROR_SERVICE_DATABASE_LOCKED) {
          SetLastError(error);
          logLastError(0, "Couldn't start RARP service");
          return EndDialog (hDlg, FALSE);
        }
      }
      break;
    }
  return FALSE;
}

/*
  Because our call to StartService() can be frustrated by a transitory lock
  on the service database held by some other process, we keep trying to
  start our driver periodically while displaying a dialog that allows the
  user to give up.
*/

void ourStartService()
{
  BOOL result = FALSE;
  if (StartService(srvHandle, 0, NULL)) result = TRUE;
  else {
    DWORD error = GetLastError();
    if (error == ERROR_SERVICE_DATABASE_LOCKED) {
      result =  DialogBox(ourInstance, MAKEINTRESOURCE(DIALOG_WAIT_SCM), 0,
                          (DLGPROC)startServiceDialogProc);
    }
    else {
      SetLastError(error);
      logLastError(0, "Couldn't start RARP service");
    }
  }
  if (result) logLine(0, "Started RARP service");
}

void connectToNTDriver()
{
  scmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
  if (scmHandle == NULL) fail(0, "OpenSCManager");
  if (installNTDriver(scmHandle)) {
    srvHandle = OpenService(scmHandle, "RARP", SERVICE_ALL_ACCESS);
    if (srvHandle != NULL) ourStartService();
    else logLastError(0, "Couldn't open RARP service");
  }
}

void disconnectFromNTDriver()
{
  if (ntRarpHandle != INVALID_HANDLE_VALUE) {
    if (!CloseHandle(ntRarpHandle)) {
      logLastError(0, "Can't close RARP device");
    }
  }
  if (srvHandle != NULL) {
    SERVICE_STATUS  serviceStatus;
    SC_HANDLE stopHandle = OpenService(scmHandle, "RARP",
                                       SERVICE_ALL_ACCESS);
    if (stopHandle == NULL) logLastError(0, "OpenService stopping RARP");
    else {
      if (!ControlService(stopHandle, SERVICE_CONTROL_STOP, &serviceStatus)) {
        logLastError(0, "ControlService SERVICE_STOP");
      }
      CloseServiceHandle(stopHandle);
    }
    SC_HANDLE removeHandle = OpenService(scmHandle, "RARP",
                                         SERVICE_ALL_ACCESS);
    if (removeHandle == NULL) logLastError(0, "OpenService removing RARP");
    else {
      if (!DeleteService(removeHandle)) {
        logLastError(0, "DeleteService for RARP");
      }
      CloseServiceHandle(removeHandle);
    }
    CloseServiceHandle(srvHandle);
  }
  if (scmHandle != NULL) CloseServiceHandle(scmHandle);
}

void shutdown()
{
  if (os == WNT) disconnectFromNTDriver();
  char dateStr[80], timeStr[80];
  GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL,
                "yyyy'-'MM'-'dd", dateStr, sizeof(dateStr));
  GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT, NULL,
                "HH':'mm':'ss", timeStr, sizeof(timeStr));
  outFile << "rarpd finished " << dateStr << " " << timeStr << endl;
  outFile.close();
}

BOOL ctrlHandler(DWORD actrlChar)
{
  switch(actrlChar)
    {
    case CTRL_SHUTDOWN_EVENT:
    case CTRL_LOGOFF_EVENT:
      shutdown();
      return FALSE;             // Quit the program.
    default:
      break;
    }
  return TRUE;                  // Don't quit.
}

/*
  Since we run under two different operating systems, there are some things
  we need to set up depending on which one it is.  We keep an enumerator
  for the OSes we tolerate.
*/

DWORD transportValueType = REG_SZ;
char* subnetMaskString = "IPMask";
char* dhcpSubnetMaskString = "DhcpIPMask";

void configureForOS(int& acmdShow)
{
  char* msgBuf = new char[logLineLen];
  ostrstream msgOss(msgBuf, logLineLen);
  char* osString = "";
  OSVERSIONINFO vinfo;
  vinfo.dwOSVersionInfoSize = sizeof(vinfo);
  GetVersionEx(&vinfo);
  osMajorVersion = vinfo.dwMajorVersion;
  osMinorVersion = vinfo.dwMinorVersion;
  switch(vinfo.dwPlatformId) {
  case VER_PLATFORM_WIN32_NT:
    os = WNT;
    osString = "NT";
    tcpKeyName = "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\"
      "Services\\Tcpip\\Parameters";
    transportValueType = REG_MULTI_SZ;
    subnetMaskString = "SubnetMask";
    dhcpSubnetMaskString = "DhcpSubnetMask";
    setNTDesktop(acmdShow, vinfo);
#if 0 // Control handler doesn't get called at system shutdown: why??
    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)ctrlHandler, TRUE)) {
      fail(0, "Can't install shutdown handler");
    }
#endif
    connectToNTDriver();
    break;
  default:
    fail(0, "This program requires Windows NT");
    break;
  }
  msgOss << "Running under Windows " << osString << " "
         << osMajorVersion << "." << osMinorVersion << "." << ends;
  logLine(0, msgBuf);
}

/*
   Here we can't proceed because a registry key we need doesn't exist.
*/

void abortOnBadKey(const char* apath, HWND amainWindow)
{
  char* msgBuf = new char[logLineLen];
  ostrstream msgOss(msgBuf, logLineLen);
  msgOss << "Can't find key" << endl
    << "  " << apath << endl
    << "in registry.\n"
    << "Make sure Microsoft TCP/IP is properly installed"
    << " and that you have Registry write access for IP."
    << ends;
  fail(amainWindow, msgBuf);
}

/*
   As we navigate outward from the root to the key we seek, we make sure to
   close all intermediate keys.  While navigating we ask only read access
   but at our destination we want to be able to modify things.
*/

HKEY getKey(const char* apath, HWND amainWindow, BOOL asilent = FALSE)
{
  HKEY thisKeyHandle = getRootKey(apath);
  if (thisKeyHandle == NULL) abortOnBadKey(apath, amainWindow);
  char subKeyName[80];
  const char* separatorP = apath + strcspn(apath, "\\");
  while (*separatorP) {
    const char* nextSubKeyP = separatorP + 1;
    size_t subKeyLen = strcspn(nextSubKeyP, "\\");
    memcpy(subKeyName, nextSubKeyP, subKeyLen);
    subKeyName[subKeyLen] = '\0';
    separatorP = nextSubKeyP + subKeyLen;
    HKEY nextKeyHandle;
    if (RegOpenKeyEx(thisKeyHandle, subKeyName, 0,
                     KEY_READ,
                     &nextKeyHandle)!= ERROR_SUCCESS) {
      if (!asilent) {
        char* msgBuf = new char[logLineLen];
        ostrstream msgOss(msgBuf, logLineLen);
        msgOss << "Failure on " << apath << " at subkey: " << subKeyName  <<
          " with read access." << ends;
        logLine(amainWindow, msgBuf);
      }
      RegCloseKey(thisKeyHandle);
      return NULL;
    }
    RegCloseKey(nextKeyHandle);
    if (RegOpenKeyEx(thisKeyHandle, subKeyName, 0,
                     KEY_READ | KEY_SET_VALUE,
                     &nextKeyHandle)!= ERROR_SUCCESS) {
      if (!asilent) {
        char* msgBuf = new char[logLineLen];
        ostrstream msgOss(msgBuf, logLineLen);
        msgOss << "Failure on " << apath << " at subkey: " << subKeyName  <<
          " with full access." << ends;
        logLine(amainWindow, msgBuf);
      }
      RegCloseKey(thisKeyHandle);
      return NULL;
    }
    if (!isRootKey(thisKeyHandle)) RegCloseKey(thisKeyHandle);
    thisKeyHandle = nextKeyHandle;
  }
  return thisKeyHandle;
}

/*
  Sometimes we just need to know if a Registry key exists...
*/

BOOL keyExists(const char* apath, HWND amainWindow)
{
  BOOL result = FALSE;
  HKEY keyHandle = getKey(apath, amainWindow, TRUE);
  if (keyHandle != NULL) {
    RegCloseKey(keyHandle);
    result = TRUE;
  }
  return result;
}

/*
   If we can find a value corresponding to the key name and expected name,
   return TRUE and stow its length.  The argument "alength" is used a la
   Microsoft, i.e. both as input and output.  We don't leave a non-root
   key handle open.
*/

BOOL getValue(const char* akeyName, char* anexpectedName, BYTE* abuf,
              DWORD& alength, DWORD atype, HWND amainWindow)
{
  HKEY keyHandle = getKey(akeyName, amainWindow);
  BOOL result = FALSE;
  DWORD regType;                // value's type
  if (keyHandle == NULL) abortOnBadKey(akeyName, amainWindow);
  if (RegQueryValueEx(keyHandle, anexpectedName, NULL,
                      &regType, abuf, &alength) == ERROR_SUCCESS) {
    if (regType == atype) result = TRUE;
    else {
      size_t bufLen = strlen(anexpectedName) + strlen(akeyName) + 100;
      char* msgBuf = new char[bufLen];
      ostrstream msgOss(msgBuf, bufLen);
      msgOss << "Unexpected value type in registry for" << endl
             << "  key: " << akeyName << endl
             << "  value name: " << anexpectedName << ends;
      alert(amainWindow, msgBuf);
    }
  }
  if (!isRootKey(keyHandle)) RegCloseKey(keyHandle);
  return result;
}

/*
  The timeout that the receiver thread itself uses to quit on a read from
  RARP is made shorter than the one the worker thread uses to kill the
  receiver thread by the initialization of ReceiverParams.
*/

struct ReceiverParams {
  HWND mainWindow;
  DWORD timeout;
  UINT ourIpAddress;
  BOOL& quiescing;
  UCHAR ourMacAddress[MacAddressSize];
  USHORT ourHwType;
  UCHAR ourHwLen;
  RarpTbl& tbl;
  ReceiverParams(HWND amainWindow, DWORD atimeout, RarpTbl& atbl,
                 UINT anipAddress, BOOL& aquiescing) :
    mainWindow(amainWindow),
    timeout((atimeout > 2000) ? (atimeout - 2000) : 1000 ),
    tbl(atbl),  ourIpAddress(anipAddress), quiescing(aquiescing) { }
  BOOL getntRarpHardwareAddress();
};

struct WorkerParams {
  HWND mainWindow;
  BOOL dumpDriver;              // dump the driver?
  SubnetHolder& excludedSubnets; // Subnets we ignore searching for netcard
  DWORD rxTimeout;              // how long we wait for reply (msec.)
  char* transportKeyName;       // card-specific TCP transport key name
  char* adapterName;            // e.g. "Elnk31"
  WorkerParams(HWND amainWindow,
               BOOL adumpDriver, DWORD arxTimeout,
               SubnetHolder& anexcludedSubnets) :
    mainWindow(amainWindow), dumpDriver(adumpDriver),
    rxTimeout(arxTimeout),
    excludedSubnets(anexcludedSubnets),
    transportKeyName(NULL), adapterName(NULL) { };
  ~WorkerParams() {
    if (transportKeyName) delete transportKeyName;
    if (adapterName) delete adapterName;
  };
  void dumpDriverMemory();
  char* findNT4NetcardTCPParams();
  char* findNT5NetcardTCPParams();
  BOOL isSubnetExcluded(char* atcpParamsKeyName);
  void openntRarp();
  BOOL checkStack();
  UINT ourIpAddress();
  BOOL probeAdapter(char* anadapterName);
};

/*
   If we get something from the netcard that looks fishy, we want to dump it.
*/

void dump(HWND amainWindow, UINT asize, BYTE* abufP, char* alegend)
{
  logLine(amainWindow, alegend);
  for (UINT offset = 0; offset < asize; offset += 16) {
    char* msgBuf = new char[logLineLen];
    ostrstream msgOss(msgBuf, logLineLen);
    msgOss << hex << setw(3) << setfill('0') << offset << ":";
    for (UINT incr = 0; incr < 16; incr++) {
      if (offset + incr >= asize) break;
      if ((incr % 4) == 0) msgOss << " ";
      msgOss << hex << setw(2) << setfill('0') << WORD(abufP[offset + incr]);
    }
    msgOss << ends;
    logLine(amainWindow, msgBuf, TRUE);
  }
}

BOOL ReceiverParams::getntRarpHardwareAddress()
{
  char allZeroes[6] = { 0 };
  DWORD length;
  BYTE hwType;
  BOOL result = FALSE;
  if (DeviceIoControl(ntRarpHandle, DIOC_RarpGetMacAddress,
                      NULL, 0, ourMacAddress, MacAddressSize, &length, NULL) &&
      memcmp(ourMacAddress, allZeroes, sizeof(allZeroes))) {
    DWORD hwLength;
    if (DeviceIoControl(ntRarpHandle, DIOC_RarpGetHWType,
                        NULL, 0, &hwType, 1, &hwLength, NULL)) {
      result = TRUE;
      ourHwLen = UCHAR(length);
      ourHwType = htons(hwType);
    }
  }
  return result;
}

/*
  The receiver thread exits when it detects a good  reply
  (params->success TRUE) or when the socket fails (FALSE.)
*/

void rarpReceiver(void* aparams)
{
  ReceiverParams& params = *((ReceiverParams*)aparams);
  if (!params.getntRarpHardwareAddress()) {
    fail(params.mainWindow, "Couldn't get hardware address.");
  }
  RarpBuf rxBuf, txBuf;
  txBuf.ap.hwType = params.ourHwType;
  txBuf.ap.protType = htons(ProtocolTypeIP);
  txBuf.ap.hwLen = params.ourHwLen;
  txBuf.ap.protLen = 4;
  txBuf.ap.op = htons(RarpOpReply);
  memcpy(txBuf.ap.senderMacAddress, params.ourMacAddress, MacAddressSize);
  txBuf.ap.senderProtAddress = params.ourIpAddress;

  while (!params.quiescing) {
    OVERLAPPED ovrlp = {0,0,0,0,0};
    DWORD bytesReturned;
    BOOL gotIt = FALSE;
    if ((ovrlp.hEvent = CreateEvent(0, FALSE, 0, NULL)) == 0) {
      fail(params.mainWindow, "Windows is out of resources.");
    }
    else if (ReadFile(ntRarpHandle, &rxBuf, sizeof(RarpBuf),
                      &bytesReturned, &ovrlp)) gotIt = TRUE;
    else if (GetLastError() == ERROR_IO_PENDING) {
      while (!gotIt && !params.quiescing) {
        if (WaitForSingleObject(ovrlp.hEvent, params.timeout)
            == WAIT_OBJECT_0) {
          GetOverlappedResult(ntRarpHandle, &ovrlp, &bytesReturned, FALSE);
          gotIt = TRUE;
        }
      }
    }
    else {
      logLastError(params.mainWindow, "Can't read from RARP");
      CloseHandle(ovrlp.hEvent);
      break;
    }
    if (gotIt) {                // It's for us.
      if (ntohs(rxBuf.ap.op) == RarpOpRequest) {
        memcpy(txBuf.ap.targetMacAddress, rxBuf.ap.targetMacAddress,
               MacAddressSize);
        RarpTblEntry* entryP = params.tbl.find(rxBuf.ap.targetMacAddress);
        if (entryP) {
          txBuf.ap.targetProtAddress = entryP->ipAddress;
          memcpy(txBuf.remoteMacAddress, rxBuf.remoteMacAddress,
                 MacAddressSize);
          if (memcmp(params.ourMacAddress, rxBuf.remoteMacAddress,
                     MacAddressSize) == 0) { // loopback testing!
            Sleep(50);          // Give client time to dump its own request.
          }
          OVERLAPPED txOvrlp = { 0 };
          DWORD bytesSent;
          if ((txOvrlp.hEvent = CreateEvent(0, FALSE, 0, NULL)) == 0) {
            fail(params.mainWindow, "Can't create event for WriteFile");
          }
          else if (!WriteFile(ntRarpHandle, &txBuf, sizeof(txBuf),
                              &bytesSent, &txOvrlp)) {
            if (GetLastError() == ERROR_IO_PENDING) {
              GetOverlappedResult(ntRarpHandle, &txOvrlp, &bytesSent, TRUE);
            }
            else logLastError(params.mainWindow, "WriteFile");
          }
          CloseHandle(txOvrlp.hEvent);
        }
      }
      else if (ntohs(rxBuf.ap.op) != RarpOpReply) {
        dump(params.mainWindow, bytesReturned, (UCHAR*)&rxBuf,
             "Buffer but not a RARP request");
      }
    }
    CloseHandle(ovrlp.hEvent);
  }
  _endthread();
}

/*
  This function returns the last segments of a Registry key, starting with
  the character after the backslash, if there is a backslash.  Usually we
  just want the last one segment, but we may want more than that.
*/

char* keyNameTail(char* akeyName, int atailSegments = 1)
{
  char* p;
  for (p = akeyName + strlen(akeyName) - 1; ; p--) {
    if (p == akeyName) return p; // no backslash; simple keyname
    if ((*p == '\\') && (--atailSegments <= 0)) break;
  }
  return (p + 1);
}

/*
   Here we log everything we wrote to the listbox.
*/

void writeLog(HWND hDlg)
{
  enum { lineBufLen = 500 };
  char* lineBuf = new char[lineBufLen];
  LRESULT lineLen;
  for (USHORT lineNo = 0;
       (lineLen = SendDlgItemMessage(hDlg, IDC_LOGBOX, LB_GETTEXTLEN,
                                     lineNo, 0)) != LB_ERR;
       lineNo++) {
    if (lineLen < lineBufLen) {
      SendDlgItemMessage(hDlg, IDC_LOGBOX, LB_GETTEXT, lineNo,
                         (LPARAM)lineBuf);
      lineBuf[lineLen] = '\0';
      outFile << lineBuf << endl;
    }
    else outFile << "**Listbox line too long: " << lineLen << endl;
  }
  delete lineBuf;
}

void WorkerParams::openntRarp()
{
  enum { SYSVersionMin = 102, SYSVersionMax = 102 };
  if (srvHandle != NULL) {      // Service exists
    char rarpDeviceName[80];
    wsprintf(rarpDeviceName, "\\\\.\\RARP%s", adapterName);
    char openMsg[120];
    wsprintf(openMsg, "Opening %s", rarpDeviceName);
    logLine(mainWindow, openMsg);
    ntRarpHandle = CreateFile(rarpDeviceName,
                              GENERIC_READ | GENERIC_WRITE, 0, NULL,
                              OPEN_EXISTING,
                              FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                              NULL);
    if (ntRarpHandle != INVALID_HANDLE_VALUE) {
      logLine(mainWindow, "Opened RARP device");
      UINT sysVersion;
      DWORD bytesReturned;
      if (DeviceIoControl(ntRarpHandle, DIOC_RarpGetVersion,
                          NULL, 0, &sysVersion, sizeof(sysVersion),
                          &bytesReturned, NULL)) {
        if ((sysVersion < SYSVersionMin) || (sysVersion > SYSVersionMax)) {
          logLine(mainWindow, "Wrong version of RARP.SYS");
          CloseHandle(ntRarpHandle);
          ntRarpHandle = INVALID_HANDLE_VALUE;
        }
        else {
          char* msgBuf = new char[logLineLen];
          ostrstream msgOss(msgBuf, logLineLen);
          msgOss << "Using RARP.SYS version " <<
            (sysVersion / 100) << "." << setw(2) << setfill('0') <<
            (sysVersion % 100) << ends;
          logLine(mainWindow, msgBuf);
        }
      }
      else {
        logLine(mainWindow, "Can't get version of RARP.SYS");
        CloseHandle(ntRarpHandle);
        ntRarpHandle = INVALID_HANDLE_VALUE;
      }
    }
    else logLastError(mainWindow, "Opening RARP device");
  }
}

/*
  Can we find the subnet for this TCP parameters key among those excluded?
*/

long getSubnetMask(char* atcpParamsKeyName, HWND amainWindow)
{
  char buf[80];
  long addr;
  long result = 0;
  DWORD addrLength = sizeof(buf), maskLength = sizeof(buf);
  if (!getValue(atcpParamsKeyName, "IPAddress", (BYTE*)buf, addrLength,
                REG_MULTI_SZ, amainWindow)) {
    fail(amainWindow, "No IPAddr for TCP params key");
  }
  addr = inet_addr(buf);
  if (addr != 0) {              // static or BOOTP
    logLine(amainWindow, "...static IP"); // ***temp
    if (!getValue(atcpParamsKeyName, subnetMaskString, (BYTE*)buf, maskLength,
                  REG_MULTI_SZ, amainWindow)) {
      fail(amainWindow, "No Subnet Mask for TCP params key");
    }
    result = inet_addr(buf) & addr;
  }
  else {                        // DHCP
    addrLength = sizeof(buf);
    if (getValue(atcpParamsKeyName, "DhcpIPAddress", (BYTE*)buf, addrLength,
                  REG_SZ, amainWindow)) {
      addr = inet_addr(buf);
      logLine(amainWindow, "...DHCP IP"); // ***temp
      maskLength = sizeof(buf);
      if (!getValue(atcpParamsKeyName, dhcpSubnetMaskString,
                    (BYTE*)buf, maskLength, REG_SZ, amainWindow)) {
        fail(amainWindow, "No DHCP Subnet Mask for TCP params key");
      }
      result = inet_addr(buf) & addr;
    }
  }
  char maskMsg[80];             // ***temp
  in_addr mask;                 // ***temp
  mask.S_un.S_addr = result;    // ***temp
  sprintf(maskMsg, "...mask = %s", inet_ntoa(mask)); // ***temp
  logLine(amainWindow, maskMsg); // ***temp
  return result;
}

BOOL WorkerParams::isSubnetExcluded(char* atcpParamsKeyName)
{
  long mask = getSubnetMask(atcpParamsKeyName, mainWindow);
  for (size_t subnetNo = 0;
       (excludedSubnets[subnetNo]) != 0;
       subnetNo++) {
    if (excludedSubnets[subnetNo] == mask) {
      logLine(mainWindow, "...excluded");
      return TRUE;
    }
  }
  return FALSE;
}

/*
  Windows NT TCP Registry navigation, pre-NT 5:

  The "Bind" value of the Tcpip Linkage key has a series of strings of the
  form \Device\adaptername, where if adaptername begins with "NdisWan" it's
  a Dial-up pseudo-adapter.  We want one entry to remain.  If so, that
  adapter's Parameters\Tcpip key name is what we return.

  While we're at it we also allocate and fill adapterName so we can find
  the MAC address later.

  The string we return is the caller's responsibility to delete.
*/

char* WorkerParams::findNT4NetcardTCPParams()
{
  char* tcpLinkageKeyName = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\"
    "Services\\Tcpip\\Linkage";
  char* srvKeyPrefix = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\"
    "Services\\";
  char* tcpParamSuffix = "\\Parameters\\Tcpip";
  DWORD bindBufLen = 1000;
  char* bindBuf = new char[bindBufLen];
  if (!getValue(tcpLinkageKeyName, "Bind", (BYTE*)bindBuf, bindBufLen,
                REG_MULTI_SZ, mainWindow)){
    fail(mainWindow, "Registry failure");
  }
  size_t nTCPKeys = 0;          // Want to end up with 1
  char* result = "";
  for (CHAR* thisValue = bindBuf;
       *thisValue;
       thisValue += (strlen(thisValue) + 1)) {
    if (strstr(thisValue, "NdisWan")) continue; // Not interested in Dial-up!
    char* resultSave = result;
    result = new char[strlen(srvKeyPrefix) + strlen(keyNameTail(thisValue)) +
                     strlen(tcpParamSuffix) + 1];
    strcpy(result, srvKeyPrefix);
    strcat(result, keyNameTail(thisValue));
    strcat(result, tcpParamSuffix);
    if (isSubnetExcluded(result)) {
      delete[] result;
      result = resultSave;
      continue;
    }
    else if (++nTCPKeys > 1) break; // Bail out if more than one!
    else {
      adapterName = new char[strlen(keyNameTail(thisValue)) + 1];
      strcpy(adapterName, keyNameTail(thisValue));
    }
  }
  if (nTCPKeys > 1) fail(mainWindow,
                         "No unique Registry key for TCP/IP over LAN");
  else if (nTCPKeys == 0) fail(mainWindow,
                               "Can't find Registry key for LAN TCP/IP");
  else openntRarp();
  delete bindBuf;
  return result;
}

/*
  In Windows 2000 we resort to the overkill of issuing the MACADDR ioctl
  against the actual device because our Registry navigation still can't
  eliminate some ghost devices.  Probably the poorly documented Setup
  API will get us out of that; Win2K ipconfig appears to use it.
*/

BOOL WorkerParams::probeAdapter(char* anadapterName)
{
  char queryResult[512];
  char deviceName[80];
  BOOL result = FALSE;
  BOOL createdDevice = FALSE;   // resorted to DefineDosDevice?
  BOOL foundDevice = BOOL(QueryDosDevice(anadapterName, queryResult,
                                         sizeof(queryResult)));
  if ((!foundDevice) && (GetLastError() == ERROR_FILE_NOT_FOUND)) {
    strcpy(deviceName, "\\Device\\");
    strcat(deviceName, anadapterName);
    createdDevice = DefineDosDevice(DDD_RAW_TARGET_PATH, anadapterName,
                                    deviceName);
  }

  if (foundDevice || createdDevice) {
    char macFileName[80];
    strcpy(macFileName, "\\\\.\\");
    strcat(macFileName, anadapterName);
    HANDLE hMAC = CreateFile(macFileName, GENERIC_READ,
                             FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                             OPEN_EXISTING, 0, INVALID_HANDLE_VALUE);
    if (hMAC != INVALID_HANDLE_VALUE) {
      UCHAR addrData[80];
      ULONG ethAddrCode = 0x01010102;
      ULONG trAddrCode = 0x02010102;
      DWORD returnedCount = DWORD(-1); // impossible value
      BOOL ioctlOK = FALSE;       // assume failure
      DWORD queryIoctl = CTL_CODE(FILE_DEVICE_PHYSICAL_NETCARD, 0,
                                  METHOD_OUT_DIRECT, FILE_ANY_ACCESS);
      if (DeviceIoControl(hMAC, queryIoctl /* IOCTL_NDIS_QUERY_GLOBAL_STATS */,
                          &ethAddrCode, sizeof(ethAddrCode), addrData,
                          sizeof(addrData), &returnedCount, NULL)) {
        ioctlOK = TRUE;
      }
      else if (DeviceIoControl(hMAC, queryIoctl,
                               &trAddrCode, sizeof(trAddrCode), addrData,
                               sizeof(addrData), &returnedCount, NULL)) {
        ioctlOK = TRUE;
      }
      if (ioctlOK && (returnedCount == 6)) result = TRUE;
      else logLine(mainWindow, "... couldn't ioctl device"); // ***temp**
      if (createdDevice) {        // Get rid of it!
        DefineDosDevice(DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION |
                        DDD_EXACT_MATCH_ON_REMOVE, anadapterName, deviceName);
      }
      CloseHandle(hMAC);
    }
  }
  else logLine(mainWindow, "... couldn't query or define device"); // ***temp**
  if (result) logLine(mainWindow, "...probed OK"); // ***temp***
  return result;
}

/*
  Windows NT TCP Registry navigation, NT 5:

  We're interested in the network adapters enumerated below the key for
  the network adapter class, which in NT 5 involves a GUID.  For each one,
  we're only interested if it's bound above to TCP/IP and below to Ethernet
  or Token Ring, and we're only interested if there's one and only one
  meeting our criteria.  Then computing the keyname is easy after we open
  the corresponding RARP device.
*/

char* WorkerParams::findNT5NetcardTCPParams()
{
  char* result = "";
  char* netcardClassKeyName =
    "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Class\\"
    "{4D36E972-E325-11CE-BFC1-08002BE10318}";
  char* tcpParamsPrefix =
    "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\"
    "Parameters\\Interfaces\\";
  enum { KeyNameBufLen = 1000, ValueBufLen = 1000 };
  DWORD valueBufLen;
  char* valueBuf = new char[ValueBufLen];
  char* keyNameBuf = new char[KeyNameBufLen];
  size_t nTCPKeys = 0;          // Want to end up with 1
  HKEY netcardClassHandle = getKey(netcardClassKeyName, mainWindow);
  if (netcardClassHandle == NULL) {
    abortOnBadKey(netcardClassKeyName, mainWindow);
  }

  for (DWORD adapterNo = 0; ; adapterNo++) {
    char netcardSubKeyName[80];   // e.g. "0001"
    DWORD netcardSubKeyNameSize = sizeof(netcardSubKeyName);
    FILETIME lastUpdate;

    LONG rc = RegEnumKeyEx(netcardClassHandle, adapterNo, netcardSubKeyName,
                           &netcardSubKeyNameSize, NULL, NULL, NULL,
                           &lastUpdate);
    if (rc != ERROR_SUCCESS) break; // No more subkeys.

    strcpy(keyNameBuf, netcardClassKeyName);
    strcat(keyNameBuf, "\\");
    strcat(keyNameBuf, netcardSubKeyName); // e.g. "0000"
    logLine(mainWindow, keyNameBuf); // ***temp***
    size_t cardKeyNameLen = strlen(keyNameBuf); // length so far...
    valueBufLen = ValueBufLen;
    strcpy(keyNameBuf + cardKeyNameLen, "\\Ndi\\Interfaces");
    if (!keyExists(keyNameBuf, mainWindow)) continue; // not physical...
    // If our driver could handle ALL media we'd use Characteristics of x84
    // as our test of the physicality of the adapter.
    if (getValue(keyNameBuf, "LowerRange", (BYTE*)valueBuf, valueBufLen,
                 REG_SZ, mainWindow) &&
        ((!stricmp(valueBuf, "ethernet")) ||
         (!stricmp(valueBuf, "token ring")))) {
      logLine(mainWindow, "...is a physical netcard"); // ***temp***
      valueBufLen = ValueBufLen;
      keyNameBuf[cardKeyNameLen] = '\0';
      if (getValue(keyNameBuf, "NetCfgInstanceId", (BYTE*)valueBuf,
                   valueBufLen, REG_SZ, mainWindow)) {
        logLine(mainWindow, valueBuf); // ***temp***
        char* resultSave = result;
        result = new char[strlen(tcpParamsPrefix) +
                         strlen(keyNameTail(valueBuf)) + 1];
        strcpy(result, tcpParamsPrefix);
        strcat(result, keyNameTail(valueBuf));
#if 0                           // ***temp 1.10.1***
        HKEY tcpParamsKey = getKey(result, mainWindow);
        if (tcpParamsKey != NULL) {
          logLine(mainWindow, "...bound to TCP/IP");
          RegCloseKey(tcpParamsKey);
        }
        else {
          delete[] result;
          result = resultSave;
          continue;
        }
#endif
        valueBufLen = ValueBufLen;
        // Is the NTEContextList stuff needed?
        char physSignature[ValueBufLen]; // signature of physical netcard
        if (!getValue(result, "NTEContextList", (BYTE*)physSignature,
                      valueBufLen, REG_MULTI_SZ, mainWindow) ||
            isSubnetExcluded(result) ||
            !probeAdapter(keyNameTail(valueBuf))) {
          delete[] result;
          result = resultSave;
          continue;
        }
        else if (++nTCPKeys > 1) break;
        else {
          adapterName = new char[strlen(keyNameTail(valueBuf)) + 1];
          strcpy(adapterName, keyNameTail(valueBuf));
        }
      }
    }
  }

  RegCloseKey(netcardClassHandle);

  if (nTCPKeys > 1) fail(mainWindow,
                         "No unique Registry key for TCP/IP over LAN");
  else if (nTCPKeys == 0) fail(mainWindow,
                               "Can't find Registry key for LAN TCP/IP");

  else openntRarp();
  delete valueBuf;
  delete keyNameBuf;
  return result;
}

/*
  When the Registry fails us we try to get our IP address this way.
*/

UINT getWinsockLocalIp()
{
  UINT result = 0;
  WSAData wsaData;
  if (WSAStartup(MAKEWORD(1, 1), &wsaData) == 0) {
    char hostName[200];
    if (gethostname(hostName, sizeof(hostName)) == 0) {
      struct hostent *phe = gethostbyname(hostName);
      if (phe != NULL) {
        for (int i = 0; result == 0 && phe->h_addr_list[i] != 0; ++i) {
          struct in_addr& ipAddr =
            *((struct in_addr*)phe->h_addr_list[i]);
          result = ipAddr.s_addr;
        }
      }
    }
    WSACleanup();
  }
  return result;
}

UINT WorkerParams::ourIpAddress()
{
  UINT result = 0;
  char valueBuf[80];
  DWORD valueBufLen1 = sizeof(valueBuf), valueBufLen2 = sizeof(valueBuf);
  if (getValue(transportKeyName, "IPAddress", (BYTE*)valueBuf, valueBufLen1,
               transportValueType, mainWindow)) {
    if ((strlen(valueBuf) != 0) && strcmp(valueBuf, "0.0.0.0")) {
      result = inet_addr(valueBuf);
    }
  }
  else if (getValue(transportKeyName, "DhcpIPAddress", (BYTE*)valueBuf,
                    valueBufLen2, transportValueType, mainWindow)) {
    if ((strlen(valueBuf) != 0) && strcmp(valueBuf, "0.0.0.0")) {
      result = inet_addr(valueBuf);
    }
  }
  return (result ? result : getWinsockLocalIp()); // 10/12/03
}

/*
   Here we check if the machine we're running on really uses the Microsoft
   IP stack.  We also make sure it isn't set up for DHCP.
*/

BOOL WorkerParams::checkStack()
{
  transportKeyName = ((osMajorVersion < 5) ? findNT4NetcardTCPParams() :
     findNT5NetcardTCPParams());
  return BOOL(transportKeyName[0] != '\0');
}

/*
  Diagnostic dump of the driver
*/

void WorkerParams::dumpDriverMemory()
{
  if (os == WNT) {
    if (ntRarpHandle == INVALID_HANDLE_VALUE) {
      fail(mainWindow, "Driver dump requires RARP.SYS");
    }
    else {
      enum { BufSize = 2000 };
      BYTE* buf = new BYTE[BufSize];
      DWORD bytesReturned;
      if (DeviceIoControl(ntRarpHandle, DIOC_RarpDumpDriver,
                          NULL, 0, buf, BufSize,
                          &bytesReturned, NULL)) {
        dump(mainWindow, bytesReturned, buf, "Driver context:");
      }
      else logLine(mainWindow, "Can't dump RARP.SYS");
      delete buf;
    }
  }
  _endthread();
}

enum { ThreadStackSize = 8192 };

/*
   We use _beginthread() and _endthread() rather than the nicer CreateThread()
   etc. because Microsoft says using the latter in a program perverse enough
   to use the C runtime library causes a memory leak.  Whether or not that's
   a bug, to ignore the hint would be to punish Microsoft for its
   candor.  Wouldn't that be the wrong thing to punish them for?
*/

void worker(void* aparams)
{
  WorkerParams& params = *((WorkerParams*)aparams);
  RarpTbl rarpTbl(params.mainWindow);
  if (params.checkStack()) {
    if (params.dumpDriver) params.dumpDriverMemory();
    ReceiverParams receiverParams(params.mainWindow, params.rxTimeout,
                                  rarpTbl,
                                  params.ourIpAddress(), quiescing);
    HANDLE receiverHandle =
      HANDLE(_beginthread(rarpReceiver, ThreadStackSize, &receiverParams));
    while (!quiescing) Sleep(1000);
    logLine(params.mainWindow, "Quiescing...");
    WaitForSingleObject(receiverHandle, INFINITE);
  }
  SendMessage(params.mainWindow, WM_REALLY_CLOSE, 0, 0);
  _endthread();
}

int APIENTRY mainDialogProc(HWND hDlg, UINT message, UINT wParam,
                            LONG lParam)
{
  switch (message)
    {
    case WM_CLOSE:
      quiescing = TRUE;
      return 1;
    case WM_REALLY_CLOSE:
      writeLog(hDlg);
      EndDialog (hDlg, 0);
      DestroyWindow(hDlg);
      PostQuitMessage(0);
      return 1;
    default:
      break;
    }
  return DefWindowProc (hDlg, message, wParam, lParam);
}

/*
   Here we set up the GUI.  The use of ShowWindow() is probably inept, but
   it's the only way I could figure out that would allow the nCmdShow arg
   to WinMain() to coerce this program into running minimized if the user
   wants it that way.  The problem seems to be that nCmdShow is always
   the same (SW_SHOWDEFAULT == decimal 10) no matter what the Win95 Shortcut
   Run property is.  Am I missing something or what?

   Another idisyncrasy here is that we disable the Close item on the control
   menu if we're running under NT with the NT driver.  This is because we
   want to be sure any outstanding read gets successfully canceled so the
   driver can be dismissed.  This isn't appropriate, though, in case we want
   the user to see the window and dismiss it only after he's seen enough,
   so we make it an option.
*/

BOOL initGUI(HINSTANCE hInstance, HWND& amainWindow, int nCmdShow,
             BOOL allowClose)
{
  static char appName[] = "RARPD";
  WNDCLASS wndclass;
  wndclass.style = CS_HREDRAW | CS_VREDRAW;
  wndclass.lpfnWndProc = (WNDPROC)mainDialogProc;
  wndclass.cbClsExtra = 0;
  wndclass.cbWndExtra = DLGWINDOWEXTRA;
  wndclass.hInstance = hInstance;
  wndclass.hIcon = LoadIcon(hInstance, "ICON_3");
  wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
  wndclass.hbrBackground  = (HBRUSH)(COLOR_BTNFACE+1);
  wndclass.lpszMenuName = NULL;
  wndclass.lpszClassName = appName;
  RegisterClass(&wndclass);
  amainWindow = CreateDialog(hInstance, MAKEINTRESOURCE(DIALOG_MAIN), NULL,
                             (DLGPROC)mainDialogProc);
  if (amainWindow) {
#if 0
    if ((srvHandle != NULL) && !allowClose) {
      HMENU ctrlMenuH = GetSystemMenu(amainWindow, FALSE);
      if (ctrlMenuH != NULL) {
        EnableMenuItem(ctrlMenuH, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
      }
    }
#endif
    ShowWindow(amainWindow,
               ((nCmdShow == SW_SHOWDEFAULT) ? SW_SHOWMINIMIZED : nCmdShow));
    return TRUE;
  }
  else {
    char* complaint = "Couldn't create main window";
    logLastError(0, complaint);
    MessageBox(0, complaint, "rarpd", MB_ICONSTOP);
    return FALSE;
  }
}

/*
  Here we extract the /t<seconds> option, returning a millisecond value,
  or, failing that, return the default 30,000 milliseconds.
*/

DWORD getTimeout(char* acmdLine)
{
  DWORD result = 30000;
  if (acmdLine != NULL) {
    char* optionP = strstr(acmdLine, "/t");
    if (optionP == NULL) optionP = strstr(acmdLine, "/T");
    if (optionP != NULL) {
      int secs = atoi(optionP + 2);
      if (secs > 0) result = secs * 1000;
    }
  }
  return result;
}

/*
  We want to set the current directory to the one we were launched from.
  This can be tricky because if we were launched by the rarpd Launcher
  our full file path is encased in quotes in the command line.  Then we can
  open the logfile according to whether the user wants to overwrite any old
  logfile or append to it.  If we were launched from the command line
  without an explicit path, there's no need to set the current directory.
*/

void openLog(char* cmdLine)
{
  const char* progNameSentinels = (cmdLine[0] == '"') ? "\\\"" : "\\ ";
  char* lastBackslash = strchr(cmdLine, '\\');
  while (lastBackslash != NULL) {
    size_t nextSentinelLoc = strcspn(lastBackslash + 1, progNameSentinels);
    char sentinel = lastBackslash[nextSentinelLoc + 1];
    if (sentinel != '\\') break;
    lastBackslash += (nextSentinelLoc + 1);
  }
  char dirPath[MAX_PATH];
  if (lastBackslash != NULL) {
    char* cmdLineDirStart = cmdLine + strspn(cmdLine, "\"");
    size_t dirPathLen = lastBackslash - cmdLineDirStart;
    memcpy(dirPath, cmdLineDirStart, dirPathLen);
    dirPath[dirPathLen] = '\0';
    SetCurrentDirectory(dirPath);
  }
  if ((strstr(cmdLine, "/a") != NULL) || (strstr(cmdLine, "/A") != NULL)) {
    outFile.open("RARPD.LOG",ios::out | ios::app);
  }
  else outFile.open("RARPD.LOG", ios::out | ios::trunc);
  char dateStr[80], timeStr[80];
  GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL,
                "yyyy'-'MM'-'dd", dateStr, sizeof(dateStr));
  GetTimeFormat(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT, NULL,
                "HH':'mm':'ss", timeStr, sizeof(timeStr));
  outFile << "rarpd " VERSION " started " << dateStr << " " << timeStr
          << endl;
  outFile << "Command line: " << cmdLine << endl;
}

/*
   Here we marshall the options entered from the command line.  We also
   set the current directory to be the one from which the program was loaded.

   Originally we set the current directory so we could find the VxD even if
   we'd been started from the Registry key
   HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run.
   This reason became obsolete when RARP.vxd became an NDIS protocol
   installed so that it gets loaded by the OS from the system directory,
   but we left the logic in because it made rarpd.log much easier to
   find when a remote user sent email complaining that the program didn't
   work.
*/

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR /* lpCmdLine */, int nCmdShow)
{
  ourInstance = hInstance;
  HWND mainWindow = 0;          // our main window; 0 means not yet ready
  BOOL dumpDriver = FALSE;      // dump the driver?
  SubnetHolder excludedSubnets;
  char* cmdLine = GetCommandLine();
  if ((cmdLine != NULL) && (*cmdLine != '\0')) {
    openLog(cmdLine);
    strupr(cmdLine);
    dumpDriver = BOOL(strstr(cmdLine, "/D") != NULL);
    excludedSubnets.init(cmdLine);
  }
  else {
    MessageBox(0, "Can't get command line", "rarpd", MB_ICONSTOP);
    exit(-1);
  }
  configureForOS(nCmdShow); // Do this before any screen I/O!
  if ((!hPrevInstance) &&
      initGUI(hInstance, mainWindow, nCmdShow, dumpDriver)) {
    logLine(mainWindow, "RARPD Version "  VERSION);
    logLine(mainWindow, "Free software copyright (C) 1996-2003 Lew Perin");
    WorkerParams workerParams(mainWindow, dumpDriver,
                              getTimeout(cmdLine),
                              excludedSubnets);
    _beginthread(worker, ThreadStackSize, &workerParams);
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
      if (!IsDialogMessage(mainWindow, &msg)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    }
  }
  shutdown();
  return 0;
}

6 - Discussion autour de la documentation

Vous pouvez poser toutes vos questions, vos remarques et vos expériences à propos de l'entête RARP. Pour cela, rendez-vous sur le Forum "TCP-IP".

7 - Suivi du document

Le 02 mars 2009, par shamanyan, correction du paragraphe 3.4 en spécifiant que ipv6 = 16 et non pas 06

Le 09 juin 2006, par _SebF, création du document.
 



mot clé : reply vpn requete rarp ipv6 protocol rarp tous sur rarp _je ipv4 trame rarp negociation rarp adresse rarp 826 protocol address length adresse physique decouverte rarp an ethernet address resolution protocol target hardware address resolution rarp champs entete cache rarp entete rarp protocol type protocole rarp frame rarp address hardware address length protocol reponse rarp cache comprendre rarp tcpip adresse mac source protocole rarp address resolution protocol rfc 826 rfc ethernet liaison rarp voip mac 0800 datatgrame rarp sender internet address ip operation request tout sur rarp datagramme rarp hardware type rarp resolution sender hardware address adresse mac mac address connaitre rarp rarp -a

Copyright © 2011-2015 FrameIP TcpIP. Tous droits réservés. Les marques et marques commerciales mentionnées appartiennent à leurs propriétaires respectifs. L'utilisation de ce site Web TcpIP implique l'acceptation des conditions d'utilisation et du règlement sur le respect de la vie privée.
Sécurité entreprise Téléphonie entreprise Expert de votre Infrastructure Test ADSL Serinya Operateur Telecom