实验七 基于ICMP的简单程序实现
【实验目的】
⑴学习套接字编程的基本流程; ⑵理解ICMP的工作原理; 【实验环境】
电脑、VC++ 6.0编程环境 【实验重点及难点】
重点:使用原始套接字编写程序; 难点:使用原始套接字编写程序。 【实验内容】 1、基础知识
⑴ ICMP的作用:ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。关于ICMP报文类型可参考维基百科。
⑵ IP的头部及ICMP头部、数据结构定义 IP头部:
typedef struct _iphdr {
//Suppose the BYTE_ORDER is LITTLE_ENDIAN unsigned int h_len:4; // 头部长度 unsigned int version:4; // IP的版本号 unsigned char tos; // 服务类型 unsigned short total_len; // 报长度 unsigned short id; // 标识符 unsigned short frag_offset; // 分片偏移 unsigned char ttl; // 生存时间
unsigned char protocol; // 上层协议 (TCP, UDP 等) unsigned short checksum; // IP 校验和 unsigned int sourceIP; // 源 IP unsigned int destIP; // 目标 IP } IpHeader;
//选项部分
typedef struct _ipoptionhdr {
unsigned char code; // 选项类型 unsigned char len; // 头部可选长度 unsigned char ptr; // 偏移量
unsigned long addr[9]; // 记录经过的IP用作路由 } IpOptionHeader;
ICMP头部:
typedef struct _icmphdr {
BYTE i_type; //ICMP类型
BYTE i_code; //ICMP code 字段 USHORT i_cksum; //ICMP 校验和 USHORT i_id; USHORT i_seq; ULONG timestamp; } IcmpHeader;
2、阅读程序,完成以下题目:
⑴ 画出程序的流程图,并解释每个模块的含义;
⑵ 运行程序,使用抓包工具捕获发送的数据包,并分析数据与程序对照。
⑶ 参照实验指导书实验12 ICMP协议应用中的设置ttl值的代码,将该程序修改为一个模拟tracert程序。
3、附录:
/*该程序使用原始套接字编写,默认ping本身IP地址,可以再VC6中设置参数 *格式为 ping www.sina.com.cn,也是可以使用-r选项,使用IP头部的选项字段记录 *经过的路由。 */
//#include \"stdafx.h\" #include // IP头部的结构体 typedef struct _iphdr { //Suppose the BYTE_ORDER is LITTLE_ENDIAN unsigned int h_len:4; // 头部长度 unsigned int version:4; // IP的版本号 unsigned char tos; // 服务类型 unsigned short total_len; // 报长度 unsigned short id; // 标识符 unsigned short frag_offset; // 分片偏移 unsigned char ttl; // 生存时间 unsigned char protocol; // 上层协议 (TCP, UDP 等) unsigned short checksum; // IP 校验和 unsigned int sourceIP; // 源 IP unsigned int destIP; // 目标 IP } IpHeader; #define ICMP_ECHO 8 #define ICMP_ECHOREPLY 0 #define ICMP_MIN 8 // ICMP包头部最小长度 // ICMP 头部结构体 // This is not the standard header, but we reserve space for time typedef struct _icmphdr { BYTE i_type; //ICMP类型 BYTE i_code; //ICMP code 字段 USHORT i_cksum; //ICMP 校验和 USHORT i_id; USHORT i_seq; ULONG timestamp; } IcmpHeader; // IP 头部的选项部分 typedef struct _ipoptionhdr { unsigned char code; // 选项类型 unsigned char len; // 头部可选长度 unsigned char ptr; // 偏移量 unsigned long addr[9]; // 记录经过的IP用作路由 } IpOptionHeader; #define DEF_PACKET_SIZE 32 // 包的默认长度 #define MAX_PACKET 1024 // 最大长度 #define MAX_IP_HDR_SIZE 60 // IP头部的最大长度 BOOL bRecordRoute; //是否记录路由 int datasize; //数据长度 char *lpdest; // 使用帮助函数 void usage(char *progname) { printf(\"usage: ping -r printf(\" host remote machine to ping \"); printf(\" datasize can be up to 1KB \"); ExitProcess(-1); } // 用于填充ICMP的头部 void FillICMPData(char *icmp_data, int datasize) { IcmpHeader *icmp_hdr = NULL; char *datapart = NULL; icmp_hdr = (IcmpHeader*)icmp_data; icmp_hdr->i_type = ICMP_ECHO; // ICMP 回送请求 类型为 8 icmp_hdr->i_code = 0; // code 为 0 icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); //填充当前进程号,用于返回响应的标示 icmp_hdr->i_cksum = 0; icmp_hdr->i_seq = 0; datapart = icmp_data + sizeof(IcmpHeader); // 使用字符填充数据包 memset(datapart, 'E' , datasize - sizeof(IcmpHeader)); } // Function: checksum // 计算校验和 USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while(size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if(size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); } // Function: DecodeIPOptions // 解析IP头部的选项部分,由于路径的路由记录 void DecodeIPOptions(char *buf, int bytes) { IpOptionHeader *ipopt = NULL; IN_ADDR inaddr; int i; HOSTENT *host = NULL; ipopt = (IpOptionHeader *)(buf + 20); printf(\"Record Route: \"); for(i = 0; i < (ipopt->ptr / 4) - 1; i++) { inaddr.S_un.S_addr = ipopt->addr[i]; if (i != 0) printf(\" \"); host = gethostbyaddr((char *)&inaddr.S_un.S_addr, sizeof(inaddr.S_un.S_addr), AF_INET); if (host) printf(\"(%-15s) %s \ else printf(\"(%-15s) \ } printf(\"\\n\"); return; } // Function: DecodeICMPHeader // 从返回的IP数据中解析出ICMP的头部数据 void DecodeICMPHeader(char *buf, int bytes, struct sockaddr_in *from) { IpHeader *iphdr = NULL; IcmpHeader *icmphdr = NULL; unsigned short iphdrlen; DWORD tick; static int icmpcount = 0; iphdr = (IpHeader *)buf; // 头部字段乘以4 iphdrlen = iphdr->h_len * 4; tick = GetTickCount(); if((iphdrlen == MAX_IP_HDR_SIZE) && (!icmpcount)) DecodeIPOptions(buf, bytes); if(bytes < iphdrlen + ICMP_MIN) { printf(\"Too few bytes from %s \\n\ } icmphdr = (IcmpHeader*)(buf + iphdrlen); if(icmphdr->i_type != ICMP_ECHOREPLY) // echo type must be 0 { printf(\"non-echo type %d recvd \\n\ return; } // 检查该返回的数据是否为上述程序发送的数据的响应 // In FillICMPData function we assign the i_id is GetCurrentProcessId() if(icmphdr->i_id != (USHORT)GetCurrentProcessId()) { printf(\"someone else's packet! \\n\"); return ; } printf(\"%d bytes from %s \ printf(\"icmp_seq = %d. \ printf(\"time: %d ms\ printf(\"\\n\"); icmpcount++; return; } // Function: ValidateArgs // 检查输入的参数是否合法 void ValidateArgs(int argc, char **argv) { bRecordRoute = FALSE; lpdest = NULL; datasize = DEF_PACKET_SIZE; for(int i = 1; i < argc; i++) { if((argv[i][0] == '-') || (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'r': // 记录经过的路由 ping -r dns.chzu.edu.cn bRecordRoute = TRUE; break; default: usage(argv[0]); break; } } else if(isdigit(argv[i][0])) datasize = atoi(argv[i]); else lpdest = argv[i]; } } // // Function: main // 创建ICMP 原始套接字,创建ICMP头部,添加合适的IP头部选项,发送ICMP数据包 // 并等待响应,在等待响应式设置一个计时器,超时将放回。否则,对收到的数据包 // 进行解析。 int main(int argc, char **argv) { WSADATA wsaData; SOCKET sockRaw = INVALID_SOCKET; struct sockaddr_in dest, from; int bread, fromlen = sizeof(from), timeout = 1000, ret; char icmp_data[MAX_PACKET], recvbuf[MAX_PACKET]; unsigned int addr = 0; USHORT seq_no = 0; struct hostent *hp = NULL; IpOptionHeader ipopt; if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf(\"WSAStartup() failed: %d \\n\ return -1; } // 检查用户输入的参数是否有效 ValidateArgs(argc, argv); // 使用 IPPROTO_ICMP 创建一个原始套接字 sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); //sock创建失败 if(sockRaw == INVALID_SOCKET) { printf(\"WSASocket() failed: %d \\n\ return -1; } // bRecordRoute=true表示需要记录经过的路由 if(bRecordRoute) { // 为每一个ICMP包设置IP头部的选项部分。 memset(&ipopt,0,sizeof(ipopt)); ipopt.code = IP_RECORD_ROUTE; // 设置记录路由选项 ipopt.ptr = 4; // Point to the first addr offset ipopt.len = 39; // 选项长度 ret = setsockopt(sockRaw, IPPROTO_IP, IP_OPTIONS, (char *)&ipopt, sizeof(ipopt)); if (ret == SOCKET_ERROR) { printf(\"setsockopt(IP_OPTIONS) failed: %d \\n\ return -1; } } // 设置发送或接收的超时时间 timeout = 1000; bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); if(bread == SOCKET_ERROR) { printf(\"setsockopt(SO_RCVTIMEO) failed: %d \\n\ return -1; } timeout = 1000; bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)); if(bread == SOCKET_ERROR) { printf(\"setsockopt(SO_SNDTIMEO) failed: %d \\n\ return -1; } // 根据需要解析域名为IP memset(&dest, 0, sizeof(dest)); dest.sin_family = AF_INET; if((dest.sin_addr.s_addr = inet_addr(lpdest)) == INADDR_NONE) { if((hp = gethostbyname(lpdest)) != NULL) { memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length); dest.sin_family = hp->h_addrtype; printf(\"dest.sin_addr = %s \\n\ } else { printf(\"gethostbyname() failed: %d \\n\ return -1; } } // 创建ICMP包 datasize += sizeof(IcmpHeader); memset(icmp_data,0,MAX_PACKET); // 填充ICMP包 FillICMPData(icmp_data,datasize); // 开始发送或者接收ICMP包 while(1) { static int nCount = 0; int bwrote; //设置发送ICMP包的数量,该程序设置为4个包 if(nCount++ == 4) break; ((IcmpHeader*)icmp_data)->i_cksum = 0; ((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); ((IcmpHeader*)icmp_data)->i_seq = seq_no++; ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize); bwrote = sendto(sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest)); if(bwrote == SOCKET_ERROR) { if(WSAGetLastError() == WSAETIMEDOUT) { printf(\"timed out \\n\"); continue; } printf(\"sendto() failed: %d \\n\ return -1; } if(bwrote < datasize) { printf(\"Wrote %d bytes \\n\ } bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen); if(bread == SOCKET_ERROR) { if(WSAGetLastError() == WSAETIMEDOUT) { printf(\"timed out \\n\"); continue; } printf(\"recvfrom() failed: %d \\n\ return -1; } DecodeICMPHeader(recvbuf, bread, &from); Sleep(1000); } // Socket清理 if (sockRaw != INVALID_SOCKET) closesocket(sockRaw); WSACleanup(); return 0; } 因篇幅问题不能全部显示,请点此查看更多更全内容