ICMP实现例子
// ICMP.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <WinSock2.h>
#include <tchar.h>
#include <WS2tcpip.h>
#include <windows.h>
#pragma comment(lib, "Ws2_32.lib")
#define ECHO_REQUER 8 //请求
#define ECHO_REPLY 0 //回复
struct IcmpHeader
{
unsigned char icmp_type; //类型
unsigned char icmp_code; //代码
unsigned short icmp_cksum; //效验和
unsigned short icmp_id; //标识
unsigned short icmp_seq; //序列号
unsigned int icmp_data; //GetTickout()
};
struct IpHeader
{
unsigned char h_len : 4; //头部长度
unsigned char version : 4; //ip协议的版本好
unsigned char tos; //区分服务
unsigned short total_len; //包的总长度(首和数据之和)
unsigned short ident; //唯一标识符
unsigned short frag_and_flags; //标志位
unsigned char ttl; //ttl(包在网络传输的生存时间)
unsigned char proto; //数据包的协议(TCP ,UDP etc)
unsigned short checksum; //IP首部校验和 checksum
unsigned int sourceIP; //发送方ip地址
unsigned int destIP; //接收方ip地址
};
void reportError(const char* pre)
{
//#if win_32
//#if linux
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
printf("%s:%s", pre, lpMsgBuf);
LocalFree(lpMsgBuf);
}
void showMsg(const char* msg)
{
printf("%s\n", msg);
}
//网际校验和是被校验数据16位值的反码和(ones-complement sum)
WORD CalcCheckSum(IN unsigned short* addr, IN int len)
{
int nleft = len;
int sum = 0;
unsigned short* w = addr;
unsigned short answer = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(unsigned char*)(&answer) = *(unsigned char*)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
int _tmain(int argc, _TCHAR* argv[])
{
//只有windows初始化socket
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//ICMP用的是原始套接字类型
int nSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
showMsg("socket ok");
//根据域名获取ip地址
char szDomain[20] = { 0 };
printf("请输入你要ping的域名:");
scanf_s("%s", szDomain, 20);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = 0;
struct hostent *host = gethostbyname(szDomain);
if (host == NULL)
{
puts("无效域名");
return 0;
}
addr.sin_addr.S_un.S_addr = *(int*)(host->h_addr_list[0]);
//ICMP协议头
char szIcmpHead[32] = { 0 };
IcmpHeader* pIcmpHeader = (IcmpHeader*)szIcmpHead;
pIcmpHeader->icmp_type = ECHO_REQUER;
pIcmpHeader->icmp_code = 0;
pIcmpHeader->icmp_cksum = 0;
pIcmpHeader->icmp_id = 1;
pIcmpHeader->icmp_seq = 0;
pIcmpHeader->icmp_data = ::GetTickCount();
//校验和0先给0 最后才计算,不然的话校验和的值算不出来
//先把其他的赋值后就知道怎么计算校验和的,因为校验和也是成员,先赋值的话,校验和值就会算错
pIcmpHeader->icmp_cksum = CalcCheckSum((unsigned short*)szIcmpHead, sizeof(szIcmpHead));
//发送ICMP协议头
int nBytes = sendto(nSocket, szIcmpHead, sizeof(szIcmpHead), 0, (sockaddr*)&addr, sizeof(addr));
if (nBytes < 0)
{
reportError("sendto");
}
sockaddr_in stAddr;
char szRecvBuf[200] = { 0 };
int nLen = sizeof(stAddr);
//设置超时时间,没有收到包就放弃[IP][ICMP]
timeval stTv = { 3, 0 };//秒,微秒
fd_set stFd;//socket数组
FD_ZERO(&stFd);//初始化
FD_SET(nSocket, &stFd);//把socket添加进数组
int nRet = select(0, &stFd, NULL, NULL, &stTv);
if (nRet == 0)
{
reportError("select");
puts("超时");
system("pause");
return 0;
}
else if (nRet != SOCKET_ERROR)
{
nBytes = recvfrom(nSocket, szRecvBuf, sizeof(szRecvBuf), 0, (sockaddr*)&stAddr, &nLen);
if (nBytes > 0)
{
IpHeader* pIpHead = (IpHeader*)szRecvBuf;
//IP层前面是IP头,所以拆包的时候获取到ICMP的头,把指针以一个IP头的步长
IcmpHeader* pIcmpHeader = (IcmpHeader*)(pIpHead + 1);
printf("来自 %s 的回复: 字节=32 时间=%dms TTL=%d\n",
inet_ntoa(*(in_addr*)&pIpHead->sourceIP),
::GetTickCount() - pIcmpHeader->icmp_data,
pIpHead->ttl);
}
}
system("pause");
return 0;
}
原始socket例子
#include <WinSock2.h>
#include <MSTcpIP.h>
#include <tchar.h>
#include <WS2tcpip.h>
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
struct IcmpHeader
{
unsigned char icmp_type; //类型
unsigned char icmp_code; //代码
unsigned short icmp_cksum; //效验和
unsigned short icmp_id; //标识
unsigned short icmp_seq; //序列号
unsigned int icmp_data; //GetTickout()
};
struct IpHeader
{
unsigned char h_len : 4; //头部长度
unsigned char version : 4; //ip协议的版本好
unsigned char tos; //区分服务
unsigned short total_len; //包的总长度(首和数据之和)
unsigned short ident; //唯一标识符
unsigned short frag_and_flags; //标志位
unsigned char ttl; //ttl(包在网络传输的生存时间)
unsigned char proto; //数据包的协议(TCP ,UDP etc)
unsigned short checksum; //IP首部校验和 checksum
unsigned int sourceIP; //发送方ip地址
unsigned int destIP; //接收方ip地址
};
struct TcpHeader
{
unsigned short nSourPort; // 源端口号16bit
unsigned short nDestPort; // 目的端口号16bit
unsigned int nSequNum; // 序列号32bit
unsigned int nAcknowledgeNum; // 确认号32bit
unsigned short nHLenAndFlag; // 前4位:TCP头长度;中6位:保留;后6位:标志位16bit
unsigned short nWindowSize; // 窗口大小16bit
unsigned short nCheckSum; // 检验和16bit
unsigned short nrgentPointer; // 紧急数据偏移量16bit
};
struct UdpHeader
{
unsigned short nSourPort; // 源端口号16bit
unsigned short nDestPort; // 目的端口号16bit
unsigned short nLength; // 数据包长度16bit
unsigned short nCheckSum; // 校验和16bit
};
void reportError(const char* pre)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
printf("%s:%s", pre, lpMsgBuf);
LocalFree(lpMsgBuf);
}
void showMsg(const char* msg)
{
printf("%s\n", msg);
}
//网际校验和是被校验数据16位值的反码和(ones-complement sum)
WORD CalcCheckSum(IN unsigned short* addr, IN int len)
{
int nleft = len;
int sum = 0;
unsigned short* w = addr;
unsigned short answer = 0;
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1) {
*(unsigned char*)(&answer) = *(unsigned char*)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
int main()
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
int nSocket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (nSocket <= 0)
{
reportError("socket");
return 0;
}
showMsg("socket ok");
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.S_un.S_addr =htonl(INADDR_LOOPBACK);
if (bind(nSocket, (sockaddr*)&addr, sizeof(addr)) < 0)
{
reportError("bind");
return 0;
}
//设置网卡为混杂模式,也叫泛听模式。可以侦听经过的所有的包(包括接收和发送的数据包 )
int optval = 1;
DWORD dwBytesRet;
int nRet = WSAIoctl(nSocket, SIO_RCVALL, &optval, sizeof(optval), nullptr, 0, &dwBytesRet, nullptr, nullptr);
if (nRet == SOCKET_ERROR)
{
reportError("WSAIoctl");
return 0;
}
/*[IP][TCP/UDP]*/
char szRecvBuff[0x1000] = { 0 };
while (true)
{
nRet = recv(nSocket, szRecvBuff, sizeof(szRecvBuff), 0);
if (nRet > 0)
{
//拿到IP协议头输出目标和源ip
IpHeader* pIphead = (IpHeader*)szRecvBuff;
printf("%-15s ==> %-15s bytes:%d",
inet_ntoa(*(in_addr*)&pIphead->sourceIP),
inet_ntoa(*(in_addr*)&pIphead->destIP),
nRet);
//通过IP协议头判断数据包是那种协议
switch (pIphead->proto)
{
case IPPROTO_TCP:
{
TcpHeader* pTcphead = (TcpHeader*)(pIphead + 1);
printf(" IPPROTO_TCP sourcePort:%d destPort:%d\n",
htons(pTcphead->nSourPort), htons(pTcphead->nDestPort));
break;
}
case IPPROTO_UDP:
{
//同上可以拆包得到UDP的头获取UDP的数据信息
UdpHeader* pUcphead = (UdpHeader*)(pIphead + 1);
printf(" IPPROTO_UDP sourcePort:%d destPort:%d\n",
htons(pUcphead->nSourPort), htons(pUcphead->nDestPort));
break;
}
case IPPROTO_ICMP:
printf(" IPPROTO_ICMP\n");
}
}
}
system("pause");
return 0;
}