计算机网络做了一个附加题,用C语言Raw_Socket实现ping指令。
通过本部的Mooc学习了一下Socket编程,然后成功写了出来orz
先放一下代码:
#include <stdio.h> #include <winsock2.h> #include <time.h> #include <iostream> #include <windows.h> #include <Mmsystem.h> #pragma comment(lib,"winmm.lib") #pragma comment(lib,"ws2_32.lib") using namespace std; const int IP_Header_min_lenght=20; /* Ver 4 Hlen 4 1 Servicetype 8 1 Total length 16 2 Identifier 16 2 flag and frag offset 16 2 TimeToLive 8 1 Prorocol 8 CHECKSUM 16 SRC ADDR 32 DES ADDR 32 160/8=20 */ typedef struct icmp_hdr { unsigned char icmp_type; // 类型 unsigned char icmp_code; // 代码 unsigned short icmp_checksum; // 校验和 unsigned short icmp_id; // ID号,设为PID unsigned short icmp_sequence; // 序列号 unsigned long icmp_timestamp; // 时间戳 } ICMP_HDR, *PICMP_HDR; unsigned short checksum(USHORT* buff, int size) { unsigned long cksum = 0; while(size>1) { cksum += *buff++; size -= sizeof(USHORT); } // 是奇数 if(size) { cksum += *(UCHAR*)buff; } // 将32位的chsum高16位和低16位相加,然后取反 cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (USHORT)(~cksum); } bool SetTimeout(SOCKET sRaw, int nTime) { int ret = setsockopt(sRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTime, sizeof(nTime)); return ret != SOCKET_ERROR; } double GetTickCountA() { __int64 Freq = 0; __int64 Count = 0; if(QueryPerformanceFrequency((LARGE_INTEGER*)&Freq)&& Freq > 0&& QueryPerformanceCounter((LARGE_INTEGER*)&Count)) { //乘以1000,把秒化为毫秒 return (double)Count / (double)Freq * 1000.0; } return 0.0; } int main() { char website_name[]="www.google.com";// www.baidu.com www.google.com char *DestIp; double succ=0; double fail=0; int min_RTT=999999999; int max_RTT=-1; int sum_RTT=0; DestIp = (char*)malloc(20*sizeof(char)); //初始化WindowsSocketsAPI WSADATA wsaData; WORD sockVersion = MAKEWORD(2,2);//声明版本 2.2 WSAStartup(sockVersion, &wsaData);//启动 //获取IP地址 struct hostent *curr_hostent = gethostbyname(website_name); DestIp = inet_ntoa( *(struct in_addr*)curr_hostent->h_addr_list[0] ); //DestIp = "192.168.88.1"; printf("正在ping %s:[%s] \n",website_name,DestIp); //printf("%s\n",curr_hostent->h_addr_list[0]); //printf("%s\n",inet_ntoa(*(struct in_addr*)curr_hostent->h_addr_list[0])); if(!curr_hostent){ puts("Get IP address error!"); //system("pause"); // exit(0); } //创立套接字 SOCKET sRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);//AF_INET=ipv4 SOCK_RAW ICMP协议 SetTimeout(sRaw, 1000); // 设置目的地址 SOCKADDR_IN dest; dest.sin_family = AF_INET;//ipv4地址家族 dest.sin_port = htons(0);//将整型变量从主机字节顺序转变成网络字节顺序 dest.sin_addr.S_un.S_addr = inet_addr(DestIp); // 创建ICMP数据包并填写 char buff[sizeof(ICMP_HDR) + 32]; ICMP_HDR* pIcmp = (ICMP_HDR*)buff; pIcmp->icmp_type = 8; // 请求一个ICMP回显 pIcmp->icmp_code = 0; pIcmp->icmp_id = (unsigned short)GetCurrentProcessId(); unsigned short nSeq = 0; char recvBuf[1024]; SOCKADDR_IN from; int nLen = sizeof(from); for(int o=0;o<10;o++) { int nRet; pIcmp->icmp_checksum = 0;//为了后续checksum函数正常 需要先置位0 pIcmp->icmp_timestamp = GetTickCountA(); pIcmp->icmp_sequence = nSeq++; pIcmp->icmp_checksum = checksum((unsigned short*)buff, sizeof(ICMP_HDR) + 32); //发送包 nRet = sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest));//发送 if(nRet == SOCKET_ERROR) { printf(" sendto() failed: %d \n", ::WSAGetLastError()); return -1; } //接收包 nRet = recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen); if(nRet == SOCKET_ERROR) { if(WSAGetLastError() == WSAETIMEDOUT) { printf(" Request timed out\n"); fail++; continue; } fail++; printf(" Failed. Error code: %d\n", WSAGetLastError()); return -1; } // 解析包 int nTick = GetTickCountA(); if(nRet < IP_Header_min_lenght + sizeof(ICMP_HDR)) { printf(" Returned too few bytes from %s \n", inet_ntoa(from.sin_addr)); fail++; } // 接收包中包含IP头,所以加20得到ICMP头 ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + IP_Header_min_lenght); // (ICMP_HDR*)(recvBuf + sizeof(IPHeader)); if(pRecvIcmp->icmp_type != 0) // 回显 { if(pRecvIcmp->icmp_type == 3) // 回显 { printf(" Unreachable\n");fail++; } else if(pRecvIcmp->icmp_type == 4) // 回显 { printf(" Origin suppression \n");fail++; } else if(pRecvIcmp->icmp_type == 5) // 回显 { printf(" Redirect \n");fail++; } else if(pRecvIcmp->icmp_type == 8) // 回显 { printf(" Echo request\n");fail++; } else if(pRecvIcmp->icmp_type == 9) // 回显 { printf(" Router announcement\n");fail++; } else if(pRecvIcmp->icmp_type == 10) // 回显 { printf(" Router request \n");fail++; } else if(pRecvIcmp->icmp_type == 11) // 回显 { printf(" Time out \n");fail++; } else if(pRecvIcmp->icmp_type == 17) // 回显 { printf(" Address subnet request\n");fail++; } else if(pRecvIcmp->icmp_type == 18) // 回显 { printf(" Address subnet response\n");fail++; } return -1; } succ++; printf(" 来自 %s的回复: ", inet_ntoa(from.sin_addr)); printf(" 字节=%d bytes :", nRet, inet_ntoa(from.sin_addr)); printf(" 时间= %d ms", nTick - pRecvIcmp->icmp_timestamp); printf(" TTL= %d \n", *(recvBuf+8)); min_RTT=min(min_RTT,(int)(nTick - pRecvIcmp->icmp_timestamp)); max_RTT=max(max_RTT,(int)(nTick - pRecvIcmp->icmp_timestamp)); sum_RTT=sum_RTT+nTick - pRecvIcmp->icmp_timestamp; Sleep(1000);//一秒 } printf("数据包丢失率:%lf",fail*100/(succ+fail)); cout<< "%"<<endl; printf("最小 RTT = %d ms \n", min_RTT); printf("最大 RTT = %d ms\n", max_RTT); printf("平均 RTT = %d ms\n", sum_RTT/10); return 0; }
还是算比较基础的吧,注释里面该有的都有啦~
主要就是socket编程和IP数据报的知识。
对于socket编程:我在编写raw_socket抓包程序时去听了本部计算机网络的MOOC,其中一章就是关于socket编程的。其中讲了声明winsocket版本、建立套接字、发送与接受等等等等函数。在编写程序时我们找到对应的函数调用即可。这里我有一个之前误解的地方:我原以为是gethostbyname将站点名翻译为32位点分地址,但在实际编写程序并调试后才发现是inet_ntoa的功劳。在了解了调用socket函数创立套接字、用gethostbyname和inet_ntoa获得ip地址、用sendto和recvfrom收发包后,接下来的工作就是填写数据包了。
我们需要写的是一个IP数据报,这与在创建socket时填写的AF_INET参数相一致。他在IP数据报的首部后的数据部分即为ICMP报文,中间有六个项,在接下来的核心代码注解中详细展示。我们需要创建一个ICMP报文大小+IC数据报头大小的缓冲区,填写每一段的内容后发送。这里有一个细节时为了正确计算校验和,需要先将其置零并填写完其余所有数据后再计算。在接受到返回的数据后,用与抓包程序类似的解析方式即可。
再放一些查资料找到的东西备用吧:
sin_zero用于填充结构体使之保持与struct sockaddr一样的长度。
(从onenote复制下来是张图片...
感谢本部mooc拯救我于苦海之中
原文:https://www.cnblogs.com/mumujzl/p/12069816.html