Windows平台Ping示例源碼分析(C/C++)
//-----------------------iphdr.h-----------------------//
//源碼分析將忽略ipv6
//邊界對齊至字節
pshpack1.h為官方頭文件,不做贅述。
#include <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 消息類型
//-----------------------resolve.h---------------------//
// 恢複默認對齊方式
#include <poppack.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函數更多的緩衝區安全機製。
(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翻譯成為可讀信息。
此函數與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頭部定義
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)
{
usage(argv[0]);
goto CLEANUP;
}
if (argv[i+1][0] == '4')
gAddressFamily = AF_INET;
else if (argv[i+1][0] == '6')
gAddressFamily = AF_INET6;
else
{
usage(argv[0]);
goto CLEANUP;
}
i++;
break;
case 'i': // 設置最大跳值
if (i+1 >= argc)
{
usage(argv[0]);
goto CLEANUP;
}
gTtl = atoi(argv[++i]);
break;
case 'l': // icmp數據段大小設置
if (i+1 >= argc)
{
usage(argv[0]);
goto CLEANUP;
}
gDataSize = atoi(argv[++i]);
break;
case 'r': // 記錄路由選項
bRecordRoute = TRUE;
break;
default:
usage(argv[0]);
goto CLEANUP;
}
}
else
{
gDestination = argv[i];
}
}
isValid = TRUE;
CLEANUP:
return isValid;
}
// 設置icmp序列號
void SetIcmpSequence(char *buf)
{
ULONG sequence=0;
sequence = GetTickCount();
if (gAddressFamily == AF_INET)
{
ICMP_HDR *icmpv4=NULL;
icmpv4 = (ICMP_HDR *)buf;
icmpv4->icmp_sequence = (USHORT)sequence;
}
else if (gAddressFamily == AF_INET6)
{
ICMPV6_HDR *icmpv6=NULL;
ICMPV6_ECHO_REQUEST *req6=NULL;
icmpv6 = (ICMPV6_HDR *)buf;
req6 = (ICMPV6_ECHO_REQUEST *)(buf + sizeof(ICMPV6_HDR));
req6->icmp6_echo_sequence = (USHORT)sequence;
}
}
// 計算icmp校驗和
void ComputeIcmpChecksum(SOCKET s, char *buf, int packetlen, struct addrinfo *dest)
{
if (gAddressFamily == AF_INET)
{
ICMP_HDR *icmpv4=NULL;
icmpv4 = (ICMP_HDR *)buf;
icmpv4->icmp_checksum = 0;
icmpv4->icmp_checksum = checksum((USHORT *)buf, packetlen);
}
else if (gAddressFamily == AF_INET6)
{
ICMPV6_HDR *icmpv6=NULL;
icmpv6 = (ICMPV6_HDR *)buf;
icmpv6->icmp6_checksum = 0;
icmpv6->icmp6_checksum = ComputeIcmp6PseudoHeaderChecksum(
s,
buf,
packetlen,
dest
);
}
}
// 發布異步接收
int PostRecvfrom(SOCKET s, char *buf, int buflen, SOCKADDR *from, int *fromlen, WSAOVERLAPPED *ol)
{
WSABUF wbuf;
DWORD flags,
bytes;
int rc;
wbuf.buf = buf;
wbuf.len = buflen;
flags = 0;
rc = WSARecvFrom( // 通過重疊IO實現異步接收
s,
&wbuf,
1,
&bytes,
&flags,
from,
fromlen,
ol,
NULL
);
if (rc == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
fprintf(stderr, "WSARecvFrom failed: %d/n", WSAGetLastError());
return SOCKET_ERROR;
}
}
return NO_ERROR;
}
// 8
void PrintPayload(char *buf, int bytes)
{
int hdrlen=0,
routes=0,
i;
UNREFERENCED_PARAMETER(bytes);
if (gAddressFamily == AF_INET)
{
SOCKADDR_IN hop;
IPV4_OPTION_HDR *v4opt=NULL;
IPV4_HDR *v4hdr=NULL;
hop.sin_family = (USHORT)gAddressFamily;
hop.sin_port = 0;
v4hdr = (IPV4_HDR *)buf;
hdrlen = (v4hdr->ip_verlen & 0x0F) * 4; // 計算IP頭部長度(8.1)
if (hdrlen > sizeof(IPV4_HDR)) // 如果長度大於無選項IP頭部長度
{
// 選項頭部指針
v4opt = (IPV4_OPTION_HDR *)(buf + sizeof(IPV4_HDR));
//計算路由數(8.2)
routes = (v4opt->opt_ptr / sizeof(ULONG)) - 1;
for(i=0; i < routes ;i++)
{
hop.sin_addr.s_addr = v4opt->opt_addr[i];
if (i == 0)
printf(" Route: ");
else
printf(" ");
PrintAddress((SOCKADDR *)&hop, sizeof(hop));
if (i < routes-1)
printf(" ->/n");
else
printf("/n");
}
}
}
return;
}
(8.1) (v4hdr->ip_verlen & 0x0F) * 4 ip_verlen前4位為版本號,後四位為頭部長度(4字節一記,因此要乘以4)。ip_verlen & 0x0F, 前四位屏蔽,後四位保留。
(8.2) v4opt->opt_ptr 指向下一個可用地址,因此需要減一。
(8.2) v4opt->opt_ptr 指向下一個可用地址,因此需要減一。
// 設置最大跳站數
int SetTtl(SOCKET s, int ttl)
{
int optlevel = 0,
option = 0,
rc;
rc = NO_ERROR;
if (gAddressFamily == AF_INET)
{
optlevel = IPPROTO_IP;
option = IP_TTL;
}
else if (gAddressFamily == AF_INET6)
{
optlevel = IPPROTO_IPV6;
option = IPV6_UNICAST_HOPS;
}
else
{
rc = SOCKET_ERROR;
}
if (rc == NO_ERROR)
{
rc = setsockopt(
s,
optlevel,
option,
(char *)&ttl,
sizeof(ttl)
);
}
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "SetTtl: setsockopt failed: %d/n", WSAGetLastError());
}
return rc;
}
// 9
int __cdecl main(int argc, char **argv)
{
WSADATA wsd;
WSAOVERLAPPED recvol; // 重疊 IO
SOCKET s=INVALID_SOCKET;
char *icmpbuf=NULL;
struct addrinfo *dest=NULL,
*local=NULL;
IPV4_OPTION_HDR ipopt;
SOCKADDR_STORAGE from; // socket地址存儲結構(9.1)
DWORD bytes,
flags;
int packetlen=0,
fromlen,
time=0,
rc,
i,
status = 0;
recvol.hEvent = WSA_INVALID_EVENT;
// 分析輸入參數
if (ValidateArgs(argc, argv) == FALSE)
{
status = -1;
goto EXIT;
}
// socket模塊啟動初始化
if ((rc = WSAStartup(MAKEWORD(2,2), &wsd)) != 0)
{
printf("WSAStartup() failed: %d/n", rc);
status = -1;
goto EXIT;
}
// 解析目的地址
dest = ResolveAddress(
gDestination,
"0",
gAddressFamily,
0,
0
);
if (dest == NULL)
{
printf("bad name %s/n", gDestination);
status = -1;
goto CLEANUP;
}
gAddressFamily = dest->ai_family;
if (gAddressFamily == AF_INET)
gProtocol = IPPROTO_ICMP;
else if (gAddressFamily == AF_INET6)
gProtocol = IPPROTO_ICMP6;
// 獲得本地地址,綁定使用
local = ResolveAddress(
NULL,
"0",
gAddressFamily,
0,
0
);
if (local == NULL)
{
printf("Unable to obtain the bind address!/n");
status = -1;
goto CLEANUP;
}
//創建Raw套接字,protocol = IPPROTO_ICMP
s = socket(gAddressFamily, SOCK_RAW, gProtocol);
if (s == INVALID_SOCKET)
{
printf("socket failed: %d/n", WSAGetLastError());
status = -1;
goto CLEANUP;
}
SetTtl(s, gTtl); //設置最大跳站數為128
if (gAddressFamily == AF_INET)
packetlen += sizeof(ICMP_HDR);
else if (gAddressFamily == AF_INET6)
packetlen += sizeof(ICMPV6_HDR) + sizeof(ICMPV6_ECHO_REQUEST);
// packetlen 為 數據長度+ICMP頭部長度
packetlen += gDataSize;
// 分配空間存儲ICMP請求(9.2)
icmpbuf = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, packetlen);
if 上一篇:
下一篇:
最後更新:2017-04-02 06:51:27