Winsock Tutorial von c-worker.ch (Fortgeschrittene Themen 1: RAW Sockets & ICMP)

Dieses Tutorial stammt von Georg Wicherski und ist teil der Seite www.c-worker.ch.
Es darf auf einer eigenen Seite veröffentlicht werden sofern es unverändert bleibt, der Autor benachrichtigt und die Seite www.c-worker.ch als Quelle erwähnt wird.
Für die Richtigkeit aller in diesem Dokument beschriebenen Angaben und für eventuelle Schäden die durch dieses Dokument entstehen könnten wird nicht gehaftet.

1. Einleitung
2. Was sind RAW Sockets
3. Das ICMP Protokoll
   a. Echo Request
   b. Echo Reply
   c. Destination Unreachable
   d. Source Quench
   e. Redirect
   f. Time Exceeded
   g. Parameter Problem
   h. Timestamp Request / Timestamp Reply
   i. Information Request / Information Reply
4. Einen RAW Socket erstellen
5. Nachrichten senden / empfangen
6. Praxisbeispiel

1. Einleitung

In den vorherigen Tutorials ging es weitestgehenst um TCP und UDP als Netzwerkprotokolle, jedoch existieren weitaus mehr Protokolle, wie zum Beispiel ICMP oder NetBios. Dieses Tutorial soll einen kleine Vorschau auf andere Protokolle neben TCP und UDP geben und zeigen wie man RAW Sockets verwendet, um weitere odere eigene Protokolle zu nutzen.

 

2. Was sind RAW Sockets

RAW Sockets sind Sockets die die Daten ohne Umschweife und vorherige Konvertierung lesen und schreiben können. Um den Unterschied zu verstehen, betrachtet man am besten, was die WinSock API dem Programm bei UDP oder TCP Sockets an Arbeit abnimmt.
Als erstes ist wichtig zu wissen, dass auch bei TCP einzelne Pakete verschickt werden, zus„tzlich gibt zu den Datenpaketen jedoch noch Statuspakete die dem Zielrechner Informationen über die Verbindung mitteilen. Damit der Zielrechner weiss, wie er das empfangene Paket zu bearbeiten hat, enth„lt jedes Datenpaket einen Header, d.h. einen Block mit Daten, der Informationen über das Paket enthählt. Das sind zum Beispiel Länge oder Typ des Pakets. Grundsätzlich enthält jedes via des IP-Protokoll versendeten Packetes (also auch TCP und UDP) auch einen IP Header, der unter anderem die Zieladresse und die Quelladresse enthält. Wenn man mittels TCP oder UDP Daten versendet, schreibt die WinSock API den IP Header von alleine, das ist bei RAW Sockets nicht der Fall. Das heisst, man muss den IP Header selber schreiben und auslesen, doch auch hier gibt es wieder Ausnahmen, wo die WinSock API und diese Arbeit abnimmt.

 

3. Das ICMP Protokoll

Auf das schon oben erwähnte ICMP Protokoll soll hier näher eingangen werden. Das Internet Protocol (IP) wird für Host zu Host Kommunikation in einem System von zusammengeschloßenen Netzwerken (zum Beispiel das Internet), Catenet genannt (siehe IEN 48), genutzt. Die Elemente, die zwei Netzwerke dieses Systems verbinden, werden Gateway genannt, diese kommunizieren wegen Kontrollfragen über das Gateway-to-Gateway Protocol (GGC). Gateway kann jeder Computer sein, der an zwei Netzwerke angeschloßen ist, da das ICMP schon im Kernel aller netzwerktauglichen Betriebssysteme implementiert ist und auch nicht abgeschaltet werden kann. Eine Ausnahme bilden hier Firewalls, welche die ICMP Pakete abblocken, bevor diese zum Kernel gelangen. Kommt es jedoch zu einem Fehler, der einen Host betrifft, wie zum Beispiel "Host Unreachable", würde es wenig Sinn machen, den zum fehlerverursachenden Host nächsten Gateway zu benachrichtigen. Für solche Gateway zu Host Verbindungen wurde das Internet Control Message Protocol (ICMP) eingeführt. Mittels des ICMP können auch zwei Hosts miteinander kommunizieren, um zum Beispiel die Verbindungsgeschwindigkeit festzustellen ("Ping"). Jedoch wurde das ICMP nicht entwickelt, um zuverläßig Daten zu übermitteln, vielmehr um ein Feedback zu liefern. Damit ein Host nicht mit Fehlermeldungen überhäuft wird, werden keine Statusmeldungen über ICMP Pakete gesendet. Weiterhin werden ICMP Pakete nur für das nullte Fragment eines Datagramms gesendet (betrf. Fragmentierung von Datagrammen, siehe RFC 791).
Das ICMP baut sich aus einem IP Header und einem Direkt folgendem ICMP Header auf. Der Protocol Wert des IP Header wird auf 1 für ICMP gesetzt, sonst gibt es an diesen IP Header nichts besonderes. Der darauf folgende ICMP Header baut sich wie folgt auf:

typedef struct
{
  unsigned char ucType; // ICMP Message Typ
  unsigned char ucCode; // Message Interner (Fehler-)Code
  unsigned short usCheckSum; // Checksum für das ICMP Paket
  unsigned short usID; // ID des Pakets, oft wie Port bei TCP / UDP genutzt
  unsigned short usSequence; // Sequenznummer, bei mehreren typgleichen, (sinn-)zusammenhängenden Paketen
  unsigned long ulTimeStamp; // Zeitstempel beim Abesenden
} icmp_header_t;

Die sogenannte Checksum ist ein Wert der zur Fehlerfeststellung innerhalb eines Datagramms fungiert. Er wird erstellt, in dem man das zu versendende Datagramm erstellt und die Checksum auf null stellt. Nun kann die eigentliche Checksum berrechnet werden, indem man als erstes einen unsigned short erstellt und in einem Schleiffendurchlauf mit dem aktuellen Wert des Puffers addiert. Nun wird der Zeiger auf den Puffer incrementiert. Wenn die Länge des Puffer ungerade ist (d.h. aus dem letztem byte kann kein short gebildet werden), wird eine 0 ans Ende angefügt. Am Ende wird sie mit ihrem 16-Bit Wert addiert. Da dies ein wenig schwierig ist zu verstehene, hier das ganze als Funktion. Das Wissen des berrechnens der Checksum ist nicht elementar, d.h. folgender Code kann einfach übernommen werden:

unsigned short Checksum(unsigned short *p_usBuffer, int iSize)
{
  unsigned long lCheckSum = 0;

  while(iSize > 1)
  {
    lCheckSum += *p_usBuffer++;
    iSize -= sizeof(unsigned short);
  }

  if(iSize)
    lCheckSum += *(unsigned char*)p_usBuffer; // + 0 entfällt, da überflüßig

  lCheckSum = ((lCheckSum >> 16) + (lChecksum & ffff)) + (lCheckSum >> 16);
  return (unsigned short)(~lCheckSum);
}

Hier nun eine Liste der Nachrichten die ICMP unterstüzt:

a. Echo Request

ucType: 8, ucCode: 0
Die Echo Request Nachricht wird an einen anderen Host oder Gateway gesendet, damit dieser die angehängten Daten unveräandert zurücksendet. Dies wird genutzt um einen Computer zu "pingen", d.h. die Zeit zu ermitteln, die ein Paket bestimmter größe zum Ziel-Host braucht. Nach dem Header folgen die Daten die zurückgesendet werden sollen.
Um beispielsweise einen Computer mit einem 32 byte großen Datenpaket zu pingen, sendet man den Header plus 32 - 12 (größe des ICMP Headers) = 20 byte beliebigen Daten. Ausgewertet wird die Zeit, indem man die Milisekunden vom Absenden des Packets bis zur Antwort misst. Von der Verwendung des Timestamp Feldes der ICMP Header ist dringend abzuraten, da dieses von der Systemzeit des Ziel-Host abhängig ist. Folgendes Beispiel: Ein User in Berlin / Deutschland pingt einen Computer in Shanghai / China. Die Differenz von Timestamp und Systemzeit ist negativ, da man in China mit der Zeit "in der Zukunft ist", das ganze wird jedoch mit unsigned longs berrechnet. Das hat zur Folge das eine Ping-Zeit von bis zu mehreren Jahren errechnet werden kann.

 

b. Echo Reply

ucType: 0, ucCode: 0
Hierbei handelt es sich lediglich um die Antwort auf eine Echo Request Message. Die Daten nach dem Header entsprechen den Daten nach dem Header der Echo Request Message.

 

c. Destination Unreachable

ucType: 3, ucCode: variabel
Die Destination Unreachable Message wird von einem Gateway gesendet, wenn ein Host, zu dem eine Nachricht gesendet wurde, nicht existiert, bzw. die Nachricht nicht zugestellt werden konnte. Die Destination Unreachable Message kann jedoch für die Codes 2 und 3 auch von einem Host gesendet werden. Die möglichen Werte für ucCode sind:

0 -Net Unreachable, das Netzwerk in dem sich der Ziel-Host befindet ist temporär nicht erreichbar oder zu weit vom Gateway entfernt.
1 -Host Unreachable, das Netzwerk in dem sich der Host befindet wurde gefunden, der Host selber jedoch nicht. Das kann der Fall sein, wenn ein Computer ausgeschaltet ist, oder eine dynamische IP (im Internet) nicht benutzt wird.
2 -Protocol Unreachable, das Protokoll welches verwendet wurde, ist auf dem Ziel-Host nicht verfügbar, viele Rechner unterstützen beispielsweise das IPX Protokoll nicht.
3 -Port Unreachable, der Port, der in einem vorherigem Datagram angegeben wurde, wird nicht unterstützt. Die WinSock API wird von dieser Nachricht zu einem Scheitern der connect() Methode veranlasst.
4 -Fragmentation Nedded and DF set, das Packet muss fragmentiert werden, jedoch ist das Dont Fragment Flag gesetzt (siehe RFC 791).
5 -Source Route failed

In allen Fällen sind die Daten nach dem ICMP Header der IP Header des ursprünglichen Datagramms und die ersten 64 Daten-Bytes. Die Destination Unreachable Message trägt ihren Namen eigentlich zu Unrecht, und sollte eher "Delivery Error Message" heissen. Sie wird nur als Antwort auf ein nicht ICMP Paket geschickt.

 

d. Source Quench

ucType: 4, ucCode: 0
Die Source Quench Message wird immer von einem Gateway gesendet, wenn er es aus Zeit- oder Speichergründen nicht schafft die Message zu verarbeiten. Der Source-Host sollte die Geschwindigkeit, mit der er sendet drosseln, bis er keine Source Quench Message mehr erhält. Nach einer Zeit kann er die Geschwindigkeit wieder steigern, bis er wieder Source Quench Messages erhält, usw. Dieses Drosseln der Geschwindigkeit ist im Win 9x Kernel bereits implementiert und muss vom Programm nicht implementiert werden.

 

e. Redirect

ucType: 5, ucCode: variabel, usID + usSequence + ulTimeStamp: anderweitig genutzt
Die Redirect Message wird an einen Host gesendet, wenn er bestimmte Nachrichten zukünftig über einen andern Gateway senden soll. Die Adresse des Gateways ist dann in usID, usSequence und ulTimeStamp enthalten. Mit folgendem Code kann sie ermittelt werden:

char *pGatewayAddress = &hdrICMPHeader.usID;
printf("New Gateway Address: %i.%i.%i.%i\n", (int) pGatewayAddress[0],
       (int) pGatewayAddress[1], (int) pGatewayAddress[2],
       (int) pGatewayAddress[3]);

Mögliche Werte für ucCode:

0 -Redirect Datagrams for the Network
1 -Redirect Datagrams for the Host
2 -Redirect Datagrams of the Type of Service and for the Network
3 -Redirect Datagrams of the Type of Service and for the Host

Auch dieses Packet enthält nach dem ICMP Header die Daten des ursprünglichen Datagramms und die ersten 64 Daten-Bytes. Der Kernel fängt auch diese Message ab, und ihr braucht euch eigentlich nicht drum zu kümmern.

 

f. Time Exceeded

ucType: 11, ucCode: 0 / 1
Sehr komplexes Thema, auch wenn es sich nicht so anhört. Kommt bald, mit der Erklärung was Fragmente sind.

 

g. Parameter Problem

ucType: 12, ucCode: 0, usID: variabel
Die Parameter Problem Message kann von einem Host oder Gateway kommen und zeigt an, dass im IP Header des ursprünglichen Pakets ein Fehler war, usID ist dann der Pointer auf die Stelle im IP Header, in der der Fehler aufgetreten ist. Folgender Code zeigt das Erkennen von Fehlern in IP Headern:

char* DetectError(char *szOldIPPacket, icmp_header_t hdrICMPHeader)
{
 if(hdrICMPHeader.ucType != 12)
  return 0;

 return szOldIPPacket + hdrICMPHeader.usID;
}

Jetzt kann man mit DetectError(szMyPacket, hdrResponseICMPHeader); den Wert für den fehlerhaften Parameter herausfinden.

 

h. Timestamp Request / Timestamp Reply

ucType: 13 / 14, ucCode: 0
Die Timestamp Request Message kann von jedem beliebigen Computer, an jeden anderen Computer gesendet werden, jedoch ist sie nahezu nutzlos, da sie von der Systemzeit des Computers abhängig ist. Nach RFC 792 (Internet Control Message Protocol / DARPA Internet Program) obliegt es dem Computer, in welcher Zeitform gesendet / geantwortet werden soll. Daher wird an dieser Stelle auch nicht weiter auf diese Nachricht eingeganden.

 

i. Information Request / Information Reply

ucType: 15 / 16, ucCode: 0
Mittlerweile überflüssige gewordene Nachricht, die wenigsten Gateways antworten noch darauf. Für weitere Informationen sollte RFC 792 konsultiert werden.

 

4. Einen RAW Socket erstellen

Um einen RAW Socket zu erstellen, muss ersteinmal wieder WinSock initialisiert werden, da dies schon im ersten Tutorial behandelt wurde, wird hier nicht nocheinmal darauf eingegangen. Einen RAW Socket erstellt man auch mittels der socket() Funktion:

SOCKET CreateRAWSocket(bool fAutoIPHeader)
{
 if(fAutoIPHeader)
  return socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
 else
  return socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
}

Diese Funktion erstellt einen einfachen RAW Socket. Doch was hat es mit dem Parameter fAutoIPHeader auf sich? Wie oben bereits erwähnt nimmt uns die WinSock API bei SOCK_STREAM oder SOCK_DGRAM das Schreiben des IP Headers und des UDP oder TCP Headers ab. Das erzeugen des IP Headers kann man sich auch bei anderen Protokollen, die der WinSock API bekannt sind, abenehmen lassen. Dazu wird als dritter Parameter der socket Funktion das Protokoll angegeben. Wenn man es sich allerding nicht nehmen lassen will, den IP Header selber zu schreiben, muss man anstatt des Protokolls IPPROTO_RAW übergeben. Wichtig ist es nur, im IP Header den richtigen Protokoll Code anzugeben. Da es den Rahmen dieses Tutorials sprengen würde, auf "echte" RAW Sockets einzugehen, werden wir uns im weiteren auf die Sockets beschränken, die ihren IP Header selbständig erzeugen. Doch auch hier ist Vorsicht geboten: wenn Daten via eines IPPROTO_ICMP Sockets verschickt werden, darf der IP Header nich in dem Daten-Puffer enthalten sein, beim empfangen ist er es jedoch!

Da es sich bei der recvfrom(...) Funktion um eine synchrone Funktion handelt, d.h. Die Funktion blockt bis Daten empfangen wurden, muss ein TimeOut gesetzt werden, damit sich das Programm nicht aufhängt, wenn keine mehr Daten kommen. Folgende Funktion setzt das TimeOut eines Sockets auf eine Bestimmte Zeit:

bool SetTimeOut(SOCKET sckSocket, unsigned int nTimeOut)
{
 unsigned int nMillis = nTimeOut;

 return setsockopt(sckSock, SOL_SOCKET, SO_RCVTIMEO, (char *) &nMillis, sizeof(nMillis)) != SOCKET_ERROR;
}

 

5. Nachrichten senden / empfangen

Da Ports teil des TCP / UDP Headers sind, kann ein RAW Socket nicht wissen, an welchen Port gesendet wird. Daher wird der sin_port Teil der SOCKADDR_IN Struktur ignoriert. Wenn man mittels eines RAW Sockets und einem Protokoll, das Ports unterstützt, an einen Bestimmten Port senden will, muss dieser im Protokoll spezifischen Header expliziet angegeben werden.

ICMP Pakete werden wie UDP Pakete mit der sendto Funktion verschickt, wobei die ersten 12 Daten-Bytes den Header representieren. Folgende Funktion schreibt den Header in eine Daten-Paket:

unsigned short WriteHeader(icmp_header_t hdrICMPHeader, // in
                        char *szData,                // in
                        unsigned short usDataLen,    // in
                        char *szPacket)              // out
{
 memcpy(szPacket, &hdrICMPHeader, sizeof(icmp_header_t));
 memcpy(szPacket + sizeof(icmp_header_t), szData, usDataLen);

 return usDataLen + sizeof(icmp_header_t);
}

Ein ICMP Paket kann jetzt einfach mittels sendto gesendet werden. Folgender Code zeigt ein Beispiel, welches ein einfaches ICMP Paket versendet. Die Antwort wird nicht verarbeitet. Der Code baut auf den oben beschriebenen Funktionen und Strukturen auf.

int main()
{
 char szData[20], szPacket[32]; // 32 = 20 + sizeof(icmp_header_t)
 unsigned short usPacketLen;
 icmp_header_t hdrICMPHeader;
 SOCKET sckSocket;
 SOCKADDR_IN saDestination;

 saDestination.sin_addr.s_addr = inet_addr("127.0.0.1"); // to local host
 saDestination.sin_family = AF_INET;
 saDestination.sin_port = 0; // unused

 hdrICMPHeader.ucType = 8; // ICMP Echo Request
 hdrICMPHeader.ucCode = 0;
 hdrICMPHeader.ulTimeStamp = GetTickCount(); // Millisekunden seit Systemstart
 hdrICMPHeader.usID = (unsigned short) GetProccessId(); // mit if(hdrICMPHeader.usID == GetProccessId()) kann jetzt geprüft werden, ob es sich um ein eigenes Paket handelt.
 hdrICMPHeader.usSequence = 0;
 hdrICMPHeader.usCheckSum = 0; // vor dem Berrechnen auf Null setzen

 szData = "c-worker.ch greets!";
 usPacketLen = WriteHeader(hdrICMPHeader, szData, 20, szPacket);
 ((icmp_header_t *) szPacket)->usCheckSum = CheckSum((unsigned short *) szPacket, usPacketLen);

 sckSocket = CreateRAWSocket(true);
 SetTimeOut(sckSocket, 1000); // 1 Sekunde TimeOut

 return (sendto(sckSocket, szPacket, usPacketLen, 0, (SOCKADDR *) &saDestination, sizeof(saDestination)) != SOCKET_ERROR ? 0 : 1);
}

Daten empfangen ist etwas komplexer, da wie oben erwähnt, man alle ICMP Packete die für den Rechner ankommen, auch ausgeliefert bekommt, da es ja Ports eigentlich nur bei TCP und UDP gibt. Ausserdem kommt hinzu, dass der IP Header geparst werden muss. Leider kann man ihn nich einfach überspringen, da er keine feste größe hat. Damit wir einen IP Header parsen können, brauchen wir ersteinmal die Struktur ip_header_t:

typedef struct
{
  unsigned int h_len:4; // Länge des Headers
  char szAdditionalData[23]; // Enthält weitere Daten, die uns jedoch nicht interessieren
} ip_header_t;

Jetzt können wir zu guter Letzt eine Funktion schreiben, die ein ICMP Paket empfängt.

bool ReceivePacket(SOCKET sckSocket, char *szPacket, size_t nMaxLength, icmp_header_t *pOutICMPHeader)
{
 char szBuffer[1024];
 ip_header_t *pIPHeader;
 icmp_header_t *pICMPHeader;
 SOCKADDR_IN saFrom;

 while(true)
 {
  int nResponseLen = 0;
  int nFromLen = sizeof(saFrom);

  nResponseLen = recvfrom(sckSocket, szBuffer, 1024, 0, (SOCKADDR *) &saFrom, &nFromLen);

  if(nResponseLen == SOCKET_ERROR)
   return false;

  pIPHeader = (ip_header_t *) szBuffer;
  pICMPHeader = (icmp_header_t *) (szBuffer + pIPHeader->h_len * 4); // h_len wird in doubleword's (int's) gespeichert, daher * 4

  if(pICMPHeader->ucID != GetProccessId()) // not our packet
   continue;

  memcpy(szPacket, szBuffer + pIPHeader->h_len * 4 + sizeof(icmp_header_t), min(nMaxLength, nResponseLen - pIPHeader->h_len * 4 - sizeof(icmp_header_t)));
  memcpy(pOutICMPHeader, pICMPHeader, sizeof(icmp_header_t));

  return true;
 }
}

 

6. Praxisbeispiel

#define ICMP_ECHOREPLY 0
#define ICMP_UNREACHABLE 3
#define ICMP_ECHO 8

#define ICMP_CODE_NETWORK_UNREACHABLE 0
#define ICMP_CODE_HOST_UNREACHABLE 1

#define ICMP_MIN_SIZE 8

#define STATUS_FAILED 0xFFFF

#define PING_RECVTIMEO 1000 //Wait max x / 1000 seconds
#define PING_FAILED 0xFFFFF0
#define PING_TIMEOUT 0xFFFFF1
#define PING_NOHOST 0xFFFFF2

typedef struct
{
  unsigned int   h_len:4;          // Länge des Headers
  unsigned int   version:4;        // IP Version
  unsigned char  tos;              // Type of service
  unsigned short total_len;        // Gesamt länge des Pakets
  unsigned short ident;            // unique identifier
  unsigned short frag_and_flags;   // flags
  unsigned char  ttl;              // TTL
  unsigned char  proto;            // Protokoll (TCP, UDP etc)
  unsigned short checksum;         // IP Checksumme
  unsigned int   sourceIP;         // Source IP
  unsigned int   destIP;           // Ziel IP
} ip_header_t;

typedef struct
{
  char           i_type;    // Type
  char           i_code;    // Code
  unsigned short i_cksum;   // Prüfsumme
  unsigned short i_id;      // Identifikatior
  unsigned short i_seq;     // Sequenz Nummer
  unsigned long  timestamp; // Um die benötigte Zeit zu messen
} icmp_header_t;


void WinsockStartup()
{
	WORD wVersionRequired = MAKEWORD(2, 2);
	WSADATA wdData;

	WSAStartup(wVersionRequired, &wdData);
}

unsigned short Checksum(unsigned short *buffer, int size)
{
  unsigned long cksum=0;
  while(size >1)
  {
    cksum += *buffer++;
    size -= sizeof(unsigned short);
  }

  if(size)
  {
    cksum += *(unsigned char*)buffer;
  }

  cksum = (cksum >> 16) + (cksum & 0xffff);
  cksum += (cksum >>16);
  return (unsigned short)(~cksum);
}

DWORD PingComputer(const char *szIP, unsigned int nDataSize)
{
	SOCKET sckSock;	
	SOCKADDR_IN saDest, saFrom;
	int nRecvTimeOut = PING_RECVTIMEO;
	nDataSize += sizeof(icmp_header_t);
	char *szICMPData = 0;
	char szRecvBuff[65000 + sizeof(ip_header_t) + sizeof(icmp_header_t)];
	DWORD dwStart, dwStop;

	WinsockStartup();

	if((sckSock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == INVALID_SOCKET)
		return PING_FAILED;

	if(setsockopt(sckSock, SOL_SOCKET, SO_RCVTIMEO, (char *) &nRecvTimeOut, sizeof(nRecvTimeOut)) == SOCKET_ERROR)
		return PING_FAILED;

	{
		unsigned long ulAddr = inet_addr(szIP);
		HOSTENT *pHostEntity = 0;

		if(ulAddr == INADDR_NONE)
		{
			pHostEntity = gethostbyname(szIP);
			memcpy(&ulAddr, pHostEntity->h_addr, pHostEntity->h_length);
		}

		saDest.sin_addr.s_addr = ulAddr;
		saDest.sin_family = (pHostEntity ? pHostEntity->h_addrtype : AF_INET);
		saDest.sin_port = 0;
	}

	{
		szICMPData = (char *) malloc(sizeof(icmp_header_t) + nDataSize);

		((icmp_header_t *) szICMPData)->i_cksum = 0;
		((icmp_header_t *) szICMPData)->i_code = 0;
		((icmp_header_t *) szICMPData)->i_id = (unsigned short) GetCurrentProcessId();
		((icmp_header_t *) szICMPData)->i_seq = 0;
		((icmp_header_t *) szICMPData)->i_type = ICMP_ECHO;

		memset(szICMPData + sizeof(icmp_header_t), '.', nDataSize);		
		memcpy(szICMPData + sizeof(icmp_header_t), "Georg rules", min(nDataSize, 11));
	}

	{
		((icmp_header_t *) szICMPData)->timestamp = GetTickCount();
		((icmp_header_t *) szICMPData)->i_cksum = Checksum((unsigned short *) szICMPData, nDataSize + sizeof(icmp_header_t));
	}

	if(sendto(sckSock, szICMPData, sizeof(icmp_header_t) + nDataSize, 0, (SOCKADDR *) &saDest, sizeof(saDest)) == SOCKET_ERROR)
		return PING_FAILED;

	dwStart = GetTickCount();

	while(true)
	{
		int iResponseLen = 0;

		{
			int nFromLen = sizeof(saFrom);

			iResponseLen = recvfrom(sckSock, szRecvBuff, 65000 + sizeof(ip_header_t) + sizeof(icmp_header_t), 0, (SOCKADDR *) &saFrom, &nFromLen);
			dwStop = GetTickCount();

			if(iResponseLen == SOCKET_ERROR)
			{
				if(WSAGetLastError() == WSAETIMEDOUT)
					return PING_TIMEOUT;
				else
					return PING_FAILED;
			}
		}

	
		{
			ip_header_t *hdrIPHeader = (ip_header_t *) szRecvBuff;
			icmp_header_t *hdrICMPHeader = (icmp_header_t *) (szRecvBuff + hdrIPHeader->h_len * 4);
			
			if(hdrICMPHeader->i_id != (unsigned short) GetCurrentProcessId())	//Someone else's packet
				continue;

			if(hdrICMPHeader->i_type == ICMP_UNREACHABLE)
				if(hdrICMPHeader->i_code == ICMP_CODE_HOST_UNREACHABLE || hdrICMPHeader->i_code == ICMP_CODE_NETWORK_UNREACHABLE)
					return PING_NOHOST;

			if(hdrICMPHeader->i_code == ICMP_ECHOREPLY)	//Ours...
				break;
		}
	}

	free(szICMPData);
	return dwStop - dwStart;
}

© 2002 by Georg Wicherski