Windows平台Ping示例源码分析(C/C++)
版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://unliminet.blog.51cto.com/380895/77717 |
//-----------------------iphdr.h-----------------------//
//源码分析将忽略ipv6//边界对齐至字节
#include <pshpack1.h> pshpack1.h为官方头文件,不做赘述。 // 1 -- ipv4 头部
typedef struct ip_hdr
{
unsigned char ip_verlen; // 前4位IP版本号(IPv4 或者IPv6)
// 后4位头部长度(32位,4字节)(1.1)
unsigned char ip_tos; // 前3位为优先级,后5位为服务类型(1.2)
unsigned short ip_totallength; // 16位包总长度包括头部和数据(字节)(1.3)
unsigned short ip_id; // 16位ID标识
unsigned short ip_offset; // 前3位为分段标识,后5位为分段偏移
unsigned char ip_ttl; // 该包可经过的路由器数量上限
unsigned char ip_protocol; // 协议类型(TCP,UDP,ICMP,IGMP等)
unsigned short ip_checksum; // ipv4 头部的校验和
unsigned int ip_srcaddr; // ipv4 源地址
unsigned int ip_destaddr; // ipv4 目的地址
} IPV4_HDR, *PIPV4_HDR, FAR * LPIPV4_HDR; 此为IPv4头部定义。IPv4头部一般为20字节大小除非使用到选项位。 (1.1) 由于最大头部的限制,ping所具有路由记录功能在如今的网络拓扑中无法使用(只能记录9个ipv4地址)。此功能由traceroute替代。 (1.2) 以上参照CCNA Study Guide的说法。在TCP/IP illustrated Volume I中前3位被忽略,后4位分别代表minimize delay, maximize throughput, maximize reliability, 和minimize monetray cost,最后1位被置0并忽略。 (1.3) 理论上ipv4可以达到的最大长度为65536字节,除了在超级通道(hyperchannel)中出现这种最大传输单元外,在普通网络中通常只允许 8192字节大小的包传输。TCP为流协议没有大小限制,UDP最大包为512字节。一台主机一次可接收的最大数据包为576字节。 // 2 -- ipv4 选项头部
typedef struct ipv4_option_hdr
{
unsigned char opt_code; // ipv4 选项头类型
unsigned char opt_len; // ipv4 选项头长度
unsigned char opt_ptr; // ipv4 选项头指针
unsigned long opt_addr[9]; // ipv4 9个地址列表(2.1)
} IPV4_OPTION_HDR, *PIPV4_OPTION_HDR, FAR *LPIPV4_OPTION_HDR; (2.1) 参照(1.1) // 3 -- icmp 头部
typedef struct icmp_hdr
{
unsigned char icmp_type; // icmp 类型
unsigned char icmp_code; // ipv4 码
unsigned short icmp_checksum; // icmp 头部及数据校验和
unsigned short icmp_id; // icmp id标识(3.1)
unsigned short icmp_sequence; // icmp 序列号,请求回应消息对
} ICMP_HDR, *PICMP_HDR, FAR *LPICMP_HDR; (3.1) id标识一般为发送icmp回显请求的进程号 // 4 -- udp 头部(此头部未在程序中用到)
typedef struct udp_hdr
{
unsigned short src_portno; // 源端口
unsigned short dst_portno; // 目的端口
unsigned short udp_length; // udp 包总长度(字节)
unsigned short udp_checksum; // udp 头部以及数据校验和(4.1)
} UDP_HDR, *PUDP_HDR; (4.1) UDP,TCP校验和都包括头部和数据。IP校验和只涉及头部。UDP校验和可选,TCP为强制。 // 5 -- ipv4 路径记录宏
#define IP_RECORD_ROUTE 0x7
// icmp 类型和码(5.1)
#define ICMPV4_ECHO_REQUEST_TYPE 8 // icmp 回显请求类型
#define ICMPV4_ECHO_REQUEST_CODE 0 // icmp 回显请求码
#define ICMPV4_ECHO_REPLY_TYPE 0 // icmp 回显回应类型
#define ICMPV4_ECHO_REPLY_CODE 0 // icmp 回显回应码
#define ICMPV4_MINIMUM_HEADER 8 // icmp 最小头部 (5.1) 参照TCP/IP Illustrated : Volume 1 Chapter 6 ICMP ICMP 消息类型 // 恢复默认对齐方式
#include <poppack.h> //-----------------------resolve.h---------------------// #ifndef _RESOLVE_H_ #define _RESOLVE_H_
// 在C++编译器中以C语言的方式编译
#ifdef _cplusplus
extern "C" {
#endif
int PrintAddress(SOCKADDR *sa, int salen);
int FormatAddress(SOCKADDR *sa, int salen, char *addrbuf, int addrbuflen);
int ReverseLookup(SOCKADDR *sa, int salen, char *namebuf, int namebuflen);
struct addrinfo *ResolveAddress(char *addr, char *port, int af, int type, int proto);
#ifdef _cplusplus
}
#endif
#endif //-----------------------resolve.cpp---------------------// // 6
#include <winsock2.h> // socket 标准头文件
#include <ws2tcpip.h> // TCP/IP实现相关(6.1)
#include <strsafe.h> // 提供安全的字符串操作(6.2)
#include <stdio.h>
#include <stdlib.h>
#include "resolve.h" (6.1) 此头文件提供 getnameinfo,getaddrinfo函数。 (6.2) StringCchPrintf, StringCchCopy 具有相对于printf 和strcpy函数更多的缓冲区安全机制。 // 7
int PrintAddress(SOCKADDR *sa, int salen)
{
char host[NI_MAXHOST],
serv[NI_MAXSERV];
int hostlen = NI_MAXHOST,
servlen = NI_MAXSERV,
rc;
rc = getnameinfo( // 提供协议无关的名字解析(7.1)
sa,
salen,
host,
hostlen,
serv,
servlen,
NI_NUMERICHOST | NI_NUMERICSERV
);
if (rc != 0)
{
fprintf(stderr, "%s: getnameinfo failed: %d\n", __FILE__, rc);
return rc;
}
if (strcmp(serv, "0") != 0)
{
if (sa->sa_family == AF_INET)
printf("[%s]:%s", host, serv);
else
printf("%s:%s", host, serv);
}
else
printf("%s", host);
return NO_ERROR;
}
(7.1) host 缺省返回为一个在网络上的完整域名。serv返回为一个端口服务名。NI_NUMBERICHOST | NI_NUMBERICSERV 意味着返回以数字形式表示的host(IP地址)和serv(端口号)。 此函数与ResolveAddress函数的区别为此函数将addrinfo结构中sockaddr翻译成为可读信息。 int FormatAddress(SOCKADDR *sa, int salen, char *addrbuf, int addrbuflen)
{
char host[NI_MAXHOST],
serv[NI_MAXSERV];
int hostlen = NI_MAXHOST,
servlen = NI_MAXSERV,
rc;
HRESULT hRet;
rc = getnameinfo(
sa,
salen,
host,
hostlen,
serv,
servlen,
NI_NUMERICHOST | NI_NUMERICSERV
);
if (rc != 0)
{
fprintf(stderr, "%s: getnameinfo failed: %d\n", __FILE__, rc);
return rc;
}
if ( (strlen(host) + strlen(serv) + 1) > (unsigned)addrbuflen)
return WSAEFAULT;
addrbuf[0] = '\0';
if (sa->sa_family == AF_INET)
{
if(FAILED(hRet = StringCchPrintf(addrbuf, addrbuflen, "%s:%s", host, serv)))
{
fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet);
return (int)hRet;
}
}
else if (sa->sa_family == AF_INET6)
{
if(FAILED(hRet = StringCchPrintf(addrbuf, addrbuflen, "[%s]:%s", host, serv)))
{
fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet);
return (int)hRet;
}
}
return NO_ERROR;
} 以上代码将解析出的IP地址和端口号以 X:X的形式存放入字符串。 struct addrinfo *ResolveAddress(char *addr, char *port, int af, int type, int proto)
{
struct addrinfo hints,
*res = NULL;
int rc;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = ((addr) ? 0 : AI_PASSIVE);
hints.ai_family = af;
hints.ai_socktype = type;
hints.ai_protocol = proto;
rc = getaddrinfo(
addr,
port,
&hints,
&res
);
if (rc != 0)
{
fprintf(stderr, "Invalid address %s, getaddrinfo failed: %d\n", addr, rc);
return NULL;
}
return res;
} 以上代码解析出域名将地址信息存入addrinfo结构。 int ReverseLookup(SOCKADDR *sa, int salen, char *buf, int buflen)
{
char host[NI_MAXHOST];
int hostlen=NI_MAXHOST,
rc;
HRESULT hRet;
rc = getnameinfo(
sa,
salen,
host,
hostlen,
NULL,
0,
0
);
if (rc != 0)
{
fprintf(stderr, "getnameinfo failed: %d\n", rc);
return rc;
}
buf[0] = '\0';
if(FAILED(hRet = StringCchCopy(buf, buflen, host)))
{
fprintf(stderr,"StringCchCopy failed: 0x%x\n",hRet);
return (int)hRet;
}
return NO_ERROR;
}
DNS逆向查找(此函数未被调用过)。 此函数和PrintAddress 函数功能相近。 //-----------------------ping.cpp---------------------// // 64位架构
#ifdef _IA64_
#pragma warning (disable: 4267)
#endif
// 此宏定义使windows.h剔除部分头文件,加快编译速度
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include "resolve.h"
#include "iphdr.h"
#define DEFAULT_DATA_SIZE 32 // icmp 数据段大小
#define DEFAULT_SEND_COUNT 4 // ping 的次数
#define DEFAULT_RECV_TIMEOUT 6000 // 接收超时
#define DEFAULT_TTL 128 // 最大跳站术
#define MAX_RECV_BUF_LEN 0xFFFF // 最大接收缓冲区大小
int gAddressFamily=AF_UNSPEC,
gProtocol=IPPROTO_ICMP,
gTtl=DEFAULT_TTL;
int gDataSize=DEFAULT_DATA_SIZE;
BOOL bRecordRoute=FALSE; // 是否记录路由
char *gDestination=NULL,
recvbuf[MAX_RECV_BUF_LEN];
int recvbuflen = MAX_RECV_BUF_LEN; // ping 命令使用方法
void usage(char *progname)
{
printf("usage: %s [options] <host> \n", progname);
printf(" host Remote machine to ping\n");
printf(" options: \n");
printf(" -a 4|6 Address family (default: AF_UNSPEC)\n");
printf(" -i ttl Time to live (default: 128) \n");
printf(" -l bytes Amount of data to send (default: 32) \n");
printf(" -r Record route (IPv4 only)\n");
return;
}
// 初始话icmp头部
void InitIcmpHeader(char *buf, int datasize)
{
ICMP_HDR *icmp_hdr=NULL;
char *datapart=NULL;
icmp_hdr = (ICMP_HDR *)buf;
icmp_hdr->icmp_type = ICMPV4_ECHO_REQUEST_TYPE;
icmp_hdr->icmp_code = ICMPV4_ECHO_REQUEST_CODE;
icmp_hdr->icmp_id = (USHORT)GetCurrentProcessId(); //进程号
icmp_hdr->icmp_checksum = 0; // 序列号未定义,校验和未计算
icmp_hdr->icmp_sequence = 0; // 序列号置空
datapart = buf + sizeof(ICMP_HDR); // 指针移至数据段头
memset(datapart, 'E', datasize); // 填充数据段
}
// 计算校验和
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);
}
// 传参解析
BOOL ValidateArgs(int argc, char **argv)
{
int i;
BOOL isValid = FALSE;
for(i=1; i < argc ;i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 'a':
if (i+1 >= argc)
&n |

