socket实现Ping命令

来源:互联网 发布:简阳市环保局何知云 编辑:程序博客网 时间:2024/05/22 08:09

实现的原理还是很简单的,主要还是要对ICMP协议有所了解。ICMP协议是在IP协议的数据部分实现的,普通的socket只能建立TCP或者UDP连接,实在传输层上做东西,只能控制要传输的数据,不能控制IP包的数据部分(即ICMP包实现的部分),所以我们需要一个原始套接字填充IP协议的数据部分。

#define WIN32_LEAN_AND_MEAN#include "stdafx.h"#include <winsock2.h>#include <ws2tcpip.h>#include <stdio.h>#include <stdlib.h>#pragma comment(lib,"ws2_32.lib")#define IP_RECORD_ROUTE  0x7#define ICMP_MIN 8#define ICMP_ECHOREPLY 0#define ICMP_ECHO 8#define DEF_PACKET_SIZE  32        // Default packet size#define MAX_PACKET       1024      // Max ICMP packet size#define MAX_IP_HDR_SIZE  60        // Max IP header size w/optionstypedef struct iphdr {unsigned int h_len : 4;// 包头长度,冒号4表示强制只占四个位unsigned int version : 4;// IP协议版本unsigned char tos;// 服务类型(TOS)unsigned short total_len;// 包的总长度unsigned short ident;// 包的唯一标识unsigned short frag_and_flags;// 标识unsigned char ttl;// 生存时间(TTL)unsigned char proto;// 传输协议 (TCP, UDP等)unsigned short checksum;// IP校验和unsigned int sourceIP;unsigned int destIP;}IpHeader;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);}typedef struct _ihdr {//icmp包头的长度不定,但icmp-echo和icmp-echoreply的包头一共12个字节BYTE i_type;// 类型BYTE i_code;// 编码USHORT i_cksum;// 检验和,双字节无符号数,占两个字节USHORT i_id;// 编号USHORT i_seq;// 序列号ULONG timestamp;// 时间戳,四个字节}IcmpHeader;void fill_icmp_data(char * icmp_data, int datasize){IcmpHeader *icmp_hdr;char *datapart;// 将缓冲区转换为icmp_hdr结构icmp_hdr = (IcmpHeader*)icmp_data;// 填充各字段的值icmp_hdr->i_type = ICMP_ECHO;// 将类型设置为ICMP响应包icmp_hdr->i_code = 0;// 将编码设置为0icmp_hdr->i_id = (USHORT)GetCurrentThreadId();// 将编号设置为当前线程的编号icmp_hdr->i_cksum = 0;// 将校验和设置为0icmp_hdr->i_seq = 0;// 将序列号设置为0datapart = icmp_data + sizeof(IcmpHeader);// 定义到数据部分// 在数据部分随便填充一些数据memset(datapart, 'E', datasize - sizeof(IcmpHeader));}int decode_icmp_resp(char *buf, int bytes, sockaddr_in *from, DWORD tid){IpHeader *iphdr;IcmpHeader *icmphdr;unsigned short iphdrlen;iphdr = (IpHeader*)buf;//先将收到的字符串转成ip报头部的格式iphdrlen = iphdr->h_len * 4;//首部长度的单位是四字节,数据报总长度的单位是字节,所以这里要乘以4if (bytes < iphdrlen + ICMP_MIN)//ICMP包头最小8个字节{return -1;}icmphdr = (IcmpHeader*)(buf + iphdrlen);//buf+iphdrlen的实质是buf+iphdrlen*sizeof(char),因为buf是指向char型的。if (icmphdr->i_type != ICMP_ECHOREPLY){return -2;}if (icmphdr->i_id != (USHORT)tid){return -3;}int time = GetTickCount() - (icmphdr->timestamp);if (time >= 0){return time;}else{return -4;}}int ping(const char *ip, DWORD timeout){WSADATA wsa;SOCKET sockRaw = NULL;sockaddr_in dest, from;hostent *hp;int datasize;char *dest_ip;char *icmp_data = NULL;char *recvbuf = NULL;USHORT seq_no = 0;int retval;int ret;// 初始化SOCKETif (WSAStartup(MAKEWORD(2, 1), &wsa) != 0){ret = -1000;// WSAStartup 错误goto FIN;}// 创建原始套接字sockRaw = WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL, 0, WSA_FLAG_OVERLAPPED);// 如果出现错误,则转到最后if (sockRaw == INVALID_SOCKET) {ret = -2;// WSASocket 错误goto FIN;}int bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout,sizeof(timeout));// 如果出现错误,则转到最后if (bread == SOCKET_ERROR) {ret = -3;// setsockopt 错误goto FIN;}// 设置套接字的发送超时选项bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout,sizeof(timeout));if (bread == SOCKET_ERROR) {ret = -4;// setsockopt 错误goto FIN;}memset(&dest, 0, sizeof(dest));unsigned int addr = 0;// 将IP地址转换为网络字节序hp = gethostbyname(ip);// 获取远程主机的名称if (!hp){addr = inet_addr(ip);   //如果使用gethostbyname取得网络地址失效的话就默认使用inet_addr产生的网络地址}if ((!hp) && (addr == INADDR_NONE)) {ret = -5; // 域名错误goto FIN;}// 配置远程通信地址if (hp != NULL)memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);elsedest.sin_addr.s_addr = addr;if (hp)dest.sin_family = hp->h_addrtype;elsedest.sin_family = AF_INET;dest_ip = inet_ntoa(dest.sin_addr);//获得gethostbyname返回的网络地址所对应的点分十进制地址,这才是真正要Ping的地址。datasize = DEF_PACKET_SIZE;datasize = datasize + sizeof(IcmpHeader);char icmp_dataStack[MAX_PACKET];char recvbufStack[MAX_PACKET];icmp_data = icmp_dataStack;recvbuf = recvbufStack;if (!icmp_data)//icmp包分配内存失败{ret = -6;goto FIN;}memset(icmp_data, 0, MAX_PACKET);fill_icmp_data(icmp_data, datasize);//((IcmpHeader*)icmp_data)->i_cksum = 0;DWORD startTime = GetTickCount();((IcmpHeader*)icmp_data)->timestamp = startTime;//((IcmpHeader*)icmp_data)->i_seq = seq_no++;((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);int bwrote;bwrote = sendto(sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest));//发送数据部分(即ICMP部分),由RAWsocket负责封装成IP包if (bwrote == SOCKET_ERROR){if (WSAGetLastError() != WSAETIMEDOUT){ret = -7;goto FIN;}}if (bwrote < datasize){ret = -8;goto FIN;}LARGE_INTEGER ticksPerSecond;LARGE_INTEGER start_tick;LARGE_INTEGER end_tick;double elapsed;QueryPerformanceFrequency(&ticksPerSecond);QueryPerformanceCounter(&start_tick);int fromlen = sizeof(from);while (1){retval = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);if (retval == SOCKET_ERROR){if (WSAGetLastError() == WSAETIMEDOUT){ret = -1;goto FIN;}ret = -9;goto FIN;}int time = decode_icmp_resp(recvbuf, retval, &from, GetCurrentThreadId());if (time >= 0){QueryPerformanceCounter(&end_tick);elapsed = ((double)(end_tick.QuadPart - start_tick.QuadPart) / ticksPerSecond.QuadPart);ret = (int)(elapsed * 1000);goto FIN;}else if (GetTickCount() - startTime >= timeout || GetTickCount() < startTime){ret = -1;goto FIN;}}FIN:closesocket(sockRaw);WSACleanup();return ret;}int main(int argc, char **argv) {printf("ping %s\n", argv[1]);int ret = ping(argv[1], 5000);if (ret >= 0){printf("%s在线,用时%dms\n", argv[1], ret);}else{printf("%d\n", ret);}system("pause");return 0;}


0 0
原创粉丝点击