libnet/icmp_echo_request_list.c

ping指令使用ICMP Echo Request封包,當對方主機接受ICMP Echo Request封包且路徑上也沒有防火牆擋下ICMP Echo Request封包,目標主機就會回覆一個ICMP Echo Reply封包。

這個程式能夠指定數個IP Address來送出ICMP Echo Request封包,不過我們並不會等待回覆的ICMP Echo Reply封包,單純送出去封包,程式主要來示範另外一種建構多個封包的做法。

Source Code

//
//  icmp_echo_request_list.c
//  功能:對特定IP列表送出ICMP Echo Request封包。
//  Created by 聲華 陳 on 2016/02/27.
//

#include <stdio.h>
#include <stdlib.h>
#include <libnet.h>

void usage(const char *cmd);
u_int32_t ip_aton(const char *ip_address);
char *ip_ntoa(u_int32_t i);

int main(int argc, const char * argv[]) {
    int c;
    int interval = 100 * 1000; //0.1s
    libnet_t *handle = NULL;
    char errbuf[LIBNET_ERRBUF_SIZE];
    libnet_ptag_t tag;
    char payload[] = "1234567890abcdef";
    int payload_length = sizeof(payload) - 1;
    char label[LIBNET_LABEL_SIZE] = {};
    int length;

    //parse argument
    opterr = 0; //don't show default error message
    while((c = getopt(argc, (char * const *)argv, "w:")) != EOF) {
        switch (c) {
            case 'w':
                interval = atoi(optarg) * 1000;
                if(interval == 0) {
                    fprintf(stderr, "Invalid interval value: %s\n", optarg);
                    exit(1);
                }//end if
                break;

            case '?':
            case 'h':
            default:
                usage(argv[0]);
                break;
        }//end switch
    }//end while

    argc -= optind;
    argv += optind;

    if(argc <= 0) {
        argv -= optind;
        usage(argv[0]);
    }//end if

    //build icmp packet
    for(int i = 0 ; i < argc ; i++) {
        u_int32_t dst_ip = ip_aton(argv[i]);

        //init
        handle = libnet_init(LIBNET_RAW4, NULL, errbuf);
        if(!handle) {
            fprintf(stderr, "libnet_init: %s\n", errbuf);
            libnet_cq_destroy();
            exit(1);
        }//end if

        //init rand seed
        libnet_seed_prand(handle);

        tag = libnet_build_icmpv4_echo(ICMP_ECHO,
                                       //type, 8
                                       0,
                                       //code
                                       0,
                                       //checksum, auto calculate
                                       libnet_get_prand(LIBNET_PRu16),
                                       //id, rand
                                       libnet_get_prand(LIBNET_PRu16),
                                       //sequence, rand
                                       (const u_int8_t *)payload,
                                       payload_length,
                                       handle,
                                       LIBNET_PTAG_INITIALIZER);
        if(tag == -1) {
            fprintf(stderr, "libnet_build_icmpv4_echo: %s\n", libnet_geterror(handle));
            libnet_destroy(handle);
            libnet_cq_destroy();
            exit(1);
        }//end if

        tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + payload_length,
                                //total length, 28 + payload length
                                0,
                                //type of service
                                libnet_get_prand(LIBNET_PRu16),
                                //id, rand
                                0,
                                //fragment offset
                                64,
                                //time to live
                                IPPROTO_ICMP,
                                //procotol, 1
                                0,
                                //checksum, auto calculate
                                libnet_get_ipaddr4(handle),
                                //source ip address
                                dst_ip,
                                //destination ip address
                                NULL,
                                0,
                                handle,
                                LIBNET_PTAG_INITIALIZER);

        if(tag == -1) {
            fprintf(stderr, "libnet_build_ipv4: %s\n", libnet_geterror(handle));
            libnet_destroy(handle);
            libnet_cq_destroy();
            exit(1);
        }//end if

        //label
        memset(label, 0, sizeof(label));
        snprintf(label, sizeof(label), "%s", ip_ntoa(dst_ip));

        //push
        libnet_cq_add(handle, label);

    }//end if

    //send packet
    for(handle = libnet_cq_head() ; libnet_cq_last() ; handle = libnet_cq_next()) {
        length = libnet_write(handle);
        if(length == -1) {
            fprintf(stderr, "libnet_write: %s\n", libnet_geterror(handle));
        }//end if
        else {
            printf("Sent: %d bytes, label: %s\n", length, libnet_cq_getlabel(handle));
        }//end else

        usleep(interval);
    }//end if

    //free
    libnet_cq_destroy();

    return 0;
}

void usage(const char *cmd) {
    printf("Usage: %s [-w Interval ms] <IP Address 1> <IP Address 2> <IP Address 3>...\n", cmd);
    exit(1);
}//end usage

u_int32_t ip_aton(const char *ip_address) {
    u_int32_t ip_integer;
    if(1 != inet_pton(AF_INET, ip_address, &ip_integer)) {
        fprintf(stderr, "Invalid IP address: %s\n", ip_address);
        libnet_cq_destroy();
        exit(1);
    }//end if
    return ip_integer;
}//end if

char *ip_ntoa(u_int32_t i) {
#define FUNCTION_BUFFER 256
    static char str[FUNCTION_BUFFER][INET_ADDRSTRLEN];
    static int which = -1;

    which = (which + 1 == FUNCTION_BUFFER ? 0 : which + 1);

    memset(str[which], 0, sizeof(str[which]));

    inet_ntop(AF_INET, &i, str[which], sizeof(str[which]));

    return str[which];
}//end ip_ntoa

結果

libnet % ./icmp_echo_request_list
Usage: ./icmp_echo_request_list [-w Interval ms] <IP Address 1> <IP Address 2> <IP Address 3>...
libnet % sudo ./icmp_echo_request_list 192.168.1.1 192.168.1.101 192.168.1.250
Sent: 44 bytes, label: 192.168.1.250
Sent: 44 bytes, label: 192.168.1.101
Sent: 44 bytes, label: 192.168.1.1
libnet %

參數

-w #每個封包間隔,單位毫秒,預設100毫秒
<IP Address 1> <IP Address 2> <IP Address 3>... #IP列表

分析

首先先直接看參數給了三個IP地址:192.168.1.1、192.168.1.101和192.168.1.250,但是抓取封包會發現實際上有送出ICMP Echo Request封包的只有192.168.1.1和192.168.1.101,可以看到第一個封包是詢問192.168.1.250的MAC Address,但是目前192.168.1.250並不在線上,沒有獲得該MAC Address,結果就不送出ICMP Echo Request封包了,所以這個程式送出的封包只有No.3和No.6。

當然可以強迫送出192.168.1.250的封包,但是我們所開啟的Socket類型並無法強迫送出,因為我們是使用IP Layer的Raw socket,Ethernet層部分是交由給系統填寫的。


void usage(const char *cmd);
u_int32_t ip_aton(const char *ip_address);
char *ip_ntoa(u_int32_t i);

函數宣告有這些:函數usage()輸出該程式用法;函數ip_aton()將字串的IP Address轉成網路順序地址;函數ip_ntoa()將網路資料格式的IP Address轉成字串。


void usage(const char *cmd) {
    printf("Usage: %s [-w Interval ms] <IP Address 1> <IP Address 2> <IP Address 3>...\n", cmd);
    exit(1);
}//end usage

函數usage()可以看到該程式w選項可有可無,最主要是後面的IP Address列表。


u_int32_t ip_aton(const char *ip_address) {
    u_int32_t ip_integer;
    if(1 != inet_pton(AF_INET, ip_address, &ip_integer)) {
        fprintf(stderr, "Invalid IP address: %s\n", ip_address);
        libnet_cq_destroy();
        exit(1);
    }//end if
    return ip_integer;
}//end if

函數ip_aton()跟前面用法不同的是多了函數libnet_cq_destroy(),因為該函數是在建構封包的時候才會呼叫,如果發生錯誤的話結束程式前還是得釋放資源(後面會再解釋)。


    //parse argument
    opterr = 0; //don't show default error message
    while((c = getopt(argc, (char * const *)argv, "w:")) != EOF) {
        switch (c) {
            case 'w':
                interval = atoi(optarg) * 1000;
                if(interval == 0) {
                    fprintf(stderr, "Invalid interval value: %s\n", optarg);
                    exit(1);
                }//end if
                break;

            case '?':
            case 'h':
            default:
                usage(argv[0]);
                break;
        }//end switch
    }//end while

    argc -= optind;
    argv += optind;

    if(argc <= 0) {
        argv -= optind;
        usage(argv[0]);
    }//end if

讀入的參數只有w而已,後面IP Address讀取方法比較特別。

...略
    argc -= optind;
    argv += optind;
...略

因為後面IP Address部分不像一般參數前面會有個選項,我們處理完前面的有選項的參數部分後,變數optind會是接下來要處理的參數Index,也就是說只要變數argc減掉變數optind就會是剩下參數的數量,變數argv加上變數optind就會指向接著IP Address的參數部分。

...略
    if(argc <= 0) {
        argv -= optind;
        usage(argv[0]);
    }//end if

那當然如果後面沒參數的話變數argc就會等於0,在結束程式之前記得變數argv要扣回去剛剛加的部分,這樣變數argv[0]才會是我們要的結果。


    //build icmp packet
    for(int i = 0 ; i < argc ; i++) {
        u_int32_t dst_ip = ip_aton(argv[i]);

        //init
        handle = libnet_init(LIBNET_RAW4, NULL, errbuf);
        if(!handle) {
            fprintf(stderr, "libnet_init: %s\n", errbuf);
            libnet_cq_destroy();
            exit(1);
        }//end if

        //init rand seed
        libnet_seed_prand(handle);

        tag = libnet_build_icmpv4_echo(ICMP_ECHO,
                                       //type, 8
                                       0,
                                       //code
                                       0,
                                       //checksum, auto calculate
                                       libnet_get_prand(LIBNET_PRu16),
                                       //id, rand
                                       libnet_get_prand(LIBNET_PRu16),
                                       //sequence, rand
                                       (const u_int8_t *)payload,
                                       payload_length,
                                       handle,
                                       LIBNET_PTAG_INITIALIZER);
        if(tag == -1) {
            fprintf(stderr, "libnet_build_icmpv4_echo: %s\n", libnet_geterror(handle));
            libnet_destroy(handle);
            libnet_cq_destroy();
            exit(1);
        }//end if

        tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + payload_length,
                                //total length, 28 + payload length
                                0,
                                //type of service
                                libnet_get_prand(LIBNET_PRu16),
                                //id, rand
                                0,
                                //fragment offset
                                64,
                                //time to live
                                IPPROTO_ICMP,
                                //procotol, 1
                                0,
                                //checksum, auto calculate
                                libnet_get_ipaddr4(handle),
                                //source ip address
                                dst_ip,
                                //destination ip address
                                NULL,
                                0,
                                handle,
                                LIBNET_PTAG_INITIALIZER);

        if(tag == -1) {
            fprintf(stderr, "libnet_build_ipv4: %s\n", libnet_geterror(handle));
            libnet_destroy(handle);
            libnet_cq_destroy();
            exit(1);
        }//end if

        //label
        memset(label, 0, sizeof(label));
        snprintf(label, sizeof(label), "%s", ip_ntoa(dst_ip));

        //enqueue
        libnet_cq_add(handle, label);

    }//end if

接著來構造ICMP Echo Request封包了。

    //build icmp packet
    for(int i = 0 ; i < argc ; i++) {
        u_int32_t dst_ip = ip_aton(argv[i]);
...略

迴圈直接取用變數argc和變數argv,每次迴圈的變數argv[i]就是參數列的IP Address,直接使用函數ip_aton()來轉成網路順序地址。

...略
        //init
        handle = libnet_init(LIBNET_RAW4, NULL, errbuf);
        if(!handle) {
            fprintf(stderr, "libnet_init: %s\n", errbuf);
            libnet_cq_destroy();
            exit(1);
        }//end if
...略

首先先初始化出一個libnet handle,函數libnet_init()第一個參數指定使用LIBNET_RAW4為IP Layer的Raw socket,Ethernet層交由給系統填寫,第二個參數使用NULL,因為我們是交由給系統去Routing,所以並不用指定哪個Interface。

...略
        //init rand seed
        libnet_seed_prand(handle);
...略

接著我們有些封包表頭欄位需要亂數產生,所以先使用函數libnet_seed_prand()來初始化亂數種子。

...略
        tag = libnet_build_icmpv4_echo(ICMP_ECHO,
                                       //type, 8
                                       0,
                                       //code
                                       0,
                                       //checksum, auto calculate
                                       libnet_get_prand(LIBNET_PRu16),
                                       //id, rand
                                       libnet_get_prand(LIBNET_PRu16),
                                       //sequence, rand
                                       (const u_int8_t *)payload,
                                       payload_length,
                                       handle,
                                       LIBNET_PTAG_INITIALIZER);
        if(tag == -1) {
            fprintf(stderr, "libnet_build_icmpv4_echo: %s\n", libnet_geterror(handle));
            libnet_destroy(handle);
            libnet_cq_destroy();
            exit(1);
        }//end if
...略

一樣建構封包的規則是由上往下,函數libnet_build_icmpv4_echo()建構封包,第一個參數Type填入ICMP_ECHO,就是8,表示這個封包是ICMP Echo Request封包,第三個參數Checksum填入0表示自動算。接著第四和第五個參數,ID和Sequence部分隨機產生就好,使用函數libnet_get_prand()參數使用LIBNET_PRu16,表示要產生一個unsigned的16位元int。這邊我們加入Payload,第六和第七的參數分別傳入Payload和Payload的長度,而Payload就是我們前面宣告的變數,在結果的封包左下角可以看到封包最後面有我們填入的Payload。

亂數產生當然也可以用最簡單的函數srand()rand()產生,不過我們目前在使用libnet,所以就使用它提供的函數產生。

...略
        tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + payload_length,
                                //total length, 28 + payload length
                                0,
                                //type of service
                                libnet_get_prand(LIBNET_PRu16),
                                //id, rand
                                0,
                                //fragment offset
                                64,
                                //time to live
                                IPPROTO_ICMP,
                                //procotol, 1
                                0,
                                //checksum, auto calculate
                                libnet_get_ipaddr4(handle),
                                //source ip address
                                dst_ip,
                                //destination ip address
                                NULL,
                                0,
                                handle,
                                LIBNET_PTAG_INITIALIZER);

        if(tag == -1) {
            fprintf(stderr, "libnet_build_ipv4: %s\n", libnet_geterror(handle));
            libnet_destroy(handle);
            libnet_cq_destroy();
            exit(1);
        }//end if
...略

接下來就是IP表頭欄位的部分,第一個參數要填入封包大小,有IP表頭大小、ICMP Echo Request表頭大小以及Payload的長度,這樣才是從IP開始後的完整大小。第三個參數ID部分一樣我們隨機產生就好了。第六個參數Protocol填入IPPROTO_ICMP表示下一層協定是ICMP。第七個參數Checksum填入0表示自動算。第八和第九個參數分別填入自己和目標的IP Address。

...略
        //label
        memset(label, 0, sizeof(label));
        snprintf(label, sizeof(label), "%s", ip_ntoa(dst_ip));

        //push
        libnet_cq_add(handle, label);

    }//end if

封包建構完後,因為我們要使用libnet的連續構造封包功能,所以我們必須對每個封包編上一個標籤,我們直接使用IP Address當作標籤,封包構造整個完成後就使用函數libnet_cq_add()將libnet handle加入libnet handle鏈結串列內。


    //send packet
    for(handle = libnet_cq_head() ; libnet_cq_last() ; handle = libnet_cq_next()) {
        length = libnet_write(handle);
        if(length == -1) {
            fprintf(stderr, "libnet_write: %s\n", libnet_geterror(handle));
        }//end if
        else {
            printf("Sent: %d bytes, label: %s\n", length, libnet_cq_getlabel(handle));
        }//end else

        usleep(interval);
    }//end if

我們建構完的封包都在libnet handle鍊結串列內,這是一個全域變數,我們要使用函數libnet_cq_head()libnet_cq_last()libnet_cq_next()搭配for迴圈才能夠將每個封包取出來。取出封包後就直接呼叫函數libnet_write()來送出封包,一樣記得呼叫函數usleep()以免封包送出太快。


    //free
    libnet_cq_destroy();

最後我們要釋放的libnet handle資源都存在libnet的全域變數內,所以要呼叫函數libnet_cq_destroy()來釋放。

結語

這程式示範了如何使用libnet構造多個封包,不一定要全部都是ICMP封包,可以自行再構造別個協定再送出。

results matching ""

    No results matching ""