libpcap/dump_tcp.c
接下來是TCP:Transmission Control Protocol(傳輸控制協定)
。
程式由libpcap/dump_ip.c修改。
TCP Header
- Source Port:2 bytes。
- Destination Port:2 bytes。
- Sequence Number:4 bytes。
- Acknowledgement Number:4 bytes。
- Header Length:4 bits。
- Reserved:4 bits。
- Flags:8 bits。
- Window Size:2 bytes。
- Checksum:2 bytes。
- Urgent Pointer:2 bytes。
- TCP Option + Padding:0 ~ 40 bytes。
- 長度:
20 ~ 60 bytes
。
Flags由左到右分別為:CWR
、ECE
、URG
、ACK
、PUSH
、RST
、SYN
以及FIN
,共8個bit。
Source Code
//
// dump_tcp.c
// 功能:分析TCP表頭。
// Created by 聲華 陳 on 2016/01/11.
//
#define __FAVOR_BSD
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <string.h>
#include <netinet/ip.h>
#include <netinet/tcp.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 dump_tcp(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);
char *tcp_ftoa(u_int8_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, "tcp", 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:
dump_tcp(length, content);
break;
case IPPROTO_ICMP:
printf("Next is ICMP\n");
break;
default:
printf("Next is %d\n", protocol);
break;
}//end switch
}//end dump_ip
char *tcp_ftoa(u_int8_t flag) {
static int f[] = {'W', 'E', 'U', 'A', 'P', 'R', 'S', 'F'};
#define TCP_FLG_MAX (sizeof f / sizeof f[0])
static char str[TCP_FLG_MAX + 1];
u_int32_t mask = 1 << 7;
int i;
for (i = 0; i < TCP_FLG_MAX; i++) {
if(mask & flag)
str[i] = f[i];
else
str[i] = '-';
mask >>= 1;
}//end for
str[i] = '\0';
return str;
}//end tcp_ftoa
void dump_tcp(u_int32_t length, const u_char *content) {
struct ip *ip = (struct ip *)(content + ETHER_HDR_LEN);
struct tcphdr *tcp = (struct tcphdr *)(content + ETHER_HDR_LEN + (ip->ip_hl << 2));
//copy header
u_int16_t source_port = ntohs(tcp->th_sport);
u_int16_t destination_port = ntohs(tcp->th_dport);
u_int32_t sequence = ntohl(tcp->th_seq);
u_int32_t ack = ntohl(tcp->th_ack);
u_int8_t header_len = tcp->th_off << 2;
u_int8_t flags = tcp->th_flags;
u_int16_t window = ntohs(tcp->th_win);
u_int16_t checksum = ntohs(tcp->th_sum);
u_int16_t urgent = ntohs(tcp->th_urp);
//print
printf("Protocol: TCP\n");
printf("+-------------------------+-------------------------+\n");
printf("| Source Port: %5u| Destination Port: %5u|\n", source_port, destination_port);
printf("+-------------------------+-------------------------+\n");
printf("| Sequence Number: %10u|\n", sequence);
printf("+---------------------------------------------------+\n");
printf("| Acknowledgement Number: %10u|\n", ack);
printf("+------+-------+----------+-------------------------+\n");
printf("| HL:%2u| RSV |F:%8s| Window Size: %5u|\n", header_len, tcp_ftoa(flags), window);
printf("+------+-------+----------+-------------------------+\n");
printf("| Checksum: %5u| Urgent Pointer: %5u|\n", checksum, urgent);
printf("+-------------------------+-------------------------+\n");
}//end dump_tcp
結果
libpcap % ./dump_tcp
No. 1
Time: 20:23:03.107696
Length: 823 bytes
Capture length: 823 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address: 6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address: f8:1a:67:53:f5:dc|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type: 0x0800|
+-------------------------+
Protocol: IP
+-----+------+------------+-------------------------+
| IV:4| HL:20| T: --------| Total Length: 809|
+-----+------+------------+-------+-----------------+
| Identifier: 13525| FF:-D-| FO: 0|
+------------+------------+-------+-----------------+
| TTL: 56| Pro: 6| Header Checksum: 14989|
+------------+------------+-------------------------+
| Source IP Address: 54.230.215.122|
+---------------------------------------------------+
| Destination IP Address: 192.168.1.100|
+---------------------------------------------------+
Protocol: TCP
+-------------------------+-------------------------+
| Source Port: 443| Destination Port: 57925|
+-------------------------+-------------------------+
| Sequence Number: 3981493633|
+---------------------------------------------------+
| Acknowledgement Number: 1550444767|
+------+-------+----------+-------------------------+
| HL:32| RSV |F:---AP---| Window Size: 111|
+------+-------+----------+-------------------------+
| Checksum: 31086| Urgent Pointer: 0|
+-------------------------+-------------------------+
No. 2
Time: 20:23:03.107707
Length: 103 bytes
Capture length: 103 bytes
Ethernet Frame:
+-------------------------+-------------------------+-------------------------+
| Destination MAC Address: 6c:40:08:bc:ae:98|
+-------------------------+-------------------------+-------------------------+
| Source MAC Address: f8:1a:67:53:f5:dc|
+-------------------------+-------------------------+-------------------------+
| Ethernet Type: 0x0800|
+-------------------------+
Protocol: IP
+-----+------+------------+-------------------------+
| IV:4| HL:20| T: --------| Total Length: 89|
+-----+------+------------+-------+-----------------+
| Identifier: 13526| FF:-D-| FO: 0|
+------------+------------+-------+-----------------+
| TTL: 56| Pro: 6| Header Checksum: 15708|
+------------+------------+-------------------------+
| Source IP Address: 54.230.215.122|
+---------------------------------------------------+
| Destination IP Address: 192.168.1.100|
+---------------------------------------------------+
Protocol: TCP
+-------------------------+-------------------------+
| Source Port: 443| Destination Port: 57925|
+-------------------------+-------------------------+
| Sequence Number: 3981494390|
+---------------------------------------------------+
| Acknowledgement Number: 1550444767|
+------+-------+----------+-------------------------+
| HL:32| RSV |F:---AP---| Window Size: 111|
+------+-------+----------+-------------------------+
| Checksum: 2760| Urgent Pointer: 0|
+-------------------------+-------------------------+
分析
#define __FAVOR_BSD
在程式最上方#define
__FAVOR_BSD
這個保留字,因為在非BSD
核心的Linux系統,有些封包表頭定義不太一樣,利用定義這個保留字讓它能夠正確使用我們想要的表頭定義。
void dump_tcp(u_int32_t length, const u_char *content);
char *tcp_ftoa(u_int8_t flag);
函數dump_tcp()
用來解析TCP和函數tcp_ftoa()
用來將Flags欄位轉成字串。
//compile filter
if(-1 == pcap_compile(handle, &fcode, "tcp", 1, mask)) {
fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(handle));
pcap_close(handle);
exit(1);
}//end if
這次過濾器使用"tcp"
。
void dump_ip(u_int32_t length, const u_char *content) {
...略
switch (protocol) {
case IPPROTO_UDP:
printf("Next is UDP\n");
break;
case IPPROTO_TCP:
dump_tcp(length, content);
break;
case IPPROTO_ICMP:
printf("Next is ICMP\n");
break;
default:
printf("Next is %d\n", protocol);
break;
}//end switch
}//end dump_ip
在下一層協定是TCP的時候呼叫dump_tcp()
。
void dump_tcp(u_int32_t length, const u_char *content) {
struct ip *ip = (struct ip *)(content + ETHER_HDR_LEN);
struct tcphdr *tcp = (struct tcphdr *)(content + ETHER_HDR_LEN + (ip->ip_hl << 2));
...略
這個封包封裝由下往上是Ethernet
->IP
->TCP
,所以必須先宣告struct ip *ip
取出IP表頭部分,接著利用變數ip
取得表頭長度:ip->ip_hl << 2
,這樣就可以利用IP表頭長度算出TCP開始位置:ETHER_HDR_LEN + (ip->ip_hl << 2)
。
//copy header
u_int16_t source_port = ntohs(tcp->th_sport);
u_int16_t destination_port = ntohs(tcp->th_dport);
u_int32_t sequence = ntohl(tcp->th_seq);
u_int32_t ack = ntohl(tcp->th_ack);
u_int8_t header_len = tcp->th_off << 2;
u_int8_t flags = tcp->th_flags;
u_int16_t window = ntohs(tcp->th_win);
u_int16_t checksum = ntohs(tcp->th_sum);
u_int16_t urgent = ntohs(tcp->th_urp);
複製表頭欄位資料,Sequence Number和Acknowledgement Number是4個byte的資料,所以要用ntohl()
。TCP的表頭長度跟IP的表頭長度一樣,需要向左移兩位(乘以4)才能夠取得:tcp->th_off << 2
。
...略
//print
printf("Protocol: TCP\n");
printf("+-------------------------+-------------------------+\n");
printf("| Source Port: %5u| Destination Port: %5u|\n", source_port, destination_port);
printf("+-------------------------+-------------------------+\n");
printf("| Sequence Number: %10u|\n", sequence);
printf("+---------------------------------------------------+\n");
printf("| Acknowledgement Number: %10u|\n", ack);
printf("+------+-------+----------+-------------------------+\n");
printf("| HL:%2u| RSV |F:%8s| Window Size: %5u|\n", header_len, tcp_ftoa(flags), window);
printf("+------+-------+----------+-------------------------+\n");
printf("| Checksum: %5u| Urgent Pointer: %5u|\n", checksum, urgent);
printf("+-------------------------+-------------------------+\n");
接下來一樣畫出來。
char *tcp_ftoa(u_int8_t flag) {
static int f[] = {'W', 'E', 'U', 'A', 'P', 'R', 'S', 'F'};
#define TCP_FLG_MAX (sizeof f / sizeof f[0])
static char str[TCP_FLG_MAX + 1];
u_int32_t mask = 1 << 7;
int i;
for (i = 0; i < TCP_FLG_MAX; i++) {
if(mask & flag)
str[i] = f[i];
else
str[i] = '-';
mask >>= 1;
}//end for
str[i] = '\0';
return str;
}//end tcp_ftoa
函數tcp_ftoa()
用來將Flags欄位轉成字串,原理已經在libpcap/dump_ip.c解釋了呦。
結語
TCP比較困難的只有Flags部份而已,剩下的方式前面都練習慣了。