学习内容,参见《Windows网络编程》第13章原始套接字
Ping程序实现步骤
- 创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。
- 创建并初始化ICMP头。
- 调用sendto或WSASendto,将ICMP请求发给远程主机。
- 调用recvfrom或WSARecvfrom,以接收任何ICMP响应。
ICMP简介
ICMP是(Internet ControlMessage Protocol)Internet控制报文协议。用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。
系统自带ping程序,检测百度是否可访问。
自己实现ping程序,检测百度是否可访问。
要显示访问IP地址,该IP即为系统自带ping程序解析IP,如上图为119.75.218.77。
使用windows的Winsock 2编程,需要进行工程配置。
- 工程右键Properties->ConfigurationProperties->Linker->Input->Additional Dependencies中添加ws2_32.lib。
- Demo代码采用Multi-Byte方式,设置Properties->ConfigurationProperties->General->Character Set为Use Multi-Byte Character Set。
备注:所有关系到收发数据的缓冲都属于简单的char类型,这些函数没有Unicode版本。当字符集为Unicode时,需要进行字符串转换。
程序源码,MyPing.cpp。
// MyPing.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_MIN 8 //Minimum 8-byte ICMP packet (header)
#define DEF_PACKET_SIZE 32
#define MAX_PACKET 1024
#define MAX_IP_HDR_SIZE 60
//IP header structure
typedef struct _iphdr
{
unsigned int h_len:4;//Length of the header
unsigned int version:4;//Version of IP
unsigned char tos;//Type of service
unsigned short total_len;//Total length of the packet
unsigned short ident;//Unique identifier
unsigned short frag_and_flags;//Flags
unsigned char ttl;//Time to live
unsigned char proto;//Protocol (TCP,UDP,etc.)
unsigned short checksum;//IP checksum
unsigned int sourceIP;
unsigned int destIP;
} IpHeader;
//ICMP header structure
typedef struct _icmphdr
{
BYTE i_type;
BYTE i_code;//Type sub code
USHORT i_cksum;
USHORT i_id;
USHORT i_seq;
//This is not the standard header, but we reserve space for time
ULONG timestamp;
} IcmpHeader;
//IP option header--use with socket option IP_OPTIONS
typedef struct _ipoptionhdr
{
unsigned char code;//Option type
unsigned char len;//Length of option hdr
unsigned char ptr;//Offset into optons
unsigned long addr[9];//List of IP addrs
} IpOptionHeader;
int datasize;
char* lpdest;
//Print usage information
void usage()
{
printf("usage:MyPing -i:IP [data size]\n");
printf(" -i:IP remote machine to Ping\n");
printf(" datasize can be up to 1 KB\n");
ExitProcess(1);
}
//Helper function to fill in various fields for our ICMP request
void FillICMPData(char* icmp_data, int datasize)
{
IcmpHeader* icmp_hdr = (IcmpHeader*)icmp_data;
icmp_hdr->i_type = ICMP_ECHO;//Request an ICMP echo
icmp_hdr->i_code = 0;
icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
icmp_hdr->i_cksum = 0;
icmp_hdr->i_seq = 0;
char* datapart = icmp_data + sizeof(IcmpHeader);
//Place some junk in the buffer
memset(datapart, 'E', datasize - sizeof(IcmpHeader));
}
//This function calculates the 16-bit one's complement sum
//of the supplied buffer (ICMP) header
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);
}
//If the IP option header is present, find the IP options
//within the IP header and print the record route option values
void DecodeIPOptions(char* buf, int bytes)
{
IpOptionHeader* ipopt = (IpOptionHeader*)(buf + 20);
printf("RR: ");
for (int i = 0; i < (ipopt->ptr / 4) - 1; i++)
{
IN_ADDR inaddr;
inaddr.S_un.S_addr = ipopt->addr[i];
if (i != 0)
{
printf(" ");
}
HOSTENT* host = gethostbyaddr((char*)&inaddr.S_un.S_addr,
sizeof(inaddr.S_un.S_addr), AF_INET);
if (host)
{
printf("(%-15s) %s\n", inet_ntoa(inaddr), host->h_name);
}
else
{
printf("(%-15s)\n", inet_ntoa(inaddr));
}
}
return;
}
//The response is an IP packet. We must decode the IP header to
//locate the ICMP data.
void DecodeICMPHeader(char* buf, int bytes, struct sockaddr_in* from)
{
static int icmpcount = 0;
IpHeader* iphdr = (IpHeader*)buf;
//Number of 32-bit words * 4 = bytes
unsigned short iphdrlen = iphdr->h_len * 4;
DWORD tick = GetTickCount();
if ((iphdrlen == MAX_IP_HDR_SIZE) && (!icmpcount))
{
DecodeIPOptions(buf, bytes);
}
if (bytes < iphdrlen + ICMP_MIN)
{
printf("Too few bytes from %s\n", inet_ntoa(from->sin_addr));
}
IcmpHeader* icmphdr = (IcmpHeader*)(buf + iphdrlen);
if (icmphdr->i_type != ICMP_ECHOREPLY)
{
printf("nonecho type %d recvd\n", icmphdr->i_type);
return;
}
//Make sure this is an ICMP reply to something we sent!
if (icmphdr->i_id != (USHORT)GetCurrentProcessId())
{
printf("someone else's packet!\n");
return;
}
printf("%d bytes from %s:", bytes, inet_ntoa(from->sin_addr));
printf(" icmp_seq = %d. ", icmphdr->i_seq);
printf(" time:%d ms", tick - icmphdr->timestamp);
printf("\n");
icmpcount++;
return;
}
void ValidateArgs(int argc, _TCHAR** argv)
{
lpdest = NULL;
datasize = DEF_PACKET_SIZE;
for (int i = 1; i < argc; i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 'i':
lpdest = argv[i] + 3;
break;
default:
usage();
break;
}
}
else if (isdigit(argv[i][0]))
{
datasize = atoi(argv[i]);
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup() failed:%d\n", GetLastError());
return -1;
}
ValidateArgs(argc, argv);
SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP,
NULL, 0, WSA_FLAG_OVERLAPPED);
if (sockRaw == INVALID_SOCKET)
{
printf("WSASocket() failed:%d\n", WSAGetLastError());
return -1;
}
//Set the send/recv timeout values
struct sockaddr_in from;
int fromlen = sizeof(from);
int timeout = 1000;
int bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO,
(char*)&timeout, sizeof(timeout));
if (bread == SOCKET_ERROR)
{
printf("setsockopt(SO_RCVTIMEO) failed:%d\n", WSAGetLastError());
return -1;
}
timeout = 1000;
bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO,
(char*)&timeout, sizeof(timeout));
if (bread == SOCKET_ERROR)
{
printf("setsockopt(SO_SNDTIMEO) failed:%d\n", WSAGetLastError());
return -1;
}
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
//Resolve the endpoint's name if necessary
dest.sin_family = AF_INET;
if ((lpdest != NULL) && strlen(lpdest) != 0)
{
dest.sin_addr.s_addr = inet_addr(lpdest);
}
else
{
struct hostent* hp = gethostbyname(lpdest);
if (hp != NULL)
{
memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
dest.sin_family = hp->h_addrtype;
printf("dest.sin_addr = %s\n", inet_ntoa(dest.sin_addr));
}
else
{
printf("gethostbyname() failed:%d\n", WSAGetLastError());
return -1;
}
}
//Create the ICMP packet
datasize += sizeof(IcmpHeader);
char* icmp_data = (char*)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY, MAX_PACKET);
if (!icmp_data)
{
printf("HeapAlloc() failed:%d\n", GetLastError());
return -1;
}
memset(icmp_data, 0, MAX_PACKET);
FillICMPData(icmp_data, datasize);
char* recvbuf = (char*)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY, MAX_PACKET);
//Start sending/receiving ICMP packets
USHORT seq_no = 0;
while (true)
{
static int nCount = 0;
int bwrote;
if (nCount++ == 4)
{
break;
}
((IcmpHeader*)icmp_data)->i_cksum = 0;
((IcmpHeader*)icmp_data)->timestamp = GetTickCount();
((IcmpHeader*)icmp_data)->i_seq = seq_no++;
((IcmpHeader*)icmp_data)->i_cksum =
checksum((USHORT*)icmp_data, datasize);
bwrote = sendto(sockRaw, icmp_data, datasize, 0,
(struct sockaddr*)&dest, sizeof(dest));
if (bwrote == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
printf("timed out\n");
continue;
}
printf("sendto() failed:%d\n", WSAGetLastError());
return -1;
}
if (bwrote < datasize)
{
printf("Wrote %d bytes\n", bwrote);
}
bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0,
(struct sockaddr*)&from, &fromlen);
if (bread == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAETIMEDOUT)
{
printf("timed out\n");
continue;
}
printf("revefrom() failed:%d\n", WSAGetLastError());
return -1;
}
DecodeICMPHeader(recvbuf, bread, &from);
Sleep(1000);
}
//Cleanup
if (sockRaw != INVALID_SOCKET)
{
closesocket(sockRaw);
}
HeapFree(GetProcessHeap(), 0, recvbuf);
HeapFree(GetProcessHeap(), 0, icmp_data);
WSACleanup();
return 0;
}