标签:过滤 int 数据 tde ohs cas host name 以太网 核心
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; /* 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(