tcpdump 正则过滤
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tcpdump 正则过滤相关的知识,希望对你有一定的参考价值。
参考技术A tcpdump采用命令行方式,它的命令格式为:tcpdump [ -adeflnNOpqStvx ] [ -c 数量 ] [ -F 文件名 ][ -i 网络接口 ] [ -r 文件名] [ -s snaplen ] [ -T 类型 ] [ -w 文件名 ] [表达式 ]
功能选项说明:
-a 将网络地址和广播地址转变成名字
-d 将匹配数据包的内容以易理解的汇编格式输出
-dd 将匹配数据包的内容以语言程序段的格式输出
-ddd将匹配数据包的内容以十进制形式输出
-e在输出行打印出数据链路层的头部信息
-f 将外部的internet以十进制数字形式打印输出
-l使标准输出变为缓冲行形式
-n不把地址(主机地址、端口)的数字形式转换成名字
-t在输出的每一行不打印时间戳
-vv输出详细的报文信息
-v输出较详细的信息
-c指定要接收的数据包的数量接收到的数量达到后就会停止接收
-F从指定的文件中读取表达式忽略其他的表达式
-i指定要监听的网络接口
-r从指定的文件中读取数据包这些包是通过选项保存得到的
-w直接将数据包写入指定的文件中不分析和打印输出
-T将监听到的包直接解释为指定类型的报文,常用的类型有远程过程调用和简单网络管理协议
-s指定抓取数据的长度,采用了默认长度为68或96字节,-s0则表示没有长度的限制。如果没有这个设置,通常会抓包会显示Packet size limited during capture,这样数据包会被截断,是不全的。
表达式是一个正则表达式,tcpdump利用它作为过滤报文的条件,如果一个报文满足表达式的条件,则这个报文将会被捕获。如果没有给出任何条件,则网络上所有的信息包将会被截获。实际上,tcpdump的表达式主要包括三种类型的关键字
主要包括host,net,port,其中host用于表示网络上一台主机,net 用于表达式一个网络地址,port用于指定端口号,若表达式缺省若没有类型,默认类型为host。
如想要截获所有192.168.90.16与本机的交互数据包,只需在本机上执行如下命令
[root@test24267 ~]# tcpdump host 192.168.90.16 | more
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
4 packets captured
5 packets received by filter
0 packets dropped by kernel
Linux系统中,tcpdump默认收集从系统接口列表中搜寻到的编号(eth*)最小的已配置好的接口上的发送接收数据包。若没有找到,则会报错:
tcpdump: NFLOG link-layer type filtering not implemented
主要包括src,dst,src or dst,src and dst,这些关键字指明了传输的方向,默认为src or dst。如
[root@test24267 ~]# tcpdump src 192.168.90.16
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
10:36:14.221553 IP 192.168.90.16.50982 > localhost.ssh: . ack 1966953710 win 255
10:36:14.430570 IP 192.168.90.16.50982 > localhost.ssh: . ack 133 win 254
10:36:14.435741 IP 192.168.90.16.50982 > localhost.ssh: P 0:52(52) ack 249 win 254
10:36:14.644771 IP 192.168.90.16.50982 > localhost.ssh: . ack 381 win 253
10:36:14.863178 IP 192.168.90.16.50982 > localhost.ssh: . ack 497 win 253
可以看到只抓取了源为192.168.90.16的单向的数据包。
包括fddi,ip,arp,rarp,tcp,udp,imcp等,默认监听所有协议的信息包。fddi是分布式光纤数据接口网络上所使用的网络协议。
除了这三种类型的关键字外,还有其他重要的关键字,如:gateway,broadcast,less,greater,还有三种逻辑运算,取非运算是'not'、'!',与运算符是'and'、'&&'、或运算符是'or'、'||',这些关键字可以组合起来构成强大的组合条件来满足我们的需求。
下面举几个例子来说明。
想要截获主机210.27.48.1 和主机210.27.48.2 或210.27.48.3的通信,使用命令
#tcpdump host 210.27.48.1 and \ (210.27.48.2 or 210.27.48.3 \)
在正则表达式中括号需要使用转义字符\来表达。
如果想要获取主机210.27.48.1除了和主机210.27.48.2之外所有主机通信的ip包,使用命令:
#tcpdump ip host 210.27.48.1 and ! 210.27.48.2
如果想要获取主机210.27.48.1接收或发出的telnet包,使用如下命令:
#tcpdump tcp and port 23 and host 210.27.48.1
Telnet使用tcp协议,应用层为23端口。
获取主机210.27.48.1接收或发出的icmp包
#tcpdump -i eth0 icmp and host 210.27.48.1
ip icmp arp rarp 和 tcp、udp、icmp这些选项等都要放到第一个参数的位置,用来过滤数据报的类型。
抓取网口eth0上源mac地址或目的mac地址为00:21:85:6C9:A3的所有数据包,
#tcpdump -i eth0 ether src or dst 00:21:85:6C9:A3
注意,这里的mac地址格式必须以':'分隔。
我们还可以监视通过指定网关的数据包:
#tcpdump -i eth0 gateway Gatewayname
捕获有TCP标记的包
1、捕获所有的SYN包:tcpdump 'tcp[13] & 2 !=0'
[root@localhost ~]# tcpdump 'tcp[13] & 2 !=0' -i em1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on em1, link-type EN10MB (Ethernet), capture size 65535 bytes
20:06:52.103251 IP 10.255.255.93.52312 > 10.255.242.67.6379: Flags [S], seq 158892324, win 14600, options [mss 1460,sackOK,TS val 614181197 ecr 0,nop,wscale 7], length 0
20:06:52.103450 IP 10.255.242.67.6379 > 10.255.255.93.52312: Flags [S.], seq 795652737, ack 158892325, win 5792, options [mss 1460,sackOK,TS val 1354263797 ecr 614181197,nop,wscale 7], length 0
#捕获所有的ACK包
tcpdump 'tcp[13] &16 !=0'
#捕获所有的SYN-ACK包
tcpdump 'tcp[13] = 18'
#捕获所有的FIN包
tcpdump 'tcp[13] &1 !=0'
#捕获所有的RST包
tcpdump 'tcp[13] &4 !=0'
#抓取端口大于1024的tcp数据包
tcpdump –i eth* ‘tcp[0:2]>1024’
这些命令可以与其他过滤项组合使用。
后台抓包,控制台退出也不会影响:
nohup tcpdump -i eth1 port 110 -w /tmp/xxx.cap &
转载linux环境下tcpdump源代码分析
linux环境下tcpdump源代码分析
原文时间 2013-10-11 13:13:02
原文链接
主题 Tcpdump
作者:韩大卫 @ 吉林师范大学
tcpdump.c 是tcpdump 工具的main.c, 本文旨对tcpdump的框架有简单了解,只展示linux平台使用的一部分核心代码。
Tcpdump 的使用目的就是打印出指定条件的报文,即使有再多的正则表达式作为过滤条件。所以只要懂得tcpdump -nXXi eth0 的实现原理即可。
进入main之前,先看一些头文件
netdissect.h里定义了一个数据结构struct netdissect_options来描述tcdpump支持的所有参数动作,每一个参数有对应的flag, 在tcpdump 的main 里面, 会根据用户的传入的参数来增加相应flag数值, 最后根据这些flag数值来实现特定动作。各个参数含义请参考源代码注释。
struct netdissect_options {
int ndo_aflag; /* translate network and broadcast addresses */
//打印出以太网头部
int ndo_eflag; /* print ethernet header */
int ndo_fflag; /* www.qixoo.qixoo.com/don‘t translate "foreign" IP address */
int ndo_Kflag; /* don‘t check TCP checksums */
//不将地址转换为名字
int ndo_nflag; /* leave addresses as numbers */
int ndo_Nflag; /* remove domains from printed host names */
int ndo_qflag; /* quick (shorter) output */
int ndo_Rflag; /* print sequence # field in AH/ESP*/
int ndo_sflag; /* use the libsmi to translate OIDs */
int ndo_Sflag; /* print raw TCP sequence numbers */
// 报文到达时间
int ndo_tflag; /* print packet arrival time */
int ndo_Uflag; /* "unbuffered" output of dump files */
int ndo_uflag; /* Print undecoded NFS handles */
//详细信息
int ndo_vflag; /* verbose */
// 十六进制打印报文
int ndo_xflag; /* print packet in hex */
// 十六进制和ASCII码打印报文
int ndo_Xflag; /* print packet in hex/ascii */
//以ASCII码显示打印报文
int ndo_Aflag; /* print packet only in ascii observing TAB,
* LF, CR and SPACE as graphical chars
*/
...
//默认的打印函数
void (*ndo_default_print)(netdissect_options *,
register const u_char *bp, register u_int length);
void (*ndo_info)(netdissect_options *, int verbose);
...
}
interface.h 接口头文件,定义了一堆宏就为了方便调用struct netdissect_options里的成员。
#ifndef NETDISSECT_REWORKED
extern netdissect_options *gndo;
...
#define nflag gndo->ndo_nflag
...
#define tflag gndo->ndo_tflag
...
#define vflag gndo->ndo_vflag
#define xflag gndo->ndo_xflag
#define Xflag gndo->ndo_Xflag
...
#endif
tcpdump.c
int
main(int argc, char **argv)
{
register char *cp, *infile, *cmdbuf, *device, *RFileName, *WFileName;
pcap_handler callback;
int type;
struct bpf_program fcode;
struct print_info printinfo;
...
//对netdissect_options中一些参数初始化
gndo->ndo_Oflag=1;
gndo->ndo_Rflag=1;
gndo->ndo_dlt=-1;
gndo->ndo_default_print=ndo_default_print;
gndo->ndo_printf=tcpdump_printf;
gndo->ndo_error=ndo_error;
gndo->ndo_warning=ndo_warning;
gndo->ndo_snaplen = DEFAULT_SNAPLEN;
...
opterr = 0;
while (
/*经典的getopt框架。 字符数组为tcpdump 支持的全部参数。可以看到, 参数x, X,t这些参数后面没有:或::, 这说明这些参数会产生叠加的效果。
*/
(op = getopt(argc, argv, "aA" B_FLAG "c:C:d" D_FLAG "eE:fF:G:i:" I_FLAG "KlLm:M:nNOpqr:Rs:StT:u" U_FLAG "vw:W:xXy:Yz:Z:")) != -1)
switch (op) {
...
//case 里面的处理大多相似,以下仅用-i,-X,-x做例。
//-i 参数用来指定网口
case ‘i‘:
if (optarg[0] == ‘0‘ && optarg[1] == 0)
error("Invalid adapter index");
device = optarg;
break;
…
//-x 为以十六进制打印报文,如使用-xx, xflag数值为2,后面根据xflag>1来打印出链路层头部
case ‘x‘:
++xflag;
++suppress_default_print;
break;
case ‘X‘:
++Xflag;
++suppress_default_print;
break;
//case ‘n‘, case ‘A‘等操作类似如上
...
}
...
/*展开核心代码前处理信号,信号处理函数cleanup会调用info()来打印当用户按ctrl+c等发送中止信号时tcpdump显示已处理报文的统计信息。
3 packets captured
3 packets received by filter
0 packets dropped by kernel
*/
(void)setsignal(SIGPIPE, cleanup);
(void)setsignal(SIGTERM, cleanup);
(void)setsignal(SIGINT, cleanup);
(void)setsignal(SIGCHLD, child_cleanup);
...
//从 -r 参数读取指定文件, 在此忽略
if (RFileName != NULL) {
...
} else {
//如果没有-i 参数来指定网络接口, 那么调用 pcap_lookupdev()来寻找可用的网络接口
if (device == NULL) {
device = pcap_lookupdev(ebuf);
if (device == NULL)
error("%s", ebuf);
}
/*pcap_open_live() 定义为:
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
device为要打开的指定设备
snaplen为最大报文长度, 由-s 指定. 默认为65536.
Promise 为是否要将网口配置为混杂模式, 由-p 指定,!Pflag:默认为是。
to_ms 为超时时间。 *ebuf 为传递错误信息使用。
函数返回捕获报文的句柄。
*/
*ebuf = ‘\0‘;
pd = pcap_open_live(device, snaplen, !pflag, 1000, ebuf);
if (pd == NULL)
error("%s", ebuf);
else if (*ebuf)
warning("%s", ebuf);
// -w 参数 加结果写入一个文件, 在此忽略
if (WFileName) {
...
} else {
//返回数据链路层的枚举值
type = pcap_datalink(pd);
printinfo.printer = lookup_printer(type);
/*lookup_printer() 作用如下:根据该数据链路层类型返回相应的打印函数指针。定义如下:
static if_printer
lookup_printer(int type)
{
struct printer *p;
for (p = printers; p->f; ++p)
if (type == p->type)
return p->f;
return NULL;
}
其中struct printer定义为 一个打印函数指针, 一个类型数值
typedef u_int (*if_printer)(const struct pcap_pkthdr *, const u_char *);
struct printer {
if_printer f;
int type;
};
printers 为一个struct printer数组, 定义如下:
static struct printer printers[] ={
{ arcnet_if_print, DLT_ARCNET },
{ ether_if_print, DLT_EN10MB },
{ token_if_print, DLT_IEEE802 },
...
}
由上可以看到, 当为以太网环境(DLT_EN10MB)时,实现函数为ether_if_print,
当为IEEE802令牌环网环境时, 实现函数为 token_if_print。
等等。 不同数据链路层环境有不同的调用函数来实现打印特定格式的报文。
for (p = printers; p->f; ++p) : 从数组首个元素开始,循环条件是元素存在f指针,依次遍历全部数组成员。
所以当数据链路层的类型为DLT_EN10MB时, 对应的打印函数为ether_if_print。
我本人觉得 lookup_printer() 这个函数写得甚是巧妙。 非常值得借鉴。 每一种类型定义一个数据结构struct printer, 包含一个函数指针和一个类型值。 将全部的类型放入一个数组中,遍历数组时根据类型值返回对应的函数指针, 再有新类型时,仅将其添加到数组中即可。
*/
if (printinfo.printer == NULL) {
gndo->ndo_dltname = pcap_datalink_val_to_name(type);
if (gndo->ndo_dltname != NULL)
error("unsupported data link type %s",
gndo->ndo_dltname);
else
error("unsupported data link type %d", type);
}
//函数指针callback指向print_packet
callback = print_packet;
//将printinfo作为unsigned char * 赋值给pcap_usrdata, 在后面作为pcap_loop()的参数
pcap_userdata = (u_char *)&printinfo;
}
if (RFileName == NULL) {
int dlt;
const char *dlt_name;
...
/*pcap_datalink() 返回数据链路层类型枚举值,这里返回DLT_EN10MB */
dlt = pcap_datalink(pd);
//根据该枚举返回数据链路类型char *name: “EN10MB”
dlt_name = pcap_datalink_val_to_name(dlt);
if (dlt_name == NULL) {
(void)fprintf(stderr, "listening on %s, link-type %u, capture size %u bytes\n",
device, dlt, snaplen);
} else {
(void)fprintf(stderr, "listening on %s, link-type %s (%s), capture size %u bytes\n",
device, dlt_name,
//获取该数据链路层类型的字符串描述
pcap_datalink_val_to_description(dlt), snaplen);
}
/*
使用tcpdump -nXXi eth0
后,打印信息:
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
即来源于此。
*/
/*调用 pcap_loop(), 循环捕获报文并将报文交给callback处理,直到遇到错误或退出信号。
Cnt 为 -c 参数指定,默认0. Usrdata 作为callback 的参数。
pcap_loop() 是libpcap 提供的API,它完成了与底层驱动的通信,首先创建了一个socket,将句柄封装后交给底层驱动,驱动收到数据包后将其写入socket, 从内核层发往用户层, 用户层的pcap_loop()持续poll 这个socket , 发现其有数据后就将数据交给callback函数处理,进行打印。 这样做的优点是可直接使用linux的既有socket IPC架构, 缺点是要承受在高速的以太网环境里,从内核层到用户层的拷贝动作产生的开销代价。
*/
status = pcap_loop(pd, cnt, callback, pcap_userdata);
...
pcap_close(pd);
/*
由上面看到, callback 的实现函数为print_packet(), pcap_loop()调用callpack 时传给print_packet()三个参数,第一个为含有特定链路层打印函数的结构体pcap_userdata, 第二个为包含报文信息的 struct pcap_pkthdr 常量指针, 第三个为数据包内容的字符串常量指针。
其中struct pcap_pkthdr 定义为:
struct pcap_pkthdr{
struct timeval ts; //时间戳数据结构
bpf_u_int32 caplen; //报文捕获长度
bpf_u_int32 len; //报文实际长度
}
注: 如一个报文实际长度100B, 但tcpdump捕获80B时停止, 那么caplen 为80, len 为 100。
static void
print_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
{
struct print_info *print_info;
u_int hdrlen;
++packets_captured;
++infodelay;
ts_print(&h->ts);
/*取得参数user 的数据结构, 后面(*print_info->printer)即调用user提供的打印函数,
这里为ether_if_print()
*/
print_info = (struct print_info *)user;
snapend = sp + h->caplen;
//调用ether_if_print()
hdrlen = (*print_info->printer)(h, sp);
if (Xflag) {
//当tcpdump 有多个X参数时, 如 tcpdump -XX 时, 以十六进制和ASCII码打印出链路层头部信息
if (Xflag > 1) {
hex_and_ascii_print("\n\t", sp, h->caplen);
} else {
//只有一个X参数,即tcpdump -X 时,不打印链路层头部
if (h->caplen > hdrlen)
hex_and_ascii_print("\n\t", sp + hdrlen,
h->caplen - hdrlen);
}
} else if (xflag) {
//同-X, 当存在多个-x 参数,如tcpdump -xx 时, 打印链路层头部, 但只以十六进制打印
if (xflag > 1) {
hex_print("\n\t", sp, h->caplen);
} else {
if (h->caplen > hdrlen)
hex_print("\n\t", sp + hdrlen,
h->caplen - hdrlen);
}
} else if (Aflag) {
//-A 参数, 以ASCII码打印报文信息
if (Aflag > 1) {
ascii_print(sp, h->caplen);
} else {
if (h->caplen > hdrlen)
ascii_print(sp + hdrlen, h->caplen - hdrlen);
}
}
putchar(‘\n‘);
--infodelay;
if (infoprint)
info(0);
}
*/
/*
在print-ether.c里, 有ether_if_print 的定义, 同样的, 在print-token.c 里有token_if_print的定义, print-arcnet.c里有arcnet_if_print的定义。Tcpdump 目录里大量的 “print-” 开头的文件均是特定的打印函数。
print-ether.c
u_int
ether_if_print(const struct pcap_pkthdr *h, const u_char *p)
{
//将报文内容, 报文捕获长度, 报文实际长度传给 ether_print
ether_print(p, h->len, h->caplen);
}
ether_print定义:
void
ether_print(const u_char *p, u_int length, u_int caplen)
{
struct ether_header *ep;
/*
以太网头部定义
#define ETHER_HDRLEN 14 //头部长14字节
#define ETHER_ADDR_LEN 6
struct ether_header {
u_int8_t ether_dhost[ETHER_ADDR_LEN];
//DMAC, 6字节
u_int8_t ether_shost[ETHER_ADDR_LEN];
//SMAC, 6字节
u_int16_t ether_type;
//type, 2字节
};
*/
u_short ether_type;
u_short extracted_ether_type;
if (caplen < ETHER_HDRLEN) {
printf("[|ether]");
return;
}
/*如果有 -e参数,打印链路层头部,调用 ether_hdr_print() ,定义见下方。
*/
if (eflag)
ether_hdr_print(p, length);
length -= ETHER_HDRLEN;
caplen -= ETHER_HDRLEN;
ep = (struct ether_header *)p;
p += ETHER_HDRLEN;
ether_type = ntohs(ep->ether_type);
//具体的打印细节不做研究了
if (ether_type <= ETHERMTU) {
/* Try to print the LLC-layer header & higher layers */
if (llc_print(p, length, caplen, ESRC(ep), EDST(ep),
&extracted_ether_type) == 0) {
if (!eflag)
ether_hdr_print((u_char *)ep, length + ETHER_HDRLEN);
if (!suppress_default_print)
default_print(p, caplen);
}
} else if (ether_encap_print(ether_type, p, length, caplen,
&extracted_ether_type) == 0) {
if (!eflag)
ether_hdr_print((u_char *)ep, length + ETHER_HDRLEN);
if (!suppress_default_print)
default_print(p, caplen);
}
}
*/
/*
使用 tcpdump -nei eth0 会有如下显示:
12:53:12.189132 d0:df:9a:53:f0:07 > 01:00:5e:7f:ff:fa, ethertype IPv4 (0x0800), length 175: 10.10.168.94.60395 > 239.255.255.250.1900: UDP, length 133
ether_hdr_print 定义:
static inline void
ether_hdr_print(register const u_char *bp, u_int length)
{
register const struct ether_header *ep;
ep = (const struct ether_header *)bp;
//打印出 原MAC > 目的MAC, 比如上面的 d0:df:9a:53:f0:07 > 01:00:5e:7f:ff:fa
(void)printf("%s > %s",
etheraddr_string(ESRC(ep)),
etheraddr_string(EDST(ep)));
//如果没有-q 参数,
if (!qflag) {
if (ntohs(ep->ether_type) <= ETHERMTU)
(void)printf(", 802.3");
else
//打印出协议类型, 如上面的ethertype IPv4 (0x0800)
(void)printf(", ethertype %s (0x%04x)",
tok2str(ethertype_values,"Unknown", ntohs(ep->ether_type)),
ntohs(ep->ether_type));
} else {
if (ntohs(ep->ether_type) <= ETHERMTU)
(void)printf(", 802.3");
else
(void)printf(", %s", tok2str(ethertype_values,"Unknown Ethertype (0x%04x)", ntohs(ep->ether_type)));
}
//打印出报文长度, 如上面的length 175
(void)printf(", length %u: ", length);
}
*/
总结:
概括地看, tcpdump.c 可分三个部分:
第一部分是用struct netdissect_options数据结构作为一个参数集合, 并用getopt框架来处理argv的参数逻辑。
第二部分是使用libpcap库函数来搭建与底层IPC通道。 其中最重要的API有三个, 第一个是pcap_lookupdev(), 查找可用网口,第二个是pcap_open_live(),打开指定设备并将其配置为混杂模式返回句柄, 第三个是使用pcap_loop()持续获取报文数据,调用回调函数进行打印处理。
第三部分是实现callback 函数,tcpdump.c里的callback函数只做了一个封装,最终调用的是参数pcap_userdata里提供的特定数据链路层的打印函数, 这个函数指针的查找是由lookup_printer()实现的。
关于pcap_open_live 和pcap_loop 这两个重要的函数源代码分析,后续介绍。
以上是关于tcpdump 正则过滤的主要内容,如果未能解决你的问题,请参考以下文章