用C++自己實現Ping程序
// // Ping.h //pragma pack(1)
define ICMP_ECHOREPLY 0
define ICMP_ECHOREQ 8
// IP Header -- RFC 791 typedef struct tagIPHDR { u_char VIHL; // Version and IHL u_char TOS; // Type Of Service short TotLen; // Total Length short ID; // Identification short FlagOff; // Flags and Fragment Offset u_char TTL; // Time To Live u_char Protocol; // Protocol u_short Checksum; // Checksum struct in_addr iaSrc; // Internet Address - Source struct in_addr iaDst; // Internet Address - Destination }IPHDR, *PIPHDR;
// ICMP Header - RFC 792 typedef struct tagICMPHDR { u_char Type; // Type u_char Code; // Code u_short Checksum; // Checksum u_short ID; // Identification u_short Seq; // Sequence //char Data; // Data }ICMPHDR, *PICMPHDR;
define REQ_DATASIZE 36
define REQ_datasize2 30
define Renum 66
// ICMP Echo Request typedef struct tagECHOREQUEST { ICMPHDR icmpHdr; DWORD dwTime; char cData[REQ_DATASIZE]; }ECHOREQUEST, PECHOREQUEST; typedef struct tempECHREQUEST{ ICMPHDR icmpHdr; DWORD dwTime; char cData[Renum]; }tempECHREQUEST,PtempECHOREQUEST; typedef struct tempECHREQUEST2{ ICMPHDR icmpHdr; DWORD dwTime; char cData[Renum+8]; }tempecho,Ptempecho; // ICMP Echo Reply typedef struct tagECHOREPLY { IPHDR ipHdr; ECHOREQUEST echoRequest; char cFiller[256]; }ECHOREPLY, PECHOREPLY; //IP echo request typedef struct tagIPEchorequest{ IPHDR ipHeader; ECHOREQUEST icmp_data; }IPECHOREQUET,PIPECHOREQUEST; typedef struct tagIPEchoTwo{ IPHDR ipHeader; char cData[REQ_datasize2]; }IPEchoTwo,PIPEchoTwo; typedef struct tagIPechoTwo2{ IPHDR ipHeader; char cData[REQ_datasize2+8]; }IPechotwo2,PIPechotwo2; //ip echo reply typedef struct tagIPREPLY{ IPHDR ipHdr; ECHOREQUEST echoRequest; char cFiller[256]; }IPECHOREPLY,PIPECHOREPLY;
pragma pack()
// PING.C -- Ping program using ICMP and RAW Sockets //
include <stdio.h>
include <stdlib.h>
include <winsock2.h>
include "ws2tcpip.h"
include <iostream>
pragma comment(lib,"WS2_32.lib")
include "ping.h"
// Internal Functions void Ping(LPCSTR pstrHost); void ReportError(LPCSTR pstrFrom); int WaitForEchoReply(SOCKET s); u_short in_cksum(u_short *addr, int len);
// ICMP Echo Request/Reply functions int SendEchoRequest(SOCKET, LPSOCKADDR_IN); DWORD RecvEchoReply(SOCKET, LPSOCKADDR_IN, u_char *);
//IP internal function void sendIP(LPCSTR pstrHost);
int SendIPEchoRequestFragmentTwo(SOCKET s,LPSOCKADDR_IN lpstToAddr); int SendIPEchoRequest(SOCKET, LPSOCKADDR_IN);
static short nId = 1; static short nSeq = 1; static short ip_id = rand()%256; static char DataToSend[Renum]; static short flag1 = 0; //標記,0標識不重復,1標識有重復 static short flag2 = 0; //標記,0標識順序,1標識逆序 char sourseIP[16]; // main() void main(int argc, char **argv) { WSADATA wsaData; WORD wVersionRequested = MAKEWORD(2,1); int nRet;
// Check arguments if (argc != 2) { fprintf(stderr,"nUsage: ping hostnamen"); return; } // Init WinSock如果成功,則返回0 nRet = WSAStartup(wVersionRequested, &wsaData); if (nRet) { fprintf(stderr,"nError initializing WinSockn"); return; } // Check version if (wsaData.wVersion != wVersionRequested) { fprintf(stderr,"nWinSock version not supportedn"); return; } printf(" n輸入目的主機IP地址:n"); gets(argv[1]); sendIP(argv[1]); system("pause"); // Free WinSock WSACleanup();}
// Ping() // Calls SendEchoRequest() and // RecvEchoReply() and prints results void Ping(LPCSTR pstrHost) { SOCKET rawSocket; //原始套接字 LPHOSTENT lpHost; //保存目的主機信息 struct sockaddr_in saDest; //目的主機 struct sockaddr_in saSrc; //源主機 DWORD dwTimeSent; //發送時間 DWORD dwElapsed; //時間間隔 u_char cTTL; //ttl int nLoop; //發送循環的ping的次數 int nRet;
// Create a Raw socket rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (rawSocket == SOCKET_ERROR) { ReportError("socket()"); return; } // Lookup host lpHost = gethostbyname(pstrHost); if (lpHost == NULL) { fprintf(stderr,"nHost not found: %sn", pstrHost); return; } // Setup destination socket address saDest.sin_addr.s_addr = *((u_long FAR *) (lpHost->h_addr)); saDest.sin_family = AF_INET; saDest.sin_port = 0; // Tell the user what we're doing printf("nPinging %s [%s] with %d bytes of data:n", pstrHost, inet_ntoa(saDest.sin_addr), REQ_DATASIZE); // Ping multiple times for (nLoop = 0; nLoop < 4; nLoop++) { // Send ICMP echo request SendEchoRequest(rawSocket, &saDest); // Use select() to wait for data to be received nRet = WaitForEchoReply(rawSocket); if (nRet == SOCKET_ERROR) { ReportError("select()"); break; } if (!nRet) { printf("nTimeOut"); break; } // Receive reply dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL); // Calculate elapsed time dwElapsed = GetTickCount() - dwTimeSent; printf("nReply from: %s: bytes=%d time=%ldms TTL=%d", inet_ntoa(saSrc.sin_addr), REQ_DATASIZE, dwElapsed, cTTL); } printf("n"); nRet = closesocket(rawSocket); if (nRet == SOCKET_ERROR) ReportError("closesocket()");} / 使用原始套接字編程,實現分片IP數據包的構造,能夠用兩個IP分片 構成ICMP ECHO請求,并接收另一方協議棧返回的ICMP應答。/ void sendIP(LPCSTR pstrHost){
SOCKET rawSocket; //原始套接字 LPHOSTENT lpHost; //保存目的主機信息 struct sockaddr_in saDest; //目的主機 struct sockaddr_in saSrc; //源主機 DWORD dwTimeSent; //發送時間 DWORD dwElapsed; //時間間隔 u_char cTTL; //ttl //int nLoop; //發送循環的ping的次數,這里是需要分片的片數目 int nRet; // Create a Raw socket創建IP數據包的套接字 rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP); if (rawSocket == SOCKET_ERROR) { ReportError("socket()"); return; } //設置套接字選項 BOOL blflag = true; int set_return = setsockopt(rawSocket, IPPROTO_IP, IP_HDRINCL, (char*)&blflag, sizeof(blflag)); if (set_return) { printf("設置選項失敗!%d",set_return); return; } if (set_return == SOCKET_ERROR) { printf("設置選項錯誤!"); return; } // Lookup host lpHost = gethostbyname(pstrHost); if (lpHost == NULL) { fprintf(stderr,"nHost not found: %sn", pstrHost); return; } printf("n輸入本機IP地址:n"); gets(sourseIP); // Setup destination socket address saDest.sin_addr.s_addr = *((u_long FAR *) (lpHost->h_addr)); saDest.sin_family = AF_INET; saDest.sin_port = 0; // Tell the user what we're doing printf("n發送數據包(2個分片第一片)到 %s --[%s] 數據大小是%d :n", pstrHost, inet_ntoa(saDest.sin_addr), REQ_DATASIZE); // Ping multiple times printf("n分片發送,不重疊輸入0,重疊輸入1:n"); std::cin>>flag1; printf("n分片發送,兩片順序發送輸入0,逆序發送輸入1:n"); std::cin>>flag2; if (flag2==0) { SendIPEchoRequest(rawSocket,&saDest); SendIPEchoRequestFragmentTwo(rawSocket,&saDest); } if (flag2==1) { SendIPEchoRequestFragmentTwo(rawSocket,&saDest); SendIPEchoRequest(rawSocket,&saDest); } // Use select() to wait for data to be received nRet = WaitForEchoReply(rawSocket); if (nRet == SOCKET_ERROR) ReportError("select()"); if (!nRet) printf("nTimeOut"); // Receive reply////////////////////////////////////////////// dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL); printf("n檢查收到的時間:%dn",dwTimeSent); // Calculate elapsed time dwElapsed = GetTickCount() - dwTimeSent; printf("n當前的時間 :%dn",GetTickCount()); printf("nReply from: %s: bytes=%d time=%ldms TTL=%d", inet_ntoa(saSrc.sin_addr), REQ_DATASIZE, dwElapsed, cTTL); printf("n"); nRet = closesocket(rawSocket); if (nRet == SOCKET_ERROR) ReportError("closesocket()");}
// SendEchoRequest() // Fill in echo request header // and send to destination int SendEchoRequest(SOCKET s,LPSOCKADDR_IN lpstToAddr) { static ECHOREQUEST echoReq;
static short nId = 1; static short nSeq = 1; int nRet;// Fill in echo request echoReq.icmpHdr.Type = ICMP_ECHOREQ; echoReq.icmpHdr.Code = 0; echoReq.icmpHdr.Checksum = 0; echoReq.icmpHdr.ID = nId++; echoReq.icmpHdr.Seq = nSeq++; // Fill in some data to send for (nRet = 0; nRet < REQ_DATASIZE; nRet++) echoReq.cData[nRet] = 'a'+nRet; // printf("nn%dnn",sizeof(echoReq)); // Save tick count when sent echoReq.dwTime = GetTickCount(); // Put data in packet and compute checksum echoReq.icmpHdr.Checksum = in_cksum((u_short *)&echoReq, sizeof(ECHOREQUEST)); // Send the echo request nRet = sendto(s, /* socket */ (LPSTR)&echoReq, /* buffer */ sizeof(ECHOREQUEST), 0, /* flags */ (LPSOCKADDR)lpstToAddr, /* destination */ sizeof(SOCKADDR_IN)); /* address length */ if (nRet == SOCKET_ERROR) ReportError("sendto()"); return (nRet);}
/發送第一片數據/ int SendIPEchoRequest(SOCKET s,LPSOCKADDR_IN lpstToAddr){
static IPECHOREQUET ipechoReq; //第一片的IP頭部處理過程 ipechoReq.ipHeader.VIHL = 0x45; //版本是4,首部長度是5 ipechoReq.ipHeader.TOS = 0x0; //由標準組織分配 ipechoReq.ipHeader.ID = ip_id; //ip的標識(一唯),這里一定要注意,ip標識不變,同一個數據包的兩片 ipechoReq.ipHeader.TTL = 64; //網絡中的最大壽命 ipechoReq.ipHeader.iaSrc.s_addr = inet_addr(sourseIP); //源主機地址 ipechoReq.ipHeader.iaDst = lpstToAddr->sin_addr; // ipechoReq.ipHeader.Protocol = IPPROTO_ICMP; //協議的類型包含ICMP ipechoReq.ipHeader.FlagOff = htons(0x2000); //第一片標志位是001000000000000 ipechoReq.ipHeader.TotLen = sizeof(IPECHOREQUET); //總產度設置成64,數據部分應該是52 ipechoReq.ipHeader.Checksum = 0; // //IP包頭校驗和計算 ipechoReq.ipHeader.Checksum = in_cksum((u_short *)&(ipechoReq.ipHeader), sizeof(IPHDR)); // Fill in echo request ipechoReq.icmp_data.icmpHdr.Type = ICMP_ECHOREQ; //請求數據包類型 ipechoReq.icmp_data.icmpHdr.Code = 0; // ipechoReq.icmp_data.icmpHdr.Checksum = 0; //檢驗和字段 ipechoReq.icmp_data.icmpHdr.ID = nId++; //成對出現的 ipechoReq.icmp_data.icmpHdr.Seq = nSeq++; //成對出現 // Save tick count when sent ipechoReq.icmp_data.dwTime = GetTickCount(); printf("n發送的時間:%dn",ipechoReq.icmp_data.dwTime); memset(DataToSend,0,Renum); //ICMP頭部填充//////////////////////////////////////////////////////////////////////////// int nRet; for (nRet = 0;nRet < REQ_DATASIZE;nRet++) DataToSend[nRet] = 'a'; for(nRet = REQ_DATASIZE;nRet<Renum;nRet++) DataToSend[nRet] = 'b'; //第一片需要發送的數據 for (nRet = 0;nRet < REQ_DATASIZE;nRet++) ipechoReq.icmp_data.cData[nRet] = DataToSend[nRet]; //計算校驗和的部分構成 if (flag1 == 0) { tempECHREQUEST tempecho; memset(tempecho.cData,0,Renum); tempecho.icmpHdr = ipechoReq.icmp_data.icmpHdr; tempecho.dwTime = ipechoReq.icmp_data.dwTime; memcpy(tempecho.cData,DataToSend,Renum); // Put data in packet and compute checksum ipechoReq.icmp_data.icmpHdr.Checksum = in_cksum((u_short *)&tempecho, sizeof(tempECHREQUEST)); } if (flag1==1) { tempecho temp; memset(temp.cData,0,Renum+8); temp.dwTime = ipechoReq.icmp_data.dwTime; temp.icmpHdr = ipechoReq.icmp_data.icmpHdr; memcpy(temp.cData,DataToSend,REQ_DATASIZE); //第一片復制 memcpy(temp.cData+REQ_DATASIZE,DataToSend+REQ_DATASIZE-8,REQ_datasize2); ipechoReq.icmp_data.icmpHdr.Checksum = in_cksum((u_short *)&temp, sizeof(tempecho)); } //發送echo 請求 nRet = sendto(s, (LPSTR)&ipechoReq, sizeof(IPECHOREQUET), 0, (LPSOCKADDR)lpstToAddr, sizeof(SOCKADDR_IN)); if (nRet == SOCKET_ERROR) ReportError("sendto()"); return nRet;} / 發送第二片數據/ int SendIPEchoRequestFragmentTwo(SOCKET s,LPSOCKADDR_IN lpstToAddr){ IPEchoTwo ipechoReq; //第二片的IP頭部處理過程
ipechoReq.ipHeader.VIHL = 0x45; //版本是4,首部長度是5 ipechoReq.ipHeader.TOS = 0x0; //由標準組織分配 ipechoReq.ipHeader.ID = ip_id; //ip的標識(一唯) ipechoReq.ipHeader.TTL = 64; //網絡中的最大壽命 ipechoReq.ipHeader.iaSrc.s_addr = inet_addr(sourseIP); //源主機地址 ipechoReq.ipHeader.iaDst = lpstToAddr->sin_addr; // ipechoReq.ipHeader.Protocol = IPPROTO_ICMP; //協議的類型包含ICMP ////////////////////////////////////////////////////////////////////////////////////// if (flag1 == 0) ipechoReq.ipHeader.FlagOff = htons(0x6); //第二片標志位是 if (flag1 == 1) ipechoReq.ipHeader.FlagOff = htons(0x5); //第二片標志位是 ipechoReq.ipHeader.TotLen = sizeof(IPEchoTwo); //總產度設置成64,數據部分應該是44 ipechoReq.ipHeader.Checksum = 0; // //IP包頭校驗和計算 ipechoReq.ipHeader.Checksum = in_cksum((u_short *)&(ipechoReq.ipHeader), sizeof(IPHDR)); //ICMP頭部填充 int nRet; //填充其他的數據部分///////////////////////////////////////////////////////////////////////////// if (flag1 == 0){ memcpy(ipechoReq.cData,DataToSend+REQ_DATASIZE,REQ_datasize2); nRet = sendto(s, (LPSTR)&ipechoReq, sizeof(IPEchoTwo), 0, (LPSOCKADDR)lpstToAddr, sizeof(SOCKADDR_IN)); } if (flag1 == 1){ IPechotwo2 ipecho; ipecho.ipHeader = ipechoReq.ipHeader; memset(ipecho.cData,0,REQ_datasize2+8); memcpy(ipecho.cData,DataToSend+REQ_DATASIZE-8,REQ_datasize2+8); nRet = sendto(s, (LPSTR)&ipechoReq, sizeof(IPechotwo2), 0, (LPSOCKADDR)lpstToAddr, sizeof(SOCKADDR_IN)); } //發送echo 請求 if (nRet == SOCKET_ERROR) ReportError("sendto()"); return nRet;} // RecvEchoReply() // Receive incoming data // and parse out fields DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL) { IPECHOREPLY ipreply; int nRet; int nAddrLen = sizeof(struct sockaddr_in);
// Receive the echo reply nRet = recvfrom(s, // socket (LPSTR)&ipreply, // buffer sizeof(IPECHOREPLY), // size of buffer 0, // flags (LPSOCKADDR)lpsaFrom, // From address &nAddrLen); // pointer to address len // Check return value if (nRet == SOCKET_ERROR) ReportError("recvfrom()"); // return time sent and IP TTL *pTTL = ipreply.ipHdr.TTL; return(ipreply.echoRequest.dwTime);}
// What happened? void ReportError(LPCSTR pWhere) { fprintf(stderr,"n%s error: %dn", WSAGetLastError()); }
// WaitForEchoReply() // Use select() to determine when // data is waiting to be read int WaitForEchoReply(SOCKET s) { struct timeval Timeout; fd_set readfds; /fd_set結構體的定義 typedef struct fd_set{ u_int fd_count; //集合中的socket數量 SOCKET fd_array[FD_SETSIZE]; //集合中包含的Socket數組 }fd_set; / readfds.fd_count = 1; readfds.fd_array[0] = s; Timeout.tv_sec = 5; Timeout.tv_usec = 0;
return(select(1, &readfds, NULL, NULL, &Timeout));}
// // Mike Muuss' in_cksum() function // and his comments from the original // ping program // // Author - // Mike Muuss // U. S. Army Ballistic Research Laboratory // December, 1983
/*
- I N _ C K S U M *
Checksum routine for Internet Protocol family headers (C Version) / u_short in_cksum(u_short addr, int len) { register int nleft = len; register u_short w = addr; register u_short answer; register int sum = 0;
/*
- Our algorithm is simple, using a 32 bit accumulator (sum),
- we add sequential 16 bit words to it, and at the end, fold
- back all the carry bits from the top 16 bits into the lower
16 bits. / while( nleft > 1 ) { sum += w++; nleft -= 2; }
/ mop up an odd byte, if necessary / if( nleft == 1 ) { u_short u = 0;
(u_char )(&u) = (u_char )w ; sum += u; }
/*
- add back carry outs from top 16 bits to low 16 bits
/
sum = (sum >> 16) + (sum & 0xffff); / add hi 16 to low 16 /
sum += (sum >> 16); / add carry /
answer = ~sum; / truncate to 16 bits */
return (answer);
}</pre>