网络协议 -- ICMP协议(3)Tracert程序

Tracert是windows系统提供的一个工具,使用该程序可以让我们看到IP数据报从一台主机到另一台主机所经过的路由器。Linux系统也提供了类似的工具,叫traceroute,功能和Tracert一样。

一、 Tracert原理

在介绍Tracert的原理之前,需要先弄清楚IP首部TTL字段的含义,IP报文每经过一个路由器,路由器都会将该IP报文首部的TTL字段减1,
当路由器收到一份IP数据报的TTL是0或1时,路由器此时不会转发该数据报,而会丢弃该数据报,并且给IP数据报首部中的源地址发送一份ICMP超时报文。

IP首部的定义见:http://blog.csdn.net/china_jeffery/article/details/78984477

Tracert就是利用了路由器会丢弃TTL为1或0的数据报且返回ICMP超时报文的特性,来实现侦测路由的功能。Tracert程序先发送TTL值为1的IP数据报,处理这份数据报的第一个路由器将TTL减1,丢弃该数据报并返回ICMP超时报文,这样程序就得到了第一个路由器的地址,以此方式,递增IP数据报TTL的值,直到数据报最终到达目标主机。

那么怎么判断数据报到达了最终的目标主机呢?
我们不能单纯的通过未收到路由器返回的ICMP差错报文的方式来判断数据报到达目的地了,因为有可能我们由于接收ICMP差错报文超时等原因导致我们收不到ICMP差错报文(这也是为什么我们后面会介绍每一个TTL跃点会发送3次或多次请求的原因)。windows平台的tracert与linux平台的traceroute的实现原理稍有不同,判断数据报到达目标主机的方式也有不同。
tracert是通过发送ping包,因为windows系统内核都实现了ping功能,所以如果目的主机收到了ping请求就会回复相应的ping包,tracert就是通过这种方式来判断数据报是否到达了目标主机。而traceroute是通过发送UDP包(UDP端口选择一个不可能使用的UDP端口,比如大于30000的端口),因为目的主机没有监听该端口,所以不会响应接收到的该UDP请求,因此当UDP包到达时,目标主机会返回“端口不可达”的错误,traceroute就是通过该错误来判断UDP包到达了目的主机。

从实现方式来看,traceroute通过UDP的方式来实现更加稳定可靠,因为大多数主机的防火墙会组织ICMP报文,而不会阻止UDP报文。

下图使用wireshark抓取的tracert 192.168.3.76命令的数据包,从图中可以看到tracert是通过发送ping包来实现的,以及每个ping包的TTL递增过程:
网络协议 -- ICMP协议(3)Tracert程序_第1张图片

二、ICMP差错报文格式

路由器在丢弃TTL为0或1的数据报时,会发送一个一份ICMP差错报文,该ICMP的差错报文的type为11, code为0.
ICMP报文的类型定义见:http://blog.csdn.net/china_jeffery/article/details/79045630

type为11的报文格式如下(code有0和1两种,格式一样):
网络协议 -- ICMP协议(3)Tracert程序_第2张图片

三、实现

该示例和之前的Ping程序的示例有所不同,该示例设置了IP_HDRINCL选项来自己构造IP头部。

程序的启动参数使用webrtc的"rtc_base/flags.h"实现,具体使用方法见:WebRTC-命令行参数解析。

代码中的其他某些功能,如断言、时间戳等基于webrtc的rtc_base实现,这些功能也可以很方便的自己实现。 rtc_base的使用参考:http://blog.csdn.net/china_jeffery/article/details/78887619

#include 
#include 
#include 
#include 
#include 
#include "rtc_base/networkprotocolheader.h"
#include "rtc_base/checks.h"
#include "rtc_base/flags.h"
#include "rtc_base/timeutils.h"

DECLARE_bool(h); // 帮助
DECLARE_int(m);  // 最大跃点数
DECLARE_int(w);  // 等待每次回复的超时时间(毫秒)
DECLARE_int(s);  // 发送ICMP包超时时间(毫秒)
DECLARE_int(n);  // 每个跃点发送的请求数

DEFINE_bool(h, false, "帮助");
DEFINE_int(m, 30, "最大跃点数");
DEFINE_int(w, 3000, "等待每次回复的超时时间(毫秒)");
DEFINE_int(s, 3000, "发送ICMP包超时时间(毫秒)");
DEFINE_int(n, 3, "每个跃点发送的请求数");

const int kPingDataSize = 36;
__u32 kLocalIP = 0;

__u32 GetLocalIPv4Address() {
    return inet_addr("192.168.42.26");
    char hostname[MAX_PATH] = { 0 };
    gethostname(hostname, MAX_PATH);
    struct hostent FAR* lpHostEnt = gethostbyname(hostname);
    if (lpHostEnt == NULL) {
        return htonl(0x7f000001); //127.0.0.1
    }

    LPSTR lpAddr = lpHostEnt->h_addr_list[0];

    struct in_addr addr;
    memcpy(&addr, lpAddr, 4);

    return addr.s_addr;
}

std::string IPv4ToString(__u32 ip) {
    in_addr addr;
    addr.s_addr = ip;
    char *p= inet_ntoa(addr);
    if (p)
        return p;
    return "";
}

std::string GetPrintString(const char* fmt, ...) {
    char buf[100];
    va_list arglist;
    va_start(arglist, fmt);

    StringCchVPrintfA(buf, 100, fmt, arglist);
    va_end(arglist);

    return buf;
}


bool print_ip(__u32* ips, int count, __u32 dest_ip) {
    bool has_ip = false;
    bool trace_end = false;

    for (int i = 0; i < count; i++) {
        if (ips[i] != 0) {
            printf(" %s", IPv4ToString(ips[i]).c_str());
            has_ip = true;
            trace_end = (ips[i] == dest_ip);
        }
    }
    if (!has_ip)
        printf(" timeout");

    printf("\n");

    if (trace_end)
        printf("Trace Complete\n");
    return trace_end;
}


void FillRequestIPPacket(__u8* ip_packet, __u16 ip_packet_size, __u16 seq, __u8 ttl, __u32 dest_addr) {
    RTC_DCHECK(ip_packet);

    iphdr* p_iphdr = reinterpret_cast(ip_packet);
    memset(p_iphdr, 0, sizeof(iphdr));
    p_iphdr->version = 4;
    p_iphdr->ihl = sizeof(iphdr)/4; // no option
    p_iphdr->tos = 0;
    p_iphdr->frag_off = 0;
    p_iphdr->id = (__u16)rtc::Time32();
    p_iphdr->ttl = ttl;
    p_iphdr->protocol = IPPROTO_ICMP;
    p_iphdr->tot_len = ip_packet_size;
    p_iphdr->daddr = dest_addr;
    p_iphdr->saddr = kLocalIP;
    p_iphdr->check = rtc::GetCheckSum(reinterpret_cast<__u16*>(p_iphdr), p_iphdr->ihl*4);

    ping_hdr* p_ping_hdr = reinterpret_cast(ip_packet + p_iphdr->ihl*4);
    p_ping_hdr->common_hdr.type = 8;
    p_ping_hdr->common_hdr.code = 0;
    p_ping_hdr->id = (__u16)GetCurrentProcessId();
    p_ping_hdr->seq = seq;

    // fill some junk in the buffer.
    if (kPingDataSize > 0)
        memset((void*)((__u8*)p_ping_hdr+sizeof(ping_hdr)), 'E', kPingDataSize);

    p_ping_hdr->common_hdr.check = 0;
    p_ping_hdr->common_hdr.check = rtc::GetCheckSum(reinterpret_cast<__u16*>(p_ping_hdr), ip_packet_size - p_iphdr->ihl*4);
}

// return source ip address
bool DecodeIPPacket(const __u8* ip_packet, __u16 ip_packet_size, __u32 send_time, __u32* src_addr) {
    const iphdr* ip_hdr = reinterpret_cast<const iphdr*>(ip_packet);
    __u32 use_time = rtc::Time32() - send_time;

    __u16 ip_hdr_len = ip_hdr->ihl * 4; // bytes

    const icmp_common_hdr *icmp_hdr = reinterpret_cast<const icmp_common_hdr*>(ip_packet + ip_hdr_len);
    if (icmp_hdr->type == 0 && icmp_hdr->code == 0) { // 回显应答
        const ping_hdr *p_ping_hdr = reinterpret_cast<const ping_hdr*>(icmp_hdr);
        if (p_ping_hdr->id != (__u16)GetCurrentProcessId()) {
            printf("other process ping response packet, pid=%d\n", GetCurrentProcessId());
            return false;
        }

        printf("%-10s", GetPrintString("<%d ms", use_time == 0 ? 1 : use_time).c_str());
        *src_addr = ip_hdr->saddr;
        return true;
    }
    else if (icmp_hdr->type == 11 && icmp_hdr->code == 0) { // cause by ttl == 0
        printf("%-10s", GetPrintString("<%d ms", use_time == 0 ? 1 : use_time).c_str());

        *src_addr = ip_hdr->saddr;
        return true;
    }
    else {
        printf("unexpected response, type=%d, code=%d\n", icmp_hdr->type, icmp_hdr->code);
        return false;
    }
}

#define SAFE_RELEASE \
if (req_ip_packet) { \
delete[] req_ip_packet; \
    req_ip_packet = NULL;\
}\
if (rsp_ip_packet) { \
delete[] rsp_ip_packet; \
    rsp_ip_packet = NULL;\
}\
if (s != INVALID_SOCKET) {\
    closesocket(s);\
    s = INVALID_SOCKET;\
}\
WSACleanup();

int main(int argc, char**argv) {
    rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
    if (FLAG_h) {
        rtc::FlagList::Print(NULL, false);
        return 1;
    }

    char *hostname = argv[argc - 1];
    if (!hostname || strlen(hostname) == 0) {
        printf("Invalid host name\n");
        return 1;
    }

    printf("Trace %s\n", hostname);

    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(2, 2);
    WSAStartup(wVersionRequested, &wsaData);

    sockaddr_in from;
    int from_len = sizeof(sockaddr_in);

    kLocalIP = GetLocalIPv4Address();

    sockaddr_in dest;
    memset(&dest, 0, sizeof(sockaddr_in));
    dest.sin_family = AF_INET;
    dest.sin_addr.s_addr = inet_addr(hostname);

    // resolve host name
    if (dest.sin_addr.s_addr == INADDR_NONE) {
        unsigned long begin_time = rtc::Time32();
        struct addrinfo* result = nullptr;
        struct addrinfo hints = { 0 };
        hints.ai_family = AF_UNSPEC;

        hints.ai_flags = AI_ADDRCONFIG;
        int ret = getaddrinfo(hostname, nullptr, &hints, &result);
        if (ret != 0) {
            printf("Resolve host name failed, error code = %d\n", ret);
            return 1;
        }
        unsigned long end_time = rtc::Time32();
        struct addrinfo* cursor = result;
        printf("------------------------------\n");
        printf("Resolve [time < %d ms]: \n", end_time - begin_time);
        bool flag = false;
        for (; cursor; cursor = cursor->ai_next) {
            sockaddr_in *paddr_in = reinterpret_cast(cursor->ai_addr);
            printf("%s\n", inet_ntoa(paddr_in->sin_addr));

            if (!flag) {
                dest.sin_addr = paddr_in->sin_addr;
                flag = true;
            }
        }
        freeaddrinfo(result);
        printf("-------------------------------\n");
    }

    printf("Tracing %s [%d max hops]: \n", inet_ntoa(dest.sin_addr), FLAG_m);

    SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (s == INVALID_SOCKET) {
        printf("create socket failed, error code = %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    int err = setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&FLAG_s), sizeof(FLAG_s));
    RTC_DCHECK(err != SOCKET_ERROR);
    if (err == SOCKET_ERROR) {
        printf("setsockopt for SO_SNDTIMEO failed, error code = %d\n", WSAGetLastError());
        closesocket(s);
        WSACleanup();
        return 1;
    }

    err = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&FLAG_w), sizeof(FLAG_w));
    RTC_DCHECK(err != SOCKET_ERROR);
    if (err == SOCKET_ERROR) {
        printf("setsockopt for SO_RCVTIMEO failed, error code = %d\n", WSAGetLastError());
        closesocket(s);
        WSACleanup();
        return 1;
    }

    int opt = 1;
    err = setsockopt(s, IPPROTO_IP, IP_HDRINCL, reinterpret_cast<const char*>(&opt), sizeof(opt));
    RTC_DCHECK(err != SOCKET_ERROR);
    if (err == SOCKET_ERROR) {
        printf("setsockopt for IP_HDRINCL failed, error code = %d\n", WSAGetLastError());
        closesocket(s);
        WSACleanup();
        return 1;
    }

    // ip packet for ping request.
    __u16 req_ip_packet_size = sizeof(iphdr) + sizeof(ping_hdr) + kPingDataSize;
    __u8 *req_ip_packet = new __u8[req_ip_packet_size];
    memset(req_ip_packet, 0, req_ip_packet_size);

    // ip packet for icmp response or ping echo.
    __u16 rsp_ip_packet_size = // ICMP差错报文的大小
        sizeof(iphdr)
        + sizeof(icmp_common_hdr)  // ICMP(type=11,code=0或1)差错报文
        + 4 // unused
        + sizeof(iphdr) + 8;

    //取ping包大小和ICMP差错报文大小的最大值,保证无论返回哪种报文缓冲区都够用,
    //也可以直接分配一个足够大的缓冲区,如1024
    //
    rsp_ip_packet_size = std::max(rsp_ip_packet_size, req_ip_packet_size);

    __u8 *rsp_ip_packet = new __u8[rsp_ip_packet_size];
    memset(rsp_ip_packet, 0, rsp_ip_packet_size);
    RTC_DCHECK(rsp_ip_packet);

    int ttl = 1;
    int seq = 0;
    __u32 *ips = new __u32[FLAG_n];

    for(int hop = 1; hop <= FLAG_m; hop++) {
        printf(" %-4d", hop);

        for (int i = 0; i < FLAG_n; i++) {
            ips[i] = 0;
            seq++;
            FillRequestIPPacket(req_ip_packet, req_ip_packet_size, seq, ttl, dest.sin_addr.s_addr);

            __u32 send_time = rtc::Time32();
            int sent = sendto(s,
                reinterpret_cast<const char*>(req_ip_packet),
                req_ip_packet_size,
                0,
                reinterpret_cast<const sockaddr*>(&dest),
                sizeof(sockaddr));

            if (sent == SOCKET_ERROR) {
                printf("%-10s", "*");

                if (i == FLAG_n - 1)
                    if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
                        SAFE_RELEASE;
                        return 0;
                    }
                continue;
            }


            int bread = recvfrom(s,
                reinterpret_cast<char*>(rsp_ip_packet),
                rsp_ip_packet_size,
                0,
                reinterpret_cast(&from),
                &from_len);

            if (bread == SOCKET_ERROR) {
                int gle = WSAGetLastError();
                printf("%-10s", "*");

                if (i == FLAG_n - 1)
                    if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
                        SAFE_RELEASE;
                        return 0;
                    }
                continue;
            }

            __u32 dest_ip = 0;
            DecodeIPPacket(reinterpret_cast<const __u8*>(rsp_ip_packet), rsp_ip_packet_size, send_time, &dest_ip);
            ips[i] = dest_ip;

            if (i == FLAG_n - 1)
                if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
                    SAFE_RELEASE;
                    return 0;
                }
        }

        ttl++;
    }

    SAFE_RELEASE;
    return 0;
}

运行效果:
网络协议 -- ICMP协议(3)Tracert程序_第3张图片

完整的示例代码见:https://gitee.com/china_jeffery/webrtc项目中的webrtc/src/msvc/test/tracert工程.

你可能感兴趣的:(☆,网络编程,网络协议)