SOCK_RAW ICMP 协议 PING

SOCK_RAW ICMP 协议 PING


以前的笔记,移过来了。

ICMP需要使用 原始套接字
原始套接字是允许访问底层传输的一种套接字协议,它们可能会被恶意利用,因此 仅 Administrator用户组有权限创建SOCK_RAW类型的套接字。

任何人在NT下都可以创建原始套接字,但没有Administrator权限的人不能用它做任何事情,因为bind函数会直接返回错误,错误码:WSAEACCESS

如果需要绕开管理员权限,可以使用windows提供的IcmpSendEcho系列函数。
在发送ping请求的时候,我只封装了一个ICMP报文,并没有自己手动添加IP头,封装IP报文。
因为内核会自动添加IP头,如果想自己添加IP头,可以调用setsockopt设置IP_HDRINCL选项,告诉内核由我们自己来封装IP头。

ICMP 格式
Head:
8bit(1字节) 的类型 具体消息类型代码 上网查。
8bit(1字节) 的功能代码
16bit(2字节) 的校验和
头部共占4字节。
Body:
取决于类型和代码
选项数据:
随意

校验和计算:
将数据以WORD(SHORT)为单位累加到一个DWORD类型中,如果数据长度为奇数,最后一个BYTE将被扩展到WORD
这时累加的结果是一个WORD,最后将这个WORD的高16位和低16位相加,然后取反,就是校验和了。

#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#pragma warning(disable:4996)//为了直接使用inet aton 或者 inet addr 这里懒得用pton
#pragma comment(lib,"ws2_32.lib")

//定义IP首部格式
typedef struct _IPHeader {
	u_char VIHL; //版本和首部长度
	u_char ToS; //服务类型
	u_short TotalLen; //总长度
	u_short ID; //标识号
	u_short Frag_Flags; //片偏移量
	u_char TTL; //生存时间
	u_char Protocol; //协议
	u_short Checksum; //首部校验和
	struct in_addr SrcIP; //源IP地址
	struct in_addr DestIP; //目的地址
}IPHDR, *PIPHDR;

/*
ICMP 消息结构
*/
typedef struct _icmp_hdr
{
	//head
	unsigned char icmp_type;//消息类型
	unsigned char icmp_code;//功能代码
	unsigned short icmp_checksum;//校验和

	//body
	unsigned short icmp_id;//唯一标识此请求的ID号,通常设置为进程ID
	unsigned short icmp_sequence;//序列号 一般从0开始(没有强制性,从任何数字开始都可以),每发送一次新的回显请求就加1。因为ICMP是在IP数据报内部被传输的,而IP协议又是不可靠、无连接的,所以ping程序打印出返回的每个分组的序列号,方便我们查看是否有分组丢失、失序或重复。
	
	unsigned long  icmp_timestamp;//时间戳

}ICMP_HDR,*PICMP_HDR;

//计算校验和
WORD checkSum(WORD* wBuff, int nSize) {
	DWORD dwSum = 0;;
	//将数据以WORD为单位累加到wSum
	while (nSize > 1) {
		dwSum += *wBuff++;
		nSize -= sizeof(WORD);
	}

	//若出现最后还剩一个字节继续与前面结果相加(也就是为奇数的情况)。
	if (nSize) {
		dwSum += *(BYTE*)wBuff++; 
	}

	//将wSum的高16位和低16位相加,然后取反
	dwSum = (dwSum >> 16/*这里无脑高16位*/) + (dwSum & 0xffff/*这里和(0x0000ffff)做与运算 取低16位*/); //先取和
	dwSum += (dwSum >> 16);//如果还有高于16位,将继续与低16位相加
	return (WORD)(~dwSum);
}

int main()
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (INVALID_SOCKET == s) {
		printf_s("创建套接字失败%d \n", WSAGetLastError());
		closesocket(s);
		s = INVALID_SOCKET;
		system("pause");
		return -1;
	}
	int nNetTimeout = 1000; //1秒
	//发送时限
	setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char *)&nNetTimeout, sizeof(int));
	//接收时限
	setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&nNetTimeout, sizeof(int));
	


	SOCKADDR_IN destSin;
	destSin.sin_family = AF_INET;
	destSin.sin_port = htons(0);
	destSin.sin_addr.S_un.S_addr = inet_addr("123.125.114.144");
	

	//创建ICMP封包
	char buff[sizeof(ICMP_HDR) + 32] = {0};
	PICMP_HDR pIcmp = (PICMP_HDR)buff;
	pIcmp->icmp_type = 0x08;//请求回显
	pIcmp->icmp_id = (USHORT)GetCurrentProcessId(); //记得转成USHORT

	//填充数据部分,可以为任意内容 也就是结构体的之后多出来的32位
	memset((char*)pIcmp + sizeof(ICMP_HDR), 'S', 32);

	
	USHORT nSeq = 0;
	char recvBuff[1024] = {0};
	SOCKADDR_IN fromSin;
	int nLen=sizeof(fromSin);

	//开始发送/接收ICMP封包
	while (true) {
		static int nCount = 0;//用来记录回显多少次

		if (nCount++ == 4) break;

		//每次重新循环之前,重置结构部分数据
		pIcmp->icmp_checksum = 0;//这里必须重置为0 因为它是在buff里的,直接去算会加上上一次计算的值 将导致第二次开始 全部出错
		pIcmp->icmp_timestamp = GetTickCount();
		pIcmp->icmp_sequence = nSeq++;
		
		//计算校验和
		pIcmp->icmp_checksum = checkSum((WORD*)buff,sizeof(buff));

		int nRet = sendto(s, buff, sizeof(buff), 0, (sockaddr*)&destSin, sizeof(destSin));

		if (nRet == SOCKET_ERROR){
			printf_s("发送失败 %d\n", WSAGetLastError());
			system("pause");
			return -1;
		}
		
		nRet = recvfrom(s, recvBuff, sizeof(recvBuff), 0, (sockaddr*)&fromSin, &nLen);
		if (nRet == SOCKET_ERROR) {
			DWORD dwError = WSAGetLastError();
			if (dwError == WSAETIMEDOUT){
				printf_s("接收超时 \n");
				continue;
			}
			printf_s("接收失败%d \n", dwError);
			system("pause");
			return -1;
		}
		
		//开始解析ICMP封包
		int nTick = GetTickCount();
		if (nRet < sizeof(IPHDR) + sizeof(ICMP_HDR)) {
			printf_s("接收的字节数太小 来自:%s %d %d\n",inet_ntoa(fromSin.sin_addr),nRet, sizeof(ICMP_HDR));
		}

		PICMP_HDR pRecvIcmp = (PICMP_HDR)(recvBuff + sizeof(IPHDR));

		if (pRecvIcmp->icmp_type != 0) /*回显*/{
			printf_s("接收没有回应 类型:%d \n", pRecvIcmp->icmp_type);
			system("pause");
			return -1;
		}

		if (pRecvIcmp->icmp_id != GetCurrentProcessId())  {
			printf_s("获取到其他进程的封包了 ID::%d \n", pRecvIcmp->icmp_id);
			system("pause");
			return -1;
		}

		printf_s("%d 字节 来自:%s", nRet, inet_ntoa(fromSin.sin_addr));
		printf_s(" 回应 = %d.", pRecvIcmp->icmp_sequence);
		printf_s(" 时间 = %d ms.\n", nTick - pRecvIcmp->icmp_timestamp);
		Sleep(1000);
	}
	closesocket(s);
	s = INVALID_SOCKET;
	
	WSACleanup();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没事干写博客玩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值