分享
 
 
 

透析ICMP协议(四): 应用篇ping(RAW Socket)

王朝other·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

原理简介:

--------

用RAW Socket实现的ping可能比上一节的应用ICMP.DLL的程序庞大些, 但是这才是我们需要关注的东西, 我的观点真正想做网络开发的程序员应该静下心来读读这篇文章, 相信你会从中获益颇多. 中间我也会讲解一些东西为后一章的路由追踪做一些铺垫.

另一个重要的要讲的东西, 微软公布随时不支持上节讲的ping用到的开发接口, 但是本节的讲的是更一般的东西. 所以它不会过时, 甚至做很小的改动就可以移植到别的系统上去. 系统移植不是我们的讲的重点. 但是微软的长期支持足以引起我们充分的重视.

如何少作变动来使的这个程序实现追踪路由的功能, 这里只是抛砖引玉. 将ICMP包中IP包的包头该为特定的值就能得到那个路由器的IP(要求到达目的地的跳数大于你设的特定值).

这个程序需要windows2k/WindowsXP/WindowsNT平台和系统治理员的权限.

具体实现:

--------

这段源代码大部分来自:

http://tangentsoft.net/wskfaq/examples/rawping.Html

[bugfree]只做了少量修改,给出了大量的注释, 最后结合经验给出了自己的建议.

----------

/*

* 程序名: rawping_driver.cpp

* 说明:

* 驱动程序,也是主函数

*/

#include <winsock2.h>

#include <iostream.h>

#include "rawping.h"

#define DEFAULT_PACKET_SIZE 32 // 默认ICMP包字节数

#define DEFAULT_TTL 30 // 默认TTL值

#define MAX_PING_DATA_SIZE 1024 // 最大数据块

#define MAX_PING_PACKET_SIZE (MAX_PING_DATA_SIZE + sizeof(IPHeader)) //最大ICMP包长度

/* 为 send_buf 和 recv_buf 分配内存

* send_buf大小为 packet_size

* recv_buf大小为 MAX_PING_PACKET_SIZE, 保证大于send_buf

*/

int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf,

int packet_size);

///////////////////////////////////////////////////////////////////////

// Program entry point

int main(int argc, char* argv[])

{

int seq_no = 0; //用在发送和接受的ICMP包头中

ICMPHeader* send_buf = 0;

IPHeader* recv_buf = 0;

// 判定命令行是否合法

if (argc < 2) {

cerr << "usage: " << argv[0] << " <host> [data_size] [ttl]" <<

endl;

cerr << "\tdata_size can be up to " << MAX_PING_DATA_SIZE <<

" bytes. Default is " << DEFAULT_PACKET_SIZE << "." <<

endl;

cerr << "\tttl should be 255 or lower. Default is " <<

DEFAULT_TTL << "." << endl;

return 1;

}

// 处理命令行参数

int packet_size = DEFAULT_PACKET_SIZE;

int ttl = DEFAULT_TTL;

if (argc > 2) {

int temp = atoi(argv[2]);

if (temp != 0) {

packet_size = temp;

}

if (argc > 3) {

temp = atoi(argv[3]);

if ((temp >= 0) && (temp <= 255)) {

ttl = temp;

}

}

}

packet_size = max(sizeof(ICMPHeader),

min(MAX_PING_DATA_SIZE, (unsigned int)packet_size));

// 启动 Winsock

WSAData wsaData;

if (WSAStartup(MAKEWord(2, 1), &wsaData) != 0) {

cerr << "Failed to find Winsock 2.1 or better." << endl;

return 1;

}

SOCKET sd; // RAW Socket句柄

sockaddr_in dest, source;

// 三个任务(创建sd, 设置ttl, 初试dest的值)

if (setup_for_ping(argv[1], ttl, sd, dest) < 0) {

goto cleanup; //释放资源并退出

}

// 为send_buf和recv_buf分配内存

if (allocate_buffers(send_buf, recv_buf, packet_size) < 0) {

goto cleanup;

}

// 初试化IMCP数据包(type=8,code=0)

init_ping_packet(send_buf, packet_size, seq_no);

// 发送ICMP数据包

if (send_ping(sd, dest, send_buf, packet_size) >= 0) {

while (1) {

// 接受回应包

if (recv_ping(sd, source, recv_buf, MAX_PING_PACKET_SIZE) <

0) {

// Pull the sequence number out of the ICMP header. If

// it's bad, we just complain, but otherwise we take

// off, because the read failed for some reason.

unsigned short header_len = recv_buf->h_len * 4;

ICMPHeader* icmphdr = (ICMPHeader*)

((char*)recv_buf + header_len);

if (icmphdr->seq != seq_no) {

cerr << "bad sequence number!" << endl;

continue;

}

else {

break;

}

}

if (decode_reply(recv_buf, packet_size, &source) != -2) {

// SUCcess or fatal error (as opposed to a minor error)

// so take off.

break;

}

}

}

cleanup:

delete[]send_buf; //释放分配的内存

delete[]recv_buf;

WSACleanup(); // 清理winsock

return 0;

}

// 为send_buf 和 recv_buf的内存分配. 太简单, 我略过

int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf,

int packet_size)

{

// First the send buffer

send_buf = (ICMPHeader*)new char[packet_size];

if (send_buf == 0) {

cerr << "Failed to allocate output buffer." << endl;

return -1;

}

// And then the receive buffer

recv_buf = (IPHeader*)new char[MAX_PING_PACKET_SIZE];

if (recv_buf == 0) {

cerr << "Failed to allocate output buffer." << endl;

return -1;

}

return 0;

}

/*

* 程序名: rawping.h

* 说明:

* 主要函数库头文件

*/

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>

// ICMP 包类型, 具体参见本文的第一节

#define ICMP_ECHO_REPLY 0

#define ICMP_DEST_UNREACH 3

#define ICMP_TTL_EXPIRE 11

#define ICMP_ECHO_REQUEST 8

// 最小的ICMP包大小

#define ICMP_MIN 8

// IP 包头

struct IPHeader {

BYTE h_len:4; // Length of the header in dwords

BYTE version:4; // Version of IP

BYTE tos; // Type of service

USHORT total_len; // Length of the packet in dwords

USHORT ident; // unique identifier

USHORT flags; // Flags

BYTE ttl; // Time to live, 这个字段我在下一节中用来实现Tracert功能

BYTE proto; // Protocol number (TCP, UDP etc)

USHORT checksum; // IP checksum

ULONG source_ip;

ULONG dest_ip;

};

// ICMP 包头(实际的包不包括timestamp字段,

// 作者用来计算包的回应时间,其实完全没有必要这样做)

struct ICMPHeader {

BYTE type; // ICMP packet type

BYTE code; // Type sub code

USHORT checksum;

USHORT id;

USHORT seq;

ULONG timestamp; // not part of ICMP, but we need it

};

extern USHORT ip_checksum(USHORT* buffer, int size);

extern int setup_for_ping(char* host, int ttl, SOCKET& sd, sockaddr_in& dest);

extern int send_ping(SOCKET sd, const sockaddr_in& dest, ICMPHeader* send_buf, int packet_size);

extern int recv_ping(SOCKET sd, sockaddr_in& source, IPHeader* recv_buf,

int packet_size);

extern int decode_reply(IPHeader* reply, int bytes, sockaddr_in* from);

extern void init_ping_packet(ICMPHeader* icmp_hdr, int packet_size, int seq_no);

/*

* 程序名: rawping.cpp

* 说明:

* 主要函数库实现部分

*/

include <winsock2.h>

#include <ws2tcpip.h>

#include <iostream.h>

#include "rawping.h"

// 计算ICMP包的校验和的简单算法, 很多地方都有说明, 这里没有必要具体将

// 只是一点要提, 做校验之前, 务必将ICMP包头的checksum字段置为0

USHORT ip_checksum(USHORT* buffer, int size)

{

unsigned long cksum = 0;

// Sum all the words together, adding the final byte if size is odd

while (size > 1) {

cksum += *buffer++;

size -= sizeof(USHORT);

}

if (size) {

cksum += *(UCHAR*)buffer;

}

// Do a little shuffling

cksum = (cksum >> 16) + (cksum & 0xffff);

cksum += (cksum >> 16);

// Return the bitwise complement of the resulting mishmash

return (USHORT)(~cksum);

}

//初试化RAW Socket, 设置ttl, 初试化dest

// 返回值 <0 表失败

int setup_for_ping(char* host, int ttl, SOCKET& sd, sockaddr_in& dest)

{

// Create the socket

sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);

if (sd == INVALID_SOCKET) {

cerr << "Failed to create raw socket: " << WSAGetLastError() <<

endl;

return -1;

}

if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*)&ttl,

sizeof(ttl)) == SOCKET_ERROR) {

cerr << "TTL setsockopt failed: " << WSAGetLastError() << endl;

return -1;

}

// Initialize the destination host info block

memset(&dest, 0, sizeof(dest));

// Turn first passed parameter into an IP address to ping

unsigned int addr = inet_addr(host);

if (addr != INADDR_NONE) {

// It was a dotted quad number, so save result

dest.sin_addr.s_addr = addr;

dest.sin_family = AF_INET;

}

else {

// Not in dotted quad form, so try and look it up

hostent* hp = gethostbyname(host);

if (hp != 0) {

// Found an address for that host, so save it

memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);

dest.sin_family = hp->h_addrtype;

}

else {

// Not a recognized hostname either!

cerr << "Failed to resolve " << host << endl;

return -1;

}

}

return 0;

}

//初试化ICMP的包头, 给data部分填充数据, 最后计算整个包的校验和

void init_ping_packet(ICMPHeader* icmp_hdr, int packet_size, int seq_no)

{

// Set up the packet's fields

icmp_hdr->type = ICMP_ECHO_REQUEST;

icmp_hdr->code = 0;

icmp_hdr->checksum = 0;

icmp_hdr->id = (USHORT)GetCurrentProcessId();

icmp_hdr->seq = seq_no;

icmp_hdr->timestamp = GetTickCount();

// "You're dead meat now, packet!"

const unsigned long int deadmeat = 0xDEADBEEF;

char* datapart = (char*)icmp_hdr + sizeof(ICMPHeader);

int bytes_left = packet_size - sizeof(ICMPHeader);

while (bytes_left > 0) {

memcpy(datapart, &deadmeat, min(int(sizeof(deadmeat)),

bytes_left));

bytes_left -= sizeof(deadmeat);

datapart += sizeof(deadmeat);

}

// Calculate a checksum on the result

icmp_hdr->checksum = ip_checksum((USHORT*)icmp_hdr, packet_size);

}

// 发送生成的ICMP包

// 返回值 <0 表失败

int send_ping(SOCKET sd, const sockaddr_in& dest, ICMPHeader* send_buf,

int packet_size)

{

// Send the ping packet in send_buf as-is

cout << "Sending " << packet_size << " bytes to " <<

inet_ntoa(dest.sin_addr) << "..." << flush;

int bwrote = sendto(sd, (char*)send_buf, packet_size, 0,

(sockaddr*)&dest, sizeof(dest));

if (bwrote == SOCKET_ERROR) {

cerr << "send failed: " << WSAGetLastError() << endl;

return -1;

}

else if (bwrote < packet_size) {

cout << "sent " << bwrote << " bytes..." << flush;

}

return 0;

}

// 接受ICMP包

// 返回值 <0 表失败

int recv_ping(SOCKET sd, sockaddr_in& source, IPHeader* recv_buf,

int packet_size)

{

// Wait for the ping reply

int fromlen = sizeof(source);

int bread = recvfrom(sd, (char*)recv_buf,

packet_size + sizeof(IPHeader), 0,

(sockaddr*)&source, &fromlen);

if (bread == SOCKET_ERROR) {

cerr << "read failed: ";

if (WSAGetLastError() == WSAEMSGSIZE) {

cerr << "buffer too small" << endl;

}

else {

cerr << "error #" << WSAGetLastError() << endl;

}

return -1;

}

return 0;

}

// 对收到的ICMP解码

// 返回值 -2表忽略, -1 表失败, 0 成功

int decode_reply(IPHeader* reply, int bytes, sockaddr_in* from)

{

// 跳过IP包头, 找到ICMP的包头

unsigned short header_len = reply->h_len * 4;

ICMPHeader* icmphdr = (ICMPHeader*)((char*)reply + header_len);

// 包的长度合法, header_len + ICMP_MIN为最小ICMP包的长度

if (bytes < header_len + ICMP_MIN) {

cerr << "too few bytes from " << inet_ntoa(from->sin_addr) <<

endl;

return -1;

}

// 下面的包类型具体参见我的第一部分 "透析ICMP协议(一): 协议原理"

else if (icmphdr->type != ICMP_ECHO_REPLY) { //非正常回复

if (icmphdr->type != ICMP_TTL_EXPIRE) { //ttl减为零

if (icmphdr->type == ICMP_DEST_UNREACH) { //主机不可达

cerr << "Destination unreachable" << endl;

}

else { //非法的ICMP包类型

cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<

" received" << endl;

}

return -1;

}

}

else if (icmphdr->id != (USHORT)GetCurrentProcessId()) {

//不是本进程发的包, 可能是同机的其它ping进程发的

return -2;

}

// 指出包传递了多远

// [bugfree]我认为作者这里有问题, 因为有些系统的ttl初值为128如winXP,

// 有些为256如我的DNS服务器211.97.168.129, 作者假设为256有点武断,

// 可以一起探讨这个问题, 回email:zhangliangsd@hotmail.com

int nHops = int(256 - reply->ttl);

if (nHops == 192) {

// TTL came back 64, so ping was probably to a host on the

// LAN -- call it a single hop.

nHops = 1;

}

else if (nHops == 128) {

// Probably localhost

nHops = 0;

}

// 所有工作结束,打印信息

cout << endl << bytes << " bytes from " <<

inet_ntoa(from->sin_addr) << ", icmp_seq " <<

icmphdr->seq << ", ";

if (icmphdr->type == ICMP_TTL_EXPIRE) {

cout << "TTL expired." << endl;

}

else {

cout << nHops << " hop" << (nHops == 1 ? "" : "s");

cout << ", time: " << (GetTickCount() - icmphdr->timestamp) <<

" ms." << endl;

}

return 0;

}

总结和建议:

-----------

bugfree建议其中的这些方面需要改进:

1. 头文件iostream.h 改为 iostream, 后者是标准C++的头文件

同时添加对std::cout 和 std::endl;的引用

对于cerr 建议都改为std::cout(因为后者头文件不支持)

2. 程序的发送和接受采用了同步的方式, 这使得假如出现网络问题recv_ping将陷入持续等待.

这是我们不想看到的.

这三种技术可以达到目的:

- 使用多线程, 将ping封装进线程, 在主程序中对它的超时进行处理

- 使用select()函数来实现

- 使用windows的 WSAAsyncSelect()

这里对这些方法不作具体讨论, 留给读者自已完成.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有