libpcap/dump_ip.c
接下來就是解析IP:Internet Protocol(網路協定)
了,IP的Option部分因為太多種類,就不詳細解析了。
程式由libpcap/dump_ethernet.c和libpcap/dump_arp.c修改。
IP Header
- Version:4 bits。
- Header Length:4 bits。
- Type of Service:1 byte。
- Total Length:2 bytes。
- Identification:2 bytes。
- Flags:3 bits。
- Fragment Offset:13 bits。
- Time to Live:1 byte。
- Protocol:1 byte。
- Header Checksum:2 bytes。
- Source IP Address:4 bytes。
- Destination IP Address:4 bytes。
- IP Option + Padding:0 ~ 40 bytes。
- 長度:
20 ~ 60 bytes
。
Source Code
//
// dump_ip.c
// 功能:分析IP表頭。
// Created by 聲華 陳 on 2016/01/11.
//
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <string.h>
#include <netinet/ip.h>
#define MAC_ADDRSTRLEN 2*6+5+1
void dump_ethernet(u_int32_t length, const u_char *content);
void dump_ip(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);
char *ip_ttoa(u_int8_t flag);
char *ip_ftoa(u_int16_t flag);
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, "ip", 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:
printf("Next is ARP\n");
break;
case ETHERTYPE_IP:
dump_ip(length, content);
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
char *ip_ttoa(u_int8_t flag) {
static int f[] = {'1', '1', '1', 'D', 'T', 'R', 'C', 'X'};
#define TOS_MAX (sizeof(f)/sizeof(f[0]))
static char str[TOS_MAX + 1]; //return buffer
u_int8_t mask = 1 << 7; //mask
int i;
for(i = 0 ; i < TOS_MAX ; i++) {
if(mask & flag)
str[i] = f[i];
else
str[i] = '-';
mask >>= 1;
}//end for
str[i] = 0;
return str;
}//end ip_ttoa
char *ip_ftoa(u_int16_t flag) {
static int f[] = {'R', 'D', 'M'}; //flag
#define IP_FLG_MAX (sizeof(f)/sizeof(f[0]))
static char str[IP_FLG_MAX + 1]; //return buffer
u_int16_t mask = 1 << 15; //mask
int i;
for(i = 0 ; i < IP_FLG_MAX ; i++) {
if(mask & flag)
str[i] = f[i];
else
str[i] = '-';
mask >>= 1;
}//end for
str[i] = 0;
return str;
}//end ip_ftoa
void dump_ip(u_int32_t length, const u_char *content) {
struct ip *ip = (struct ip *)(content + ETHER_HDR_LEN);
u_int version = ip->ip_v;
u_int header_len = ip->ip_hl << 2;
u_char tos = ip->ip_tos;
u_int16_t total_len = ntohs(ip->ip_len);
u_int16_t id = ntohs(ip->ip_id);
u_int16_t offset = ntohs(ip->ip_off);
u_char ttl = ip->ip_ttl;
u_char protocol = ip->ip_p;
u_int16_t checksum = ntohs(ip->ip_sum);
//print
printf("Protocol: IP\n");
printf("+-----+------+------------+-------------------------+\n");
printf("| IV:%1u| HL:%2u| T: %8s| Total Length: %10u|\n",
version, header_len, ip_ttoa(tos), total_len);
printf("+-----+------+------------+-------+-----------------+\n");
printf("| Identifier: %5u| FF:%3s| FO: %5u|\n",
id, ip_ftoa(offset), offset & IP_OFFMASK);
printf("+------------+------------+-------+-----------------+\n");
printf("| TTL: %3u| Pro: %3u| Header Checksum: %5u|\n",
ttl, protocol, checksum);
printf("+------------+------------+-------------------------+\n");
printf("| Source IP Address: %15s|\n", ip_ntoa(&ip->ip_src));
printf("+---------------------------------------------------+\n");
printf("| Destination IP Address: %15s|\n", ip_ntoa(&ip->ip_dst));
printf("+---------------------------------------------------+\n");
switch (protocol) {
case IPPROTO_UDP:
printf("Next is UDP\n");
break;
case IPPROTO_TCP:
printf("Next is TCP\n");
break;
case IPPROTO_ICMP:
printf("Next is ICMP\n");
break;
default:
printf("Next is %d\n", protocol);
break;
}//end switch
}//end dump_ip
結果
libpcap % ./dump_ip
No. 1
Time: 12:40:31.601016
Length: 98 bytes
Capture length: 98 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address: f8:1a:67:53:f5:dc|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address: 6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type: 0x0800|
+-------------------------+
Protocol: IP
+-----+------+------------+-------------------------+
| IV:4| HL:20| T: --------| Total Length: 84|
+-----+------+------------+-------+-----------------+
| Identifier: 6796| FF:---| FO: 0|
+------------+------------+-------+-----------------+
| TTL: 64| Pro: 1| Header Checksum: 56423|
+------------+------------+-------------------------+
| Source IP Address: 192.168.1.100|
+---------------------------------------------------+
| Destination IP Address: 192.168.1.1|
+---------------------------------------------------+
Next is ICMP
No. 2
Time: 12:40:28.226933
Length: 67 bytes
Capture length: 67 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address: f8:1a:67:53:f5:dc|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address: 6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type: 0x0800|
+-------------------------+
Protocol: IP
+-----+------+------------+-------------------------+
| IV:4| HL:20| T: --------| Total Length: 53|
+-----+------+------------+-------+-----------------+
| Identifier: 49478| FF:-D-| FO: 0|
+------------+------------+-------+-----------------+
| TTL: 64| Pro: 6| Header Checksum: 47625|
+------------+------------+-------------------------+
| Source IP Address: 192.168.1.100|
+---------------------------------------------------+
| Destination IP Address: 64.233.188.125|
+---------------------------------------------------+
Next is TCP
分析
void dump_ip(u_int32_t length, const u_char *content);
char *ip_ntoa(void *i);
char *ip_ttoa(u_int8_t flag);
char *ip_ftoa(u_int16_t flag);
這次宣告dump_ip()
來處理IP表頭,ip_ttoa()
是用來將Type of Service
欄位轉成字串,ip_ftoa()
則是用來將Flags
欄位轉成字串。
//compile filter
if(-1 == pcap_compile(handle, &fcode, "ip", 1, mask)) {
fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(handle));
pcap_close(handle);
exit(1);
}//end if
過濾器用"ip"
,表示只抓IPv4的封包。
void dump_ethernet(u_int32_t length, const u_char *content) {
...略
switch (type) {
case ETHERTYPE_ARP:
printf("Next is ARP\n");
break;
case ETHERTYPE_IP:
dump_ip(length, content);
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為IP時,就呼叫dump_ip()
來解析IP表頭。
void dump_ip(u_int32_t length, const u_char *content) {
struct ip *ip = (struct ip *)(content + ETHER_HDR_LEN);
u_int version = ip->ip_v;
u_int header_len = ip->ip_hl << 2;
u_char tos = ip->ip_tos;
u_int16_t total_len = ntohs(ip->ip_len);
u_int16_t id = ntohs(ip->ip_id);
u_int16_t offset = ntohs(ip->ip_off);
u_char ttl = ip->ip_ttl;
u_char protocol = ip->ip_p;
u_int16_t checksum = ntohs(ip->ip_sum);
...略
宣告struct ip *ip
來指向IP表頭開始的位置。表頭長度欄位需要乘以4才是真正的長度,等同與向左位移兩位:ip->ip_hl << 2
。剩下一樣兩個byte的欄位要用ntohs()
轉換。
//print
printf("Protocol: IP\n");
printf("+-----+------+------------+-------------------------+\n");
printf("| IV:%1u| HL:%2u| T: %8s| Total Length: %10u|\n",
version, header_len, ip_ttoa(tos), total_len);
printf("+-----+------+------------+-------+-----------------+\n");
printf("| Identifier: %5u| FF:%3s| FO: %5u|\n",
id, ip_ftoa(offset), offset & IP_OFFMASK);
printf("+------------+------------+-------+-----------------+\n");
printf("| TTL: %3u| Pro: %3u| Header Checksum: %5u|\n",
ttl, protocol, checksum);
printf("+------------+------------+-------------------------+\n");
printf("| Source IP Address: %15s|\n", ip_ntoa(&ip->ip_src));
printf("+---------------------------------------------------+\n");
printf("| Destination IP Address: %15s|\n", ip_ntoa(&ip->ip_dst));
printf("+---------------------------------------------------+\n");
接下來就把封包畫出來,這邊要注意的是Flags和Fragment Offset共用2個byte,前面3個bit是Flags後面13個bit是Fragment Offset,我們可以直接將共用的兩個byte直接與IP_OFFMASK
保留字做&
運算來取得Fragment Offset。
...略
switch (protocol) {
case IPPROTO_UDP:
printf("Next is UDP\n");
break;
case IPPROTO_TCP:
printf("Next is TCP\n");
break;
case IPPROTO_ICMP:
printf("Next is ICMP\n");
break;
default:
printf("Next is %d\n", protocol);
break;
}//end switch
}//end dump_ip
用ip->p
欄位來判斷下一層協定是什麼。一樣雖然都是#define
的保留字,不過還是要記住幾項。
#define IPPROTO_ICMP 1 /* control message protocol */
#define IPPROTO_IGMP 2 /* group mgmt protocol */
#define IPPROTO_TCP 6 /* tcp */
#define IPPROTO_UDP 17 /* user datagram protocol */
#define IPPROTO_OSPFIGP 89 /* OSPFIGP */
char *ip_ttoa(u_int8_t flag) {
static int f[] = {'1', '1', '1', 'D', 'T', 'R', 'C', 'X'};
#define TOS_MAX (sizeof(f)/sizeof(f[0]))
static char str[TOS_MAX + 1]; //return buffer
u_int8_t mask = 1 << 7; //mask
int i;
for(i = 0 ; i < TOS_MAX ; i++) {
if(mask & flag)
str[i] = f[i];
else
str[i] = '-';
mask >>= 1;
}//end for
str[i] = 0;
return str;
}//end ip_ttoa
char *ip_ftoa(u_int16_t flag) {
static int f[] = {'R', 'D', 'M'}; //flag
#define IP_FLG_MAX (sizeof(f)/sizeof(f[0]))
static char str[IP_FLG_MAX + 1]; //return buffer
u_int16_t mask = 1 << 15; //mask
int i;
for(i = 0 ; i < IP_FLG_MAX ; i++) {
if(mask & flag)
str[i] = f[i];
else
str[i] = '-';
mask >>= 1;
}//end for
str[i] = 0;
return str;
}//end ip_ftoa
函數ip_ttoa()
和ip_ftoa()
做法都是利用bit的&
運算,當有該bit被設定時,就給對應的字元。
來解釋ip_ttoa()
就好,ip_ftoa()
原理相同。
char *ip_ttoa(u_int8_t flag) {
static int f[] = {'1', '1', '1', 'D', 'T', 'R', 'C', 'X'};
#define TOS_MAX (sizeof(f)/sizeof(f[0]))
static char str[TOS_MAX + 1]; //return buffer
u_int8_t mask = 1 << 7; //mask
int i;
for(i = 0 ; i < TOS_MAX ; i++) {
if(mask & flag)
str[i] = f[i];
else
str[i] = '-';
mask >>= 1;
}//end for
str[i] = 0;
return str;
}//end ip_ttoa
變數f
是用來表示每個bit對應的要轉換的字元,變數str
是用來傳回的字元陣列,變數mask
是遮罩,這邊宣告成1 << 7
,相當於8 bit被設定最左邊的位元。
變數mask
:
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
假設變數flag
為:
0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
接下來迴圈跑TOS_MAX
次(8次),迴圈第一次:
變數mask
:
mask | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
flag | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
str | - |
接著變數mask
向右移一位,迴圈進入第二次:
變數mask
:
mask | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
flag | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
str | - | 1 |
類推下去,最後變數str
:
- | 1 | 1 | D | - | T | - | - |
而ip_ftoa()
相同,只是迴圈只跑IP_FLG_MAX
次(3次)。
結語
IP表頭比較複雜的是被細切不同比例的bit欄位:Flags和Fragment Offset,其他部分就比較沒什麼要注意的。