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. 0xf8 | 2. 0x1a | 3. 0x67 | 4. 0x53 | 5. 0xf5 | 6. 0xdc | ... |
讓ethernet
變數指向content
變數變這樣:
content ethernet ↓ ↓ |
||||||
1. 0xf8 | 2. 0x1a | 3. 0x67 | 4. 0x53 | 5. 0xf5 | 6. 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. 0xf8 | 2. 0x1a | 3. 0x67 | 4. 0x53 | 5. 0xf5 | 6. 0xdc | 7. 0x6c | 8. 0x40 | 9. 0x08 | 10. 0xbc |
ethernet->ether_type ↓ | |||||||||
11. 0xae | 12. 0x98 | 13. 0x08 | 14. 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
,這裡已經解釋了呦。
結語
解析封包其實就是一直使用指標指向對的位置來取得表頭欄位資料,剩下的就是如何去"翻譯"它的資料,只要把這的原理弄懂,指標會變得很厲害。