12 第1页 | 共2 页下一页
返回列表 发新帖
查看: 2354|回复: 12
打印 上一主题 下一主题

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

[复制链接]

1023

主题

3

听众

359

积分

设计实习生

Rank: 2

纳金币
335582
精华
0

最佳新人

跳转到指定楼层
楼主
发表于 2011-8-31 08:00:37 |只看该作者 |倒序浏览

原理简介:

--------

用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 << "        data_size can be up to " << MAX_PING_DATA_SIZE <<

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

                endl;

        cerr << "        ttl 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()

       这里对这些方法不作具体讨论, 留给读者自已完成.
分享到: QQ好友和群QQ好友和群 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
转播转播0 分享淘帖0 收藏收藏0 支持支持0 反对反对0
回复

使用道具 举报

Asen    

867

主题

0

听众

1万

积分

外协人员

Rank: 7Rank: 7Rank: 7

纳金币
17488
精华
1
沙发
发表于 2011-8-31 10:00:33 |只看该作者
回复

使用道具 举报

1023

主题

3

听众

359

积分

设计实习生

Rank: 2

纳金币
335582
精华
0

最佳新人

板凳
发表于 2011-12-30 18:23:44 |只看该作者
我无语!
回复

使用道具 举报

5969

主题

1

听众

39万

积分

首席设计师

Rank: 8Rank: 8

纳金币
-1
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

地板
发表于 2012-2-21 23:18:25 |只看该作者
不错哦,顶一下......
回复

使用道具 举报

462

主题

1

听众

31万

积分

首席设计师

Rank: 8Rank: 8

纳金币
2
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

5#
发表于 2012-3-15 23:18:37 |只看该作者
非常感谢,管理员设置了需要对新回复进行审核,您的帖子通过审核后将被显示出来,现在将转入主题
回复

使用道具 举报

tc    

5089

主题

1

听众

33万

积分

首席设计师

Rank: 8Rank: 8

纳金币
-1
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

6#
发表于 2012-4-26 23:19:39 |只看该作者
不错哦,谢谢楼主
回复

使用道具 举报

tc    

5089

主题

1

听众

33万

积分

首席设计师

Rank: 8Rank: 8

纳金币
-1
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

7#
发表于 2012-5-9 23:27:03 |只看该作者
很经典,很实用,学习了!
回复

使用道具 举报

1023

主题

3

听众

359

积分

设计实习生

Rank: 2

纳金币
335582
精华
0

最佳新人

8#
发表于 2012-5-13 23:18:50 |只看该作者
我就看看,我不说话
回复

使用道具 举报

   

671

主题

1

听众

3247

积分

中级设计师

Rank: 5Rank: 5

纳金币
324742
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

9#
发表于 2012-7-8 23:21:17 |只看该作者
不错不错,收藏了
回复

使用道具 举报

5969

主题

1

听众

39万

积分

首席设计师

Rank: 8Rank: 8

纳金币
-1
精华
0

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

10#
发表于 2012-7-19 23:21:28 |只看该作者
楼主收集的可真全哦
回复

使用道具 举报

12 第1页 | 共2 页下一页
返回列表 发新帖
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|纳金网 ( 闽ICP备08008928号

GMT+8, 2024-5-12 01:12 , Processed in 0.100401 second(s), 29 queries .

Powered by Discuz!-创意设计 X2.5

© 2008-2019 Narkii Inc.

回顶部