2016年11月9日 星期三

libpcap - Dump ARP frame(9)



  • 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使用而已。


要多include的header:
#include <net/if_arp.h>


一樣開啟預設的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


為了方便解析,這次加入了過濾器(BPF filter)的方式,只對arp封包有興趣。

宣告會用到的變數。
//generate bpf filter
bpf_u_int32 net, mask;
struct bpf_program fcode;


產生過濾器的時候,會需要netmask,所以先呼叫函數pcap_lookupnet()來取得netmask。
//get network and mask
if(-1 == pcap_lookupnet(device, &net, &mask, errbuf)) {
    fprintf(stderr, "pcap_lookupnet(): %s\n", errbuf);
    mask = PCAP_NETMASK_UNKNOWN;
}//end if

但是netmask不是一定需要的,所以如果當取得失敗的時候,使用marcos PCAP_NETMASK_UNKNOWN當作netmask使用就好了。

巨集PCAP_NETMASK_UNKNOWN宣告:
/*
 * Value to pass to pcap_compile() as the netmask if you don't know what
 * the netmask is.
 */
#define PCAP_NETMASK_UNKNOWN 0xffffffff


接著來產生過濾器。
//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

函數pcap_compile()能夠將filter expression轉成電腦使用的結構體,這邊使用"arp"表示只要arp封包。

過濾器表達式(filter expression)教學

函數pcap_compile()原型:
int pcap_compile(pcap_t *p, struct bpf_program *program, char *buf, int optimize, bpf_u_int32 mask);
  • 返回值:成功傳回0,失敗傳回-1,錯誤訊息從pcap_geterr()取得。
  • 參數:p一個libpcap handle。program最後過濾器結果儲存的結構指標。buf過濾器表達式。optimize是否要最佳化過濾器表達式。mask要設定的那個device的遮罩netmask,可以使用PCAP_NETMASK_UNKNOWN代替。
  • 功能:將過濾器表達式轉成核心能夠使用過濾器結構。


接著將產生的結構體設定在handle上。
//set filter
if(-1 == pcap_setfilter(handle, &fcode)) {
    fprintf(stderr, "pcap_pcap_setfilter(): %s\n", pcap_geterr(handle));
    pcap_freecode(&fcode);
    pcap_close(handle);
    exit(1);
}//end if

函數pcap_setfilter()原型:
int pcap_setfilter(pcap_t *p, struct bpf_program *program);
  • 返回值:成功傳回0,失敗傳回-1,錯誤訊息從pcap_geterr()取得。
  • 參數:p一個libpcap handle。program過濾器結構指標。
  • 功能:設定過濾器到libpcap handle上。


使用完過濾器的結構體之後,不需要的時候要釋放掉。
//free bpf code
pcap_freecode(&fcode);

函數pcap_freecode()原型:
void pcap_freecode(struct bpf_program *program);
  • 參數:program要釋放的過濾器結構。
  • 功能:釋放從pcap_compile()產生的過濾器結構。


接著只抓兩個封包。
//start capture
pcap_loop(handle, 2, pcap_callback, NULL);


解析ethernet過程中,當下一層的類型是0x0806(或marcos ETHERTYPE_ARP),就呼叫函數dump_arp()來解析arp封包。
printf("Next protocol is ");
switch (type) {
    case ETHERTYPE_ARP:
        printf("ARP\n");
        dump_arp(length, content);
        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


函數dump_arp()是用來解析arp封包的函數,一開始先宣告一些封包表頭欄位。
u_short hdr_type;
u_short pro_type;
u_char hdr_len;
u_char pro_len;
u_short op;
char sender_mac[MAC_ADDRSTRLEN] = {0};
char sender_ip[INET_ADDRSTRLEN] = {0};
char target_mac[MAC_ADDRSTRLEN] = {0};
char target_ip[INET_ADDRSTRLEN] = {0};


struct ether_arp *arp = (struct ether_arp *)(content + ETHER_HDR_LEN);

結構struct ether_arp是ethernet常用的arp封包表頭結構體;起始位置當然就是ethernet表頭後的位置,所以是content + 14(或marcos ETHER_HDR_LEN)。

結構struct ether_arp宣告:
/*
 * Ethernet Address Resolution Protocol.
 *
 * See RFC 826 for protocol description.  Structure below is adapted
 * to resolving internet addresses.  Field names used correspond to
 * RFC 826.
 */
struct    ether_arp {
    struct    arphdr ea_hdr;    /* fixed-size header */
    u_char    arp_sha[ETHER_ADDR_LEN];    /* sender hardware address */
    u_char    arp_spa[4];    /* sender protocol address */
    u_char    arp_tha[ETHER_ADDR_LEN];    /* target hardware address */
    u_char    arp_tpa[4];    /* target protocol address */
};
#define    arp_hrd    ea_hdr.ar_hrd
#define    arp_pro    ea_hdr.ar_pro
#define    arp_hln    ea_hdr.ar_hln
#define    arp_pln    ea_hdr.ar_pln
#define    arp_op    ea_hdr.ar_op

結構struct arphdr宣告:
/*
 * Address Resolution Protocol.
 *
 * See RFC 826 for protocol description.  ARP packets are variable
 * in size; the arphdr structure defines the fixed-length portion.
 * Protocol type values are the same as those for 10 Mb/s Ethernet.
 * It is followed by the variable-sized fields ar_sha, arp_spa,
 * arp_tha and arp_tpa in that order, according to the lengths
 * specified.  Field names used correspond to RFC 826.
 */
struct    arphdr {
    u_short    ar_hrd;        /* format of hardware address */
#define ARPHRD_ETHER     1    /* ethernet hardware format */
#define ARPHRD_IEEE802    6    /* token-ring hardware format */
#define ARPHRD_FRELAY     15    /* frame relay hardware format */
#define ARPHRD_IEEE1394    24    /* IEEE1394 hardware address */
#define ARPHRD_IEEE1394_EUI64 27 /* IEEE1394 EUI-64 */
    u_short    ar_pro;        /* format of protocol address */
    u_char    ar_hln;        /* length of hardware address */
    u_char    ar_pln;        /* length of protocol address */
    u_short    ar_op;        /* one of: */
#define    ARPOP_REQUEST    1    /* request to resolve address */
#define    ARPOP_REPLY    2    /* response to previous request */
#define    ARPOP_REVREQUEST 3    /* request protocol address given hardware */
#define    ARPOP_REVREPLY    4    /* response giving protocol address */
#define ARPOP_INVREQUEST 8     /* request to identify peer */
#define ARPOP_INVREPLY    9    /* response identifying peer */
/*
 * The remaining fields are variable in size,
 * according to the sizes above.
 */
#ifdef COMMENT_ONLY
    u_char    ar_sha[];    /* sender hardware address */
    u_char    ar_spa[];    /* sender protocol address */
    u_char    ar_tha[];    /* target hardware address */
    u_char    ar_tpa[];    /* target protocol address */
#endif
};

這邊有個要注意的地方:結構struct ether_arp的arp_spa和arp_tpa被宣告成u_char [4],這兩個是指ip地址,常理應該會宣告成無號的32 bit整數,雖然都是4個byte,但是這樣隱含的問題就是結構的對齊(Struct Member Alignment)。

這兩個成員前面都是u_char,預設情況會結構的成員只要資料型態不同就會試著對齊到4的倍數,所以結果會有問題。

當然也可以取消掉對齊,在gcc編譯器使用__attribute__((packed))宣告。

像是:
struct    my_ether_arp {
    struct    arphdr ea_hdr;    /* fixed-size header */
    u_char    arp_sha[ETHER_ADDR_LEN];    /* sender hardware address */
    u_int32_t arp_spa;    /* sender protocol address */
    u_char    arp_tha[ETHER_ADDR_LEN];    /* target hardware address */
    u_int32_t arp_tpa[4];    /* target protocol address */
} __attribute__((packed));

Windows的cl編譯器(VC++)則是:
#pragma pack(push, 1)
struct    my_ether_arp {
    struct    arphdr ea_hdr;    /* fixed-size header */
    u_char    arp_sha[ETHER_ADDR_LEN];    /* sender hardware address */
    u_int32_t arp_spa;    /* sender protocol address */
    u_char    arp_tha[ETHER_ADDR_LEN];    /* target hardware address */
    u_int32_t arp_tpa[4];    /* target protocol address */
};
#pragma pack(pop)

通用解法:
#ifdef _WIN32
#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
#else
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
#endif

使用方法:
PACK (
struct    my_ether_arp {
    struct    arphdr ea_hdr;    /* fixed-size header */
    u_char    arp_sha[ETHER_ADDR_LEN];    /* sender hardware address */
    u_int32_t arp_spa;    /* sender protocol address */
    u_char    arp_tha[ETHER_ADDR_LEN];    /* target hardware address */
    u_int32_t arp_tpa[4];    /* target protocol address */
});

這樣就不需要特別判斷使用哪個編譯器了。


接著就將標頭欄位複製到變數內。
//copy header
hdr_type = ntohs(arp->arp_hrd);
pro_type = ntohs(arp->arp_pro);
hdr_len = arp->arp_hln;
pro_len = arp->arp_pln;
op = ntohs(arp->arp_op);
snprintf(sender_mac, sizeof(sender_mac), "%s", mac_ntoa(arp->arp_sha));
snprintf(sender_ip, sizeof(sender_ip), "%s", ip_ntoa(arp->arp_spa));
snprintf(target_mac, sizeof(target_mac), "%s", mac_ntoa(arp->arp_tha));
snprintf(target_ip, sizeof(target_ip), "%s", ip_ntoa(arp->arp_tpa));

其中函數ip_ntoa()是將網路地址格式轉成字串格式。


其中做法就跟函數mac_ntoa()一樣。

static const char *ip_ntoa(void *i) {
    static char ip[STR_BUF][INET_ADDRSTRLEN];
    static int which = -1;

    which = (which + 1 == STR_BUF ? 0 : which + 1);
    
    memset(ip[which], 0, INET_ADDRSTRLEN);
    inet_ntop(AF_INET, i, ip[which], sizeof(ip[which]));
    
    return ip[which];
}//end ip_ntoa

只是用函數inet_ntop()來將網路地址轉成字串格式;就不檢查回傳值了。


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

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

宣告變數arp_op_name是根據每個arp的operation code的數值來宣告對應的index位置,例如arp request的operation code為1,arp reply的operation code為2,接來的if判斷只是確保不會超過index範圍。


最後就將封包列印出來。
//print
printf("Protocol: ARP\n");
printf("+-------------------------+-------------------------+\n");
printf("| Hard Type: %2u%-11s| Protocol: %#06x%-8s|\n",
       hdr_type, (hdr_type == ARPHRD_ETHER) ? "(Ethernet)" : "(Not Ether)",
       pro_type, (pro_type == ETHERTYPE_IP) ? "(IP)" : "(Not IP)");
printf("+------------+------------+-------------------------+\n");
printf("| Hard Len:%2u| Addr Len:%2u| OP: %4d%16s|\n",
       hdr_len, pro_len, op, arp_op_name[op]);
printf("+------------+------------+-------------------------+-------------------------+\n");
printf("| Sender MAC Address:                                        %17s|\n", sender_mac);
printf("+---------------------------------------------------+-------------------------+\n");
printf("| Sender IP Address:                 %15s|\n", sender_ip);
printf("+---------------------------------------------------+-------------------------+\n");
printf("| Target MAC Address:                                        %17s|\n", target_mac);
printf("+---------------------------------------------------+-------------------------+\n");
printf("| Target IP Address:                 %15s|\n", target_ip);
printf("+---------------------------------------------------+\n");


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


執行結果(Mac OS X):
libpcap % ./dump-arp 
Sniffing: en0
No. 1
 Time: 16:31:08.240019
 Length: 58 bytes
 Capture length: 58 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 protocol is ARP
Protocol: ARP
+-------------------------+-------------------------+
| Hard Type:  1(Ethernet) | Protocol: 0x0800(IP)    |
+------------+------------+-------------------------+
| Hard Len: 6| Addr Len: 4| OP:    1   (ARP Request)|
+------------+------------+-------------------------+-------------------------+
| Sender MAC Address:                                        6c:40:08:bc:ae:98|
+---------------------------------------------------+-------------------------+
| Sender IP Address:                    192.168.1.50|
+---------------------------------------------------+-------------------------+
| Target MAC Address:                                        00:00:00:00:00:00|
+---------------------------------------------------+-------------------------+
| Target IP Address:                     192.168.1.1|
+---------------------------------------------------+

No. 2
 Time: 16:31:08.246209
 Length: 42 bytes
 Capture length: 42 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        d8:fe:e3:a4:d3:78|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0806|
+-------------------------+
Next protocol is ARP
Protocol: ARP
+-------------------------+-------------------------+
| Hard Type:  1(Ethernet) | Protocol: 0x0800(IP)    |
+------------+------------+-------------------------+
| Hard Len: 6| Addr Len: 4| OP:    2     (ARP Reply)|
+------------+------------+-------------------------+-------------------------+
| Sender MAC Address:                                        d8:fe:e3:a4:d3:78|
+---------------------------------------------------+-------------------------+
| Sender IP Address:                     192.168.1.1|
+---------------------------------------------------+-------------------------+
| Target MAC Address:                                        6c:40:08:bc:ae:98|
+---------------------------------------------------+-------------------------+
| Target IP Address:                    192.168.1.50|
+---------------------------------------------------+



執行結果(CentOS):
[root@tutu libpcap]# ./dump-arp 
Sniffing: eth0
No. 1
 Time: 15:04:15.011590
 Length: 42 bytes
 Capture length: 42 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   ff:ff:ff:ff:ff:ff|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        00:0c:29:b0:81:f7|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0806|
+-------------------------+
Next protocol is ARP
Protocol: ARP
+-------------------------+-------------------------+
| Hard Type:  1(Ethernet) | Protocol:0x0800(IP)     |
+------------+------------+-------------------------+
| HardLen:  6| Addr Len: 4| OP:    1   (ARP Request)|
+------------+------------+-------------------------+-------------------------+
| Sender MAC Address:                                        00:0c:29:b0:81:f7|
+---------------------------------------------------+-------------------------+
| Sender IP Address:                    192.168.1.67|
+---------------------------------------------------+-------------------------+
| Target MAC Address:                                        ff:ff:ff:ff:ff:ff|
+---------------------------------------------------+-------------------------+
| Target IP Address:                     192.168.1.1|
+---------------------------------------------------+

No. 2
 Time: 15:04:15.018333
 Length: 60 bytes
 Capture length: 60 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address:                                   00:0c:29:b0:81:f7|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address:                                        d8:fe:e3:a4:d3:78|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type:    0x0806|
+-------------------------+
Next protocol is ARP
Protocol: ARP
+-------------------------+-------------------------+
| Hard Type:  1(Ethernet) | Protocol:0x0800(IP)     |
+------------+------------+-------------------------+
| HardLen:  6| Addr Len: 4| OP:    2     (ARP Reply)|
+------------+------------+-------------------------+-------------------------+
| Sender MAC Address:                                        d8:fe:e3:a4:d3:78|
+---------------------------------------------------+-------------------------+
| Sender IP Address:                     192.168.1.1|
+---------------------------------------------------+-------------------------+
| Target MAC Address:                                        00:0c:29:b0:81:f7|
+---------------------------------------------------+-------------------------+
| Target IP Address:                    192.168.1.67|
+---------------------------------------------------+



Source code on Github

沒有留言:

張貼留言