libpcap/dump_ethernet.c

終於要來講正題啦~

前面講了那麼東西是因為呢,網路世界的封包很多種不同協定,所以必須要先熟悉一下工具才能夠開始抓封包。

我們主要就是解析Ethernet:Ethernet(乙太網路),解析封包要從最底層分析上去。

Ethernet Header

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

Ethernet表頭算是所有封包表頭裡最簡單的一個協定。

Source Code

//
//  dump_ethernet.c
//  功能:分析Ethernet表頭。
//  Created by 聲華 陳 on 2016/01/09.
//

#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <string.h>

#define MAC_ADDRSTRLEN 2*6+5+1
void dump_ethernet(u_int32_t length, const u_char *content);
void pcap_callback(u_char *arg, const struct pcap_pkthdr *header, const u_char *content);
char *mac_ntoa(u_char *d);

int main(int argc, const char * argv[]) {
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *handle = NULL;
    char *device = "en0";

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

    //ethernet only
    if(pcap_datalink(handle) != DLT_EN10MB) {
        fprintf(stderr, "Sorry, Ethernet only.\n");
        pcap_close(handle);
        exit(1);
    }//end if

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

    //free
    pcap_close(handle);
    return 0;
}


char *mac_ntoa(u_char *d) {
    static char str[MAC_ADDRSTRLEN];

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

    return str;
}//end mac_ntoa

void pcap_callback(u_char *arg, const struct pcap_pkthdr *header, const u_char *content) {
    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, 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");
}//end pcap_callback

void dump_ethernet(u_int32_t length, const u_char *content) {
    struct ether_header *ethernet = (struct ether_header *)content;
    char dst_mac_addr[MAC_ADDRSTRLEN] = {};
    char src_mac_addr[MAC_ADDRSTRLEN] = {};
    u_int16_t type;

    //copy header
    strlcpy(dst_mac_addr, mac_ntoa(ethernet->ether_dhost), sizeof(dst_mac_addr));
    strlcpy(src_mac_addr, mac_ntoa(ethernet->ether_shost), sizeof(src_mac_addr));
    type = ntohs(ethernet->ether_type);

    //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_addr);
    printf("+-------------------------+-------------------------+-------------------------+\n");
    printf("| Source MAC Address:                                        %17s|\n", src_mac_addr);
    printf("+-------------------------+-------------------------+-------------------------+\n");
    if (type < 1500)
        printf("| Length:            %5u|\n", type);
    else
        printf("| Ethernet Type:    0x%04x|\n", type);
    printf("+-------------------------+\n");

    switch (type) {
        case ETHERTYPE_ARP:
            printf("Next is ARP\n");
            break;

        case ETHERTYPE_IP:
            printf("Next is IP\n");
            break;

        case ETHERTYPE_REVARP:
            printf("Next is RARP\n");
            break;

        case ETHERTYPE_IPV6:
            printf("Next is IPv6\n");
            break;

        default:
            printf("Next is %#06x", type);
            break;
    }//end switch

}//end dump_ethernet

結果

libpcap % ./dump_ethernet 
No. 1
    Time: 19:16:47.973246
    Length: 66 bytes
    Capture length: 66 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   f8:1a:67:53:f5:dc|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0800|
+-------------------------+
Next is IP

No. 2
    Time: 19:16:53.709819
    Length: 42 bytes
    Capture length: 42 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   ff:ff:ff:ff:ff:ff|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0806|
+-------------------------+
Next is ARP

分析

#define MAC_ADDRSTRLEN 2*6+5+1
void dump_ethernet(u_int32_t length, const u_char *content);
void pcap_callback(u_char *arg, const struct pcap_pkthdr *header, const u_char *content);
char *mac_ntoa(u_char *d);

這邊先宣告等等要用的函數,MAC_ADDRSTRLEN是MAC Address字串的長度,dump_ethernet()是解析Ethernet的函數,mac_ntoa()是將Bytes型態的MAC Address轉成字串。


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

來開啟en0抓封包。


    //ethernet only
    if(pcap_datalink(handle) != DLT_EN10MB) {
        fprintf(stderr, "Sorry, Ethernet only.\n");
        pcap_close(handle);
        exit(1);
    }//end if

檢查一下是不是Ethernet,因為我們只解析Ethernet,不是就結束。


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

    //free
    pcap_close(handle);

抓兩個封包就好了,結束記得釋放空間。


void pcap_callback(u_char *arg, const struct pcap_pkthdr *header, const u_char *content) {
    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, 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");
}//end pcap_callback

將封包的時戳、長度資訊轉成字串,這些前面都做過了,這裡最主要的是dump_ethernet()


重點的解析封包函數void dump_ethernet(u_int32_t length, const u_char *content);

void dump_ethernet(u_int32_t length, const u_char *content) {
    struct ether_header *ethernet = (struct ether_header *)content;
    char dst_mac_addr[MAC_ADDRSTRLEN] = {};
    char src_mac_addr[MAC_ADDRSTRLEN] = {};
    u_int16_t type;

    //copy header
    strlcpy(dst_mac_addr, mac_ntoa(ethernet->ether_dhost), sizeof(dst_mac_addr));
    strlcpy(src_mac_addr, mac_ntoa(ethernet->ether_shost), sizeof(src_mac_addr));
    type = ntohs(ethernet->ether_type);

首先我們宣告struct ether_header *ethernet指標來指向封包開頭。

以結果的No. 1封包來講,原本是這樣:

content
1. 0xf82. 0x1a3. 0x674. 0x535. 0xf56. 0xdc...

ethernet變數指向content變數變這樣:

content ethernet
↓ ↓
1. 0xf82. 0x1a3. 0x674. 0x535. 0xf56. 0xdc...

我們來看看struct ether_header結構:

struct    ether_header {
    u_char    ether_dhost[ETHER_ADDR_LEN];
    u_char    ether_shost[ETHER_ADDR_LEN];
    u_short    ether_type;
};

所以再來考慮每個結構成員,它們會指向:

ethernet->ether_dhost
ethernet->ether_shost
1. 0xf82. 0x1a3. 0x674. 0x535. 0xf56. 0xdc7. 0x6c8. 0x409. 0x0810. 0xbc
ethernet->ether_type
11. 0xae12. 0x9813. 0x0814. 0x00

這樣就可以快速的利用ethernet裡面的成員取得各個表頭欄位的資料了,解析封包都是使用這樣的技巧來分析。


    //copy header
    strlcpy(dst_mac_addr, mac_ntoa(ethernet->ether_dhost), sizeof(dst_mac_addr));
    strlcpy(src_mac_addr, mac_ntoa(ethernet->ether_shost), sizeof(src_mac_addr));
    type = ntohs(ethernet->ether_type);

接下來就將資料轉成字串,這裡使用strlcpy()來複製(比較安全),mac_ntoa()傳回MAC Address的字串。ether_type是兩個byte的資料且是封包轉成電腦順序,所以用ntohs()

那可能會想到說為何MAC Address卻不用轉,因為MAC Address的結構宣告是6個分開的byte,而不像變數type是一個2個byte。


    //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_addr);
    printf("+-------------------------+-------------------------+-------------------------+\n");
    printf("| Source MAC Address:                                        %17s|\n", src_mac_addr);
    printf("+-------------------------+-------------------------+-------------------------+\n");
    if (type < 1500)
        printf("| Length:            %5u|\n", type);
    else
        printf("| Ethernet Type:    0x%04x|\n", type);
    printf("+-------------------------+\n");

把封包畫出來吧。


    switch (type) {
        case ETHERTYPE_ARP:
            printf("Next is ARP\n");
            break;

        case ETHERTYPE_IP:
            printf("Next is IP\n");
            break;

        case ETHERTYPE_REVARP:
            printf("Next is RARP\n");
            break;

        case ETHERTYPE_IPV6:
            printf("Next is IPv6\n");
            break;

        default:
            printf("Next is %#06x", type);
            break;
    }//end switch

}//end dump_ethernet

下一層協定是用type記錄,這裡雖然都用#define的保留字,不過還是要稍微記一下幾個常見的:

#define ETHERTYPE_IP        0x0800    /* IP protocol */
#define ETHERTYPE_ARP       0x0806    /* Addr. resolution protocol */
#define ETHERTYPE_REVARP    0x8035    /* reverse Addr. resolution protocol */
#define ETHERTYPE_IPV6      0x86dd    /* IPv6 */
#define ETHERTYPE_LOOPBACK  0x9000    /* used to test interfaces */


char *mac_ntoa(u_char *d) {
    static char str[MAC_ADDRSTRLEN];

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

    return str;
}//end mac_ntoa

這個函數就是將Bytes型態的Mac Address轉成字串,雖然系統也有提供類似的函數:ether_ntoa(),但是輸出的字串沒有補齊長度不是我們要的樣子,所以就自己寫。

為什麼宣告成static這裡已經解釋了呦。

結語

解析封包其實就是一直使用指標指向對的位置來取得表頭欄位資料,剩下的就是如何去"翻譯"它的資料,只要把這的原理弄懂,指標會變得很厲害。

results matching ""

    No results matching ""