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封包,可以自行再構造別個協定再送出。