libpcap/dump_arp.c

接下來要解析ARP:Address Resolution Protocol(地址解析協定)

程式由libpcap/dump_ethernet.c修改。

ARP Header

  • Hardware Type:2 bytes。
  • Protocol Type:2 bytes。
  • Hardware Length:1 byte。
  • Protocol Length:1 byte。
  • Operation Code:2 bytes。
  • Sender Hardware Address:硬體地址長度 bytes。
  • Sender Protocol Address:協定地址長度 bytes。
  • Target Hardware Address:硬體地址長度 bytes。
  • Target Protocol Address:協定地址長度 bytes。
  • 長度:8 + 硬體地址長度×2 + 協定地址長度×2 bytes

表頭橘色部分長度是依照Hardware Length以及Protocol Length決定的,那麼以Ethernet來講,硬體地址長度是6(MAC Address),協定地址長度是4(IP Address),所以Ethernet的ARP封包長度為:8+6\*2+4\*2 = 28 bytes

這邊要講的是ARP不只有Ethernet在使用,從全名Address Resolution Protocol就可以知道跟Ethernet一點關係都沒有,因為我們第一次學到ARP協定是在Ethernet網路,所以很多人會搞混。

Source Code

//
//  dump_arp.c
//  功能:分析ARP表頭。
//  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>
#include <net/if_arp.h>
#include <netinet/if_ether.h>

#define MAC_ADDRSTRLEN 2*6+5+1
void dump_ethernet(u_int32_t length, const u_char *content);
void dump_arp(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);
char *ip_ntoa(void *i);

int main(int argc, const char * argv[]) {
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *handle = NULL;
    char *device = "en0";
    bpf_u_int32 net, mask;
    struct bpf_program fcode;

    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

    //get network and mask
    if(-1 == pcap_lookupnet("en0", &net, &mask, errbuf)) {
        fprintf(stderr, "pcap_lookupnet: %s\n", errbuf);
        pcap_close(handle);
        exit(1);
    }//end if

    //compile filter
    if(-1 == pcap_compile(handle, &fcode, "arp", 1, mask)) {
        fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        exit(1);
    }//end if

    //set filter
    if(-1 == pcap_setfilter(handle, &fcode)) {
        fprintf(stderr, "pcap_pcap_setfilter: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        exit(1);
    }

    //free code
    pcap_freecode(&fcode);


    //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

char *ip_ntoa(void *i) {
    static char str[INET_ADDRSTRLEN];

    inet_ntop(AF_INET, i, str, sizeof(str));

    return str;
}//end ip_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:
            dump_arp(length, content);
            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

void dump_arp(u_int32_t length, const u_char *content) {
    struct ether_arp *arp = (struct ether_arp *)(content + ETHER_HDR_LEN);
    u_short hardware_type = ntohs(arp->ea_hdr.ar_hrd);
    u_short protocol_type = ntohs(arp->ea_hdr.ar_pro);
    u_char hardware_len = arp->ea_hdr.ar_hln;
    u_char protocol_len = arp->ea_hdr.ar_pln;
    u_short operation = ntohs(arp->ea_hdr.ar_op);

    static char *arp_op_name[] = {
        "Undefine",
        "(ARP Request)",
        "(ARP Reply)",
        "(RARP Request)",
        "(RARP Reply)"
    }; //arp option type

    if(operation < 0 || sizeof(arp_op_name)/sizeof(arp_op_name[0]) < operation)
        operation = 0;

    printf("Protocol: ARP\n");
    printf("+-------------------------+-------------------------+\n");
    printf("| Hard Type: %2u%-11s| Protocol:0x%04x%-9s|\n",
           hardware_type,
           (hardware_type == ARPHRD_ETHER) ? "(Ethernet)" : "(Not Ether)",
           protocol_type,
           (protocol_type == ETHERTYPE_IP) ? "(IP)" : "(Not IP)");
    printf("+------------+------------+-------------------------+\n");
    printf("| HardLen:%3u| Addr Len:%2u| OP: %4d%16s|\n",
           hardware_len, protocol_len, operation, arp_op_name[operation]);
    printf("+------------+------------+-------------------------+-------------------------+\n");
    printf("| Source MAC Address:                                        %17s|\n", mac_ntoa(arp->arp_sha));
    printf("+---------------------------------------------------+-------------------------+\n");
    printf("| Source IP Address:                 %15s|\n", ip_ntoa(arp->arp_spa));
    printf("+---------------------------------------------------+-------------------------+\n");
    printf("| Destination MAC Address:                                   %17s|\n", mac_ntoa(arp->arp_tha));
    printf("+---------------------------------------------------+-------------------------+\n");
    printf("| Destination IP Address:            %15s|\n", ip_ntoa(arp->arp_tpa));
    printf("+---------------------------------------------------+\n");
}//end dump_arp

結果

libpcap % ./dump_arp
No. 1
    Time: 14:11:18.739926
    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|
+-------------------------+
Protocol: ARP
+-------------------------+-------------------------+
| Hard Type:  1(Ethernet) | Protocol:0x0800(IP)     |
+------------+------------+-------------------------+
| HardLen:  6| Addr Len: 4| OP:    1   (ARP Request)|
+------------+------------+-------------------------+-------------------------+
| Source MAC Address:                                        6c:40:08:bc:ae:98|
+---------------------------------------------------+-------------------------+
| Source IP Address:                   192.168.1.100|
+---------------------------------------------------+-------------------------+
| Destination MAC Address:                                   00:00:00:00:00:00|
+---------------------------------------------------+-------------------------+
| Destination IP Address:                192.168.1.1|
+---------------------------------------------------+

No. 2
    Time: 14:11:18.741672
    Length: 42 bytes
    Capture length: 42 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        f8:1a:67:53:f5:dc|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0806|
+-------------------------+
Protocol: ARP
+-------------------------+-------------------------+
| Hard Type:  1(Ethernet) | Protocol:0x0800(IP)     |
+------------+------------+-------------------------+
| HardLen:  6| Addr Len: 4| OP:    2     (ARP Reply)|
+------------+------------+-------------------------+-------------------------+
| Source MAC Address:                                        f8:1a:67:53:f5:dc|
+---------------------------------------------------+-------------------------+
| Source IP Address:                     192.168.1.1|
+---------------------------------------------------+-------------------------+
| Destination MAC Address:                                   6c:40:08:bc:ae:98|
+---------------------------------------------------+-------------------------+
| Destination IP Address:              192.168.1.100|
+---------------------------------------------------+

分析

直接來講與libpcap/dump_ethernet.c不同的地方。


void dump_arp(u_int32_t length, const u_char *content);
char *ip_ntoa(void *i);

多宣告dump_arp()來處理ARP封包。而ip_ntoa()是將Bytes型態的IP Address轉成字串。


    //get network and mask
    if(-1 == pcap_lookupnet("en0", &net, &mask, errbuf)) {
        fprintf(stderr, "pcap_lookupnet: %s\n", errbuf);
        pcap_close(handle);
        exit(1);
    }//end if

    //compile filter
    if(-1 == pcap_compile(handle, &fcode, "arp", 1, mask)) {
        fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        exit(1);
    }//end if

    //set filter
    if(-1 == pcap_setfilter(handle, &fcode)) {
        fprintf(stderr, "pcap_pcap_setfilter: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        exit(1);
    }

    //free code
    pcap_freecode(&fcode);

這邊用過濾器"arp" 只抓ARP封包,因為ARP封包正常情況不會很頻繁發送。


void dump_ethernet(u_int32_t length, const u_char *content) {
...略
    switch (type) {
        case ETHERTYPE_ARP:
            dump_arp(length, content);
            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

當Ethernet的type是ARP的時候,就呼叫dump_arp()來分析封包。


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

void dump_arp(u_int32_t length, const u_char *content) {
    struct ether_arp *arp = (struct ether_arp *)(content + ETHER_HDR_LEN);
    u_short hardware_type = ntohs(arp->ea_hdr.ar_hrd);
    u_short protocol_type = ntohs(arp->ea_hdr.ar_pro);
    u_char hardware_len = arp->ea_hdr.ar_hln;
    u_char protocol_len = arp->ea_hdr.ar_pln;
    u_short operation = ntohs(arp->ea_hdr.ar_op);

    static char *arp_op_name[] = {
        "Undefine",
        "(ARP Request)",
        "(ARP Reply)",
        "(RARP Request)",
        "(RARP Reply)"
    }; //arp option type

    if(operation < 0 || sizeof(arp_op_name)/sizeof(arp_op_name[0]) < operation)
        operation = 0;

首先宣告struct ether_arp *arp來指向封包的第14個byte位置(ETHER_HDR_LEN),稍微來畫圖。

EthernetEthernetEthernetARPARPARPARP
12. ...13. 0x0814. 0x0615. 0x0016. 0x0117. 0x0018. ...

因為Ethernet的Type是0x0806,表示說接下來的協定是ARP,所以當Ethernet表頭資料結束後,接著就是ARP的表頭了。


    u_short hardware_type = ntohs(arp->ea_hdr.ar_hrd);
    u_short protocol_type = ntohs(arp->ea_hdr.ar_pro);
    u_char hardware_len = arp->ea_hdr.ar_hln;
    u_char protocol_len = arp->ea_hdr.ar_pln;
    u_short operation = ntohs(arp->ea_hdr.ar_op);

接下來就根據長度來取出資料,記得兩個byte的資料都要用ntohs()轉換。


    static char *arp_op_name[] = {
        "Undefine",
        "(ARP Request)",
        "(ARP Reply)",
        "(RARP Request)",
        "(RARP Reply)"
    }; //arp option type

    if(operation < 0 || sizeof(arp_op_name)/sizeof(arp_op_name[0]) < operation)
        operation = 0;

這裡的arp_op_name變數宣告的ARP類型,是根據每個ARP的Operation Code的數值來宣告對應的Index位置,例如ARP Request的Opeartion Code為1ARP Reply的Opeartion Code為2,接來的if判斷只是確保不會超過Index範圍。像這樣的做法也常見在一些核心指令的原始碼內。


    printf("Protocol: ARP\n");
    printf("+-------------------------+-------------------------+\n");
    printf("| Hard Type: %2u%-11s| Protocol:0x%04x%-9s|\n",
           hardware_type,
           (hardware_type == ARPHRD_ETHER) ? "(Ethernet)" : "(Not Ether)",
           protocol_type,
           (protocol_type == ETHERTYPE_IP) ? "(IP)" : "(Not IP)");
    printf("+------------+------------+-------------------------+\n");
    printf("| HardLen:%3u| Addr Len:%2u| OP: %4d%16s|\n",
           hardware_len, protocol_len, operation, arp_op_name[operation]);
    printf("+------------+------------+-------------------------+-------------------------+\n");
    printf("| Source MAC Address:                                        %17s|\n", mac_ntoa(arp->arp_sha));
    printf("+---------------------------------------------------+-------------------------+\n");
    printf("| Source IP Address:                 %15s|\n", ip_ntoa(arp->arp_spa));
    printf("+---------------------------------------------------+-------------------------+\n");
    printf("| Destination MAC Address:                                   %17s|\n", mac_ntoa(arp->arp_tha));
    printf("+---------------------------------------------------+-------------------------+\n");
    printf("| Destination IP Address:            %15s|\n", ip_ntoa(arp->arp_tpa));
    printf("+---------------------------------------------------+\n");

最後就是畫出來吧。


char *ip_ntoa(void *i) {
    static char str[INET_ADDRSTRLEN + 1];

    inet_ntop(AF_INET, i, str, sizeof(str));

    return str;
}//end ip_ntoa

這邊使用inet_ntop()來轉換IP Address。

結語

解析封包就是重複這樣的方式解析,不過還是得多練習。

results matching ""

    No results matching ""