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();
}