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
),稍微來畫圖。
Ethernet | Ethernet | Ethernet | ARP | ARP | ARP | ARP |
12. ... | 13. 0x08 | 14. 0x06 | 15. 0x00 | 16. 0x01 | 17. 0x00 | 18. ... |
因為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為1
,ARP 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。
結語
解析封包就是重複這樣的方式解析,不過還是得多練習。