2016年11月8日 星期二

libpcap - Dump Ethernet frame(8)


封包解析要從最底層解析上去,先從乙太網路的Ethernet frame開始。


  • Destination MAC Address:6 bytes。
  • Source MAC Address:6 bytes。
  • Type:2 bytes。
  • 長度:14 bytes。



一開始一樣要include一些ethernet相關的header。
#ifndef __linux
#include <net/if.h>
#include <netinet/in.h>
#include <net/if_dl.h>
#include <net/ethernet.h>
#else /* if BSD */
#define __FAVOR_BSD
#include <linux/if_ether.h>
#include <netpacket/packet.h>
#include <linux/if_link.h>
#include <netinet/ether.h>
#endif /* if linux */

在核心為linux時候,define了一個marcos:__FAVOR_BSD,在BSD核心系統中定義的封包header比較常用,但在linux預設並不是這些表頭欄位,但是只要define這個marcos就可以與BSD相容了。


一樣從預設的device抓封包,抓三個就好。
char errbuf[PCAP_ERRBUF_SIZE];
char *device = NULL;

//get default interface name
device = pcap_lookupdev(errbuf);
if(!device) {
    fprintf(stderr, "pcap_lookupdev(): %s\n", errbuf);
    exit(1);
}//end if

printf("Sniffing: %s\n", device);

pcap_t *handle = pcap_open_live(device, 65535, 1, 1, errbuf);
if(!handle) {
    fprintf(stderr, "pcap_open_live(): %s\n", errbuf);
    exit(1);
}//end if

//start capture
pcap_loop(handle, 3, pcap_callback, NULL);


當抓到封包時候一樣呼叫callback函數。
static int d = 0;
struct tm *ltime;
char timestr[16];
time_t local_tv_sec;

local_tv_sec = header->ts.tv_sec;
ltime = localtime(&local_tv_sec);
strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);

printf("No. %d\n", ++d);

//print header
printf("\tTime: %s.%.6d\n", timestr, (int)header->ts.tv_usec);
printf("\tLength: %d bytes\n", header->len);
printf("\tCapture length: %d bytes\n", header->caplen);

//dump ethernet
dump_ethernet(header->caplen, content);

printf("\n");

一樣都是解析時戳、封包長度,重點是接下來的解析函數dump_ethernet()


函數dump_ethernet()是我們拿來解析ethernet frame表頭的函數;先宣告出表頭的欄位,目的mac和來源mac都是字串格式,長度是macros的,type是無號的16 bit整數。
char dst_mac[MAC_ADDRSTRLEN] = {0};
char src_mac[MAC_ADDRSTRLEN] = {0};
u_int16_t type;

巨集MAC_ADDRSTRLEN宣告:
#define MAC_ADDRSTRLEN 2*6+5+1


接著宣告結構指標struct ether_header *來指向封包本體content。
struct ether_header *ethernet = (struct ether_header *)content;

結構struct ether_header宣告:
/*
 * The number of bytes in an ethernet (MAC) address.
 */
#define    ETHER_ADDR_LEN        6
/*
 * Structure of a 10Mb/s Ethernet header.
 */
struct    ether_header {
    u_char    ether_dhost[ETHER_ADDR_LEN];
    u_char    ether_shost[ETHER_ADDR_LEN];
    u_short    ether_type;
};

這邊直接放上我之前在別的地方寫的教學。




指向封包本體後,就可以直接使用變數ethernet直接來取得各個欄位。


接著就把指向的data儲存到剛剛宣告的幾個變數內。
//copy header
snprintf(dst_mac, sizeof(dst_mac), "%s", mac_ntoa(ethernet->ether_dhost));
snprintf(src_mac, sizeof(src_mac), "%s", mac_ntoa(ethernet->ether_shost));

來源和目的mac本身是十六進位資料,需要轉成字串後再用函數snprintf()儲存進變數。

函數mac_ntoa()是將十六進位轉成字串格式,在之前有用過函數ether_ntoa();雖然功能一樣,但是ether_ntoa()會有回傳值被修改的問題,所以我們重新改寫這個函數。


要傳回一個字串最常用的方法就是傳回一個由函數malloc()相關的函數,所分配的合法空間;但是缺點就是需要呼叫對應的函數free()來回收。

函數ether_ntoa()的作法就傳回一個static變數,一個程式有五個區塊Text、Data、BSS、Heap以及Stack。
  • Text:程式碼(編譯後的機器碼)放置區。
  • Data:全域變數,以及區塊(Block)內的static變數。
  • BSS:未初始化的全域變數。
  • Heap:malloc()、calloc()和realloc()配置的記憶體。
  • Stack:區域變數、函數返回點、參數...等。

static變數是被放置在Data區域,所以它並不會因為函數返回而被銷毀(Stack區域會銷毀);但函數ether_ntoa()只有一個buffer,也就是說每次傳回的位置都相同,所以傳回後沒有馬上使用的話,只要再呼叫一次就會把之前的資訊給覆蓋掉。

所以我們根據傳回static變數的方法,增加buffer的功能。

static const char *mac_ntoa(u_int8_t *d) {
#define STR_BUF 16
    static char mac[STR_BUF][MAC_ADDRSTRLEN];
    static int which = -1;

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

    memset(mac[which], 0, MAC_ADDRSTRLEN);
    snprintf(mac[which], sizeof(mac[which]), "%02x:%02x:%02x:%02x:%02x:%02x", d[0], d[1], d[2], d[3], d[4], d[5]);

    return mac[which];
}//end mac_ntoa

Line: 64宣告一個傳回用的變數,第一維有16個buffer空間,第二維是mac地址長度;Line: 65是目前使用到第幾個buffer。

Line: 67負責循環整個buffer,所以變數which的值會一直在0-15循環。

接著Line: 69-72就將對應的空間給清空再格式化字串,最後再傳回。

這個做法增加了buffer機制,所以不用在傳回後馬上複製,但是在呼叫了16次後,還是會回到第一個buffer空間。


type = ntohs(ethernet->ether_type);

函數ntohs是將網路順序轉成主機順序的函數。
相關函數原型:
uint32_t htonl(uint32_t hostlong);
uint32_t ntohl(uint32_t netlong);
uint16_t htons(uint16_t hostshort);
uint16_t ntohs(uint16_t netshort);

有兩種byte order:Network byte order和Host byte order,一般來講Network byte order是Big‐Endian,而Host byte order則通常是Little‐Endian,這是電腦在儲存資料byte放的順序方式。

剛學程式的時候,一定會有一種題目這像這樣:
int x = 0x12345678;
char c = x;
c = ?

答案c=0x78,因為電腦是Host byte order,是從後面取的資料。

但是在網路上可能會跟常理有點顛倒。同樣一個資料0x12ac,在封包內為:0x12 0xac;但是我們直接用指標指向這段資料後,列印出來的資料卻是0xac12。

幸運的是我們不必了解我們系統到底是屬於哪種Byte order,我們只要利用htonl()ntohl()htons()以及ntohs()即可完成,這四個函數的記法很簡單,h表示host,n表示network,l表示四個byte的資料(unsigned long),s表示兩個byte的資料(unsigned short)。

所以今天如果要從封包內讀取一個兩個byte的資料,就要使用函數ntohs()(network to host short)來轉換。


最後就將封包的內容給畫出來:
//print
if(type <= 1500)
    printf("IEEE 802.3 Ethernet Frame:\n");
else
    printf("Ethernet Frame:\n");

printf("+-------------------------+-------------------------+-------------------------+\n");
printf("| Destination MAC Address:                                   %17s|\n", dst_mac);
printf("+-------------------------+-------------------------+-------------------------+\n");
printf("| Source MAC Address:                                        %17s|\n", src_mac);
printf("+-------------------------+-------------------------+-------------------------+\n");
if (type < 1500)
    printf("| Length:            %5u|\n", type);
else
    printf("| Ethernet Type:    0x%04x|\n", type);
printf("+-------------------------+\n");

printf("Next protocol is ");
switch (type) {
    case ETHERTYPE_ARP:
        printf("ARP\n");
        break;
        
    case ETHERTYPE_IP:
        printf("IP\n");
        break;
        
    case ETHERTYPE_REVARP:
        printf("RARP\n");
        break;
        
    case ETHERTYPE_IPV6:
        printf("IPv6\n");
        break;
        
    default:
        printf("%#06x\n", type);
        break;
}//end switch


編譯:
libpcap % gcc -I/usr/local/opt/libpcap/include -Wall -std=gnu99 -L/usr/local/opt/libpcap/lib -lpcap dump-ethernet.c -o dump-ethernet


執行結果(Mac OS X):
libpcap % ./dump-ethernet 
Sniffing: en0
No. 1
 Time: 14:22:29.295935
 Length: 60 bytes
 Capture length: 60 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   ff:ff:ff:ff:ff:ff|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        28:28:5d:9b:3d:9e|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x8899|
+-------------------------+
Next protocol is 0x8899

No. 2
 Time: 14:22:31.165444
 Length: 145 bytes
 Capture length: 145 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   d8:fe:e3:a4:d3:78|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0800|
+-------------------------+
Next protocol is IP

No. 3
 Time: 14:22:31.166128
 Length: 112 bytes
 Capture length: 112 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   d8:fe:e3:a4:d3:78|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0800|
+-------------------------+
Next protocol is IP



執行結果(CentOS):
[root@tutu libpcap]# ./dump-ethernet 
Sniffing: eth0
No. 1
 Time: 14:23:35.698669
 Length: 54 bytes
 Capture length: 54 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                       1:0:5e:0:0:16|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                          0:c:29:b0:81:f7|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0800|
+-------------------------+
Next is IP

No. 2
 Time: 14:23:36.098591
 Length: 42 bytes
 Capture length: 42 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   ff:ff:ff:ff:ff:ff|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                          0:c:29:b0:81:f7|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0806|
+-------------------------+
Next is ARP

No. 3
 Time: 14:23:36.104002
 Length: 60 bytes
 Capture length: 60 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                     0:c:29:b0:81:f7|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        d8:fe:e3:a4:d3:78|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0806|
+-------------------------+
Next is ARP



Source code on Github

沒有留言:

張貼留言