|
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. 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.
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.
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). typedef struct
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: ucType: 8, ucCode: 0
ucType: 0, ucCode: 0
ucType: 3, ucCode: variabel
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.
ucType: 4, ucCode: 0
ucType: 5, ucCode: variabel, usID + usSequence + ulTimeStamp: anderweitig genutzt 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:
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.
ucType: 11, ucCode: 0 / 1
ucType: 12, ucCode: 0, usID: variabel 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
i. Information Request / Information Reply ucType: 15 / 16, ucCode: 0
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
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;
}
}
#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 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||