作者:小四 < mailto: [email protected] > 主页:http://www.nsfocus.com/ 日期:2000-12-16 我们曾经提供过<<libnet使用举例(1-12)>>,比较详细地介绍了报文发送编程。始终 没有介绍libpcap报文捕捉编程的原因很多,tcpdump、snort等著名软件包都是基于 libpcap,加上W.Richard.Stevens的<<Unix Network Programming Vol I>>第26章推 波助澜,实在觉得没有必要继续介绍libpcap编程。更真实的原因可能是BPF、DLPI、 SOCK_PACKET三种接口编程已经被演练得太多太滥。 今天讨论的不是效率,而是可能的移植性要求。没办法,第一次使用libpcap库,举 例能深入到什么地步,不知道。如果你也是第一次用这个库,跟我来,第N次使用? 那还是忙你的去吧,别来看这篇无聊的灌水,:-P。我无聊是因为有程序要广泛可移 植,你无聊是为什么。 char * pcap_lookupdev ( char * errbuf ); 该函数返回一个网络设备接口名,类似libnet_select_device(),对于Linux就是 "eth0"一类的名字。pcap_open_live()、pcap_lookupnet()等函数将用到这个网络设 备接口名。失败时返回NULL,errbuf包含了失败原因。errbuf一般定义如下: /usr/include/pcap.h #define PCAP_ERRBUF_SIZE 256 char errbuf[ PCAP_ERRBUF_SIZE ]; pcap_t * pcap_open_live ( char * device, int snaplen, int promisc, int to_ms, char * errbuf ); 该函数用于获取一个抽象的包捕捉句柄,后续很多libpcap函数将使用该句柄,类似 文件操作函数频繁使用文件句柄。device指定网络接口设备名,比如"eth0。snaplen 指定单包最大捕捉字节数,为了保证包捕捉不至于太低效率,snaplen尽量适中,以 恰能获得所需协议层数据为准。promisc指定网络接口是否进入混杂模式,注意即使 该参数为false(0),网络接口仍然有可能因为其他原因处在混杂模式。to_ms指定毫 秒级读超时,man手册上并没有指明什么值意味着永不超时,测试下来的结论,0可能 代表永不超时。如果调用失败返回NULL,errbuf包含失败原因。 -------------------------------------------------------------------------- /usr/include/pcap.h typedef struct pcap pcap_t; pcap-int.h里定义了struct pcap {} struct pcap { int fd; int snapshot; int linktype; int tzoff; /* timezone offset */ int offset; /* offset for proper alignment */ struct pcap_sf sf; struct pcap_md md; int bufsize; /* Read buffer */ u_char * buffer; u_char * bp; int cc; u_char * pkt; /* Place holder for pcap_next() */ struct bpf_program fcode; /* Placeholder for filter code if bpf not in kernel. */ char errbuf[PCAP_ERRBUF_SIZE]; }; -------------------------------------------------------------------------- int pcap_lookupnet ( char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp, char * errbuf ); 该函数用于获取指定网络接口的IP地址、子网掩码。不要被netp的名字所迷惑,它对 应的就是IP地址,maskp对应子网掩码。 /usr/include/pcap.h typedef u_int bpf_u_int32; 显然简单理解成32-bit即可。如果调用失败则返回-1,errbuf包含失败原因。 int pcap_compile ( pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask ); 该函数用于解析过滤规则串,填写bpf_program结构。str指向过滤规则串,格式参看 tcpdump的man手册,比如: tcpdump -x -vv -n -t ip proto \\tcp and dst 192.168.8.90 and tcp[13] \& 2 = 2 这条过滤规则将捕捉所有携带SYN标志的到192.168.8.90的TCP报文。过滤规则串可以 是空串(""),表示抓取所有过路的报文。 optimize为1表示对过滤规则进行优化处理。netmask指定子网掩码,一般从 pcap_lookupnet()调用中获取。返回值小于零表示调用失败。 这个函数可能比较难于理解,涉及的概念源自BPF,Linux系统没有这种概念,但是 libpcap采用pcap_compile()和pcap_setfilter()结合的办法屏蔽了各种链路层支持 的不同,无论是SOCK_PACKET、DLPI。曾在华中Security版上写过一篇 <<内核包捕获过滤机制介绍>>,参看该文加强理解。 -------------------------------------------------------------------------- # tcpdump -d ip proto \\tcp and dst 192.168.8.90 and tcp[13] \& 2 = 2 (000) ldh [-4096] (001) jeq #0x800 jt 2 jf 13 (002) ldb [9] (003) jeq #0x6 jt 4 jf 13 (004) ld [16] (005) jeq #0xc0a8085a jt 6 jf 13 (006) ldh [6] (007) jset #0x1fff jt 13 jf 8 (008) ldxb 4*([0]&0xf) (009) ldb [x + 13] (010) and #0x2 (011) jeq #0x2 jt 12 jf 13 (012) ret #65535 (013) ret #0 # /usr/include/net/bpf.h /* Structure for BIOCSETF. */ struct bpf_program { u_int bf_len; struct bpf_insn * bf_insns; }; /* * The instruction data structure. */ struct bpf_insn { u_short code; u_char jt; u_char jf; bpf_int32 k; }; /* * Macros for insn array initializers. */ #define BPF_STMT(code, k) { (u_short)(code), 0, 0, k } #define BPF_JUMP(code, k, jt, jf) { (u_short)(code), jt, jf, k } -------------------------------------------------------------------------- int pcap_setfilter ( pcap_t * p, struct bpf_program * fp ); 该函数用于设置pcap_compile()解析完毕的过滤规则,如果你足够聪明(愚公?),完 全可以自己提供过滤规则,无须pcap_compile()介入,就象你写 Password Sniffer For I386/FreeBSD时常做的那样。成功返回0,失败返回-1。 int pcap_dispatch ( pcap_t * p, int cnt, pcap_handler callback, u_char * user ); 该函数用于捕捉报文、分发报文到预先指定好的处理函数(回调函数)。 pcap_dispatch()接收够cnt个报文便返回,如果cnt为-1意味着所有报文集中在一个 缓冲区中。如果cnt为0,仅当发生错误、读取到EOF或者读超时到了(pcap_open_live 中指定)才停止捕捉报文并返回。callback指定如下类型的回调函数,用于处理 pcap_dispatch()所捕获的报文: typedef void ( *pcap_handler ) ( u_char *, const struct pcap_pkthdr *, const u_char * ); pcap_dispatch()返回捕捉到的报文个数,如果在读取静态文件(以前包捕捉过程中存 储下来的)时碰到EOF则返回0。返回-1表示发生错误,此时可以用pcap_perror()、 pcap_geterr()显示错误信息。 下面来看看那个回调函数,总共有三个参数,第一个形参来自pcap_dispatch()的第 三个形参,一般我们自己的包捕捉程序不需要提供它,总是为NULL。第二个形参指向 pcap_pkthdr结构,该结构位于真正的物理帧前面,用于消除不同链路层支持的差异。 最后的形参指向所捕获报文的物理帧。 -------------------------------------------------------------------------- /usr/include/pcap.h /* * Each packet in the dump file is prepended with this generic header. * This gets around the problem of different headers for different * packet interfaces. */ struct pcap_pkthdr { struct timeval ts; /* time stamp */ bpf_u_int32 caplen; /* length of portion present */ bpf_u_int32 len; /* length this packet (off wire) */ }; /usr/include/net/bpf.h /* * Structure prepended to each packet. */ struct bpf_hdr { struct timeval bh_tstamp; /* time stamp */ bpf_u_int32 bh_caplen; /* length of captured portion */ bpf_u_int32 bh_datalen; /* original length of packet */ u_short bh_hdrlen; /* length of bpf header (this struct plus alignment padding) */ }; /* * Because the structure above is not a multiple of 4 bytes, some compilers * will insist on inserting padding; hence, sizeof(struct bpf_hdr) won't work. * Only the kernel needs to know about it; applications use bh_hdrlen. */ #ifdef KERNEL #define SIZEOF_BPF_HDR 18 #endif -------------------------------------------------------------------------- void pcap_close ( pcap_t * p ); 该函数用于关闭pcap_open_live()获取的包捕捉句柄,释放相关资源。 void pcap_perror ( pcap_t * p, char * prefix ); 第一形参来自pcap_open_live(),第二行参的作用类似perror()的形参,指定错误信 息的前缀,与perror()一样,结尾自动输出一个换行。 pcap_perror( p, "pcap_compile" )的输出类似这个效果: pcap_compile: unknown ip proto ... pcap_perror并不自动exit(),与perror()一样,如果需要,应该显式调用exit()。 介绍到这里,已经可以写简单的sniffer。出于完整演示目的,提供这样一个sample code。请勿询问任何关于该代码的问题,烦了。 -------------------------------------------------------------------------- /* * File : sniffer program for I386/Linux using libpcap * Version: 0.01 aleph * Author : Anonymous ( Don't ask anything about this program, please. ) * Complie: gcc -O3 -o pcap pcap_sniffer.c -lpcap `libnet-config --defines --cflags` -Wall * : strip pcap * Usage : ./pcap -h * Date : 2000-12-15 16:35 */ /******************************************************************* * * * Head File * * * *******************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <pcap.h> #include <libnet.h> /* for LIBNET_TCP_H */ /******************************************************************* * * * Macro * * * *******************************************************************/ #define SUCCESS 0 #define FAILURE -1 typedef void Sigfunc ( int ); /* for signal handlers */ /******************************************************************* * * * Static Global Var * * * *******************************************************************/ static pcap_t * pcap_fd = NULL; /* 抽象的包捕捉句柄 */ /******************************************************************* * * * Function Prototype * * * *******************************************************************/ static void Atexit ( void ( * func ) ( void ) ); static void bpf_dump ( struct bpf_program * p, int option ); char * bpf_image ( struct bpf_insn * p, int n ); static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen ); static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet ); static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel ); static void pcap_read ( pcap_t * p ); static void sig_end ( int signo ); Sigfunc * signal ( int signo, Sigfunc * func ); static Sigfunc * Signal ( int signo, Sigfunc * func ); /* for our signal() function */ static void terminate ( void ); static void usage ( char * arg ); /*----------------------------------------------------------------------*/ static void Atexit ( void ( * func ) ( void ) ) { if ( atexit( func ) != 0 ) { exit( FAILURE ); } return; } /* end of Atexit */ static void bpf_dump ( struct bpf_program * p, int option ) { struct bpf_insn * insn; int i; int n = p->bf_len; insn = p->bf_insns; if ( option > 2 ) { fprintf( stderr, "%d\n", n ); for ( i = 0; i < n; ++insn, ++i ) { fprintf( stderr, "%u %u %u %u\n", insn->code, insn->jt, insn->jf, insn->k ); } return; } if ( option > 1 ) { for ( i = 0; i < n; ++insn, ++i ) { fprintf( stderr, "{ 0x%x, %d, %d, 0x%08x },\n", insn->code, insn->jt, insn->jf, insn->k ); } return; } for ( i = 0; i < n; ++insn, ++i ) { puts( bpf_image( insn, i ) ); } } /* end of bpf_dump */ char * bpf_image ( struct bpf_insn * p, int n ) { int v; char * fmt; char * op; static char image[256]; char operand[64]; v = p->k; switch ( p->code ) { default: op = "unimp"; fmt = "0x%x"; v = p->code; break; case BPF_RET|BPF_K: op = "ret"; fmt = "#%d"; break; case BPF_RET|BPF_A: op = "ret"; fmt = ""; break; case BPF_LD|BPF_W|BPF_ABS: op = "ld"; fmt = "[%d]"; break; case BPF_LD|BPF_H|BPF_ABS: op = "ldh"; fmt = "[%d]"; break; case BPF_LD|BPF_B|BPF_ABS: op = "ldb"; fmt = "[%d]"; break; case BPF_LD|BPF_W|BPF_LEN: op = "ld"; fmt = "#pktlen"; break; case BPF_LD|BPF_W|BPF_IND: op = "ld"; fmt = "[x + %d]"; break; case BPF_LD|BPF_H|BPF_IND: op = "ldh"; fmt = "[x + %d]"; break; case BPF_LD|BPF_B|BPF_IND: op = "ldb"; fmt = "[x + %d]"; break; case BPF_LD|BPF_IMM: op = "ld"; fmt = "#0x%x"; break; case BPF_LDX|BPF_IMM: op = "ldx"; fmt = "#0x%x"; break; case BPF_LDX|BPF_MSH|BPF_B: op = "ldxb"; fmt = "4*([%d]&0xf)"; break; case BPF_LD|BPF_MEM: op = "ld"; fmt = "M[%d]"; break; case BPF_LDX|BPF_MEM: op = "ldx"; fmt = "M[%d]"; break; case BPF_ST: op = "st"; fmt = "M[%d]"; break; case BPF_STX: op = "stx"; fmt = "M[%d]"; break; case BPF_JMP|BPF_JA: op = "ja"; fmt = "%d"; v = n + 1 + p->k; break; case BPF_JMP|BPF_JGT|BPF_K: op = "jgt"; fmt = "#0x%x"; break; case BPF_JMP|BPF_JGE|BPF_K: op = "jge"; fmt = "#0x%x"; break; case BPF_JMP|BPF_JEQ|BPF_K: op = "jeq"; fmt = "#0x%x"; break; case BPF_JMP|BPF_JSET|BPF_K: op = "jset"; fmt = "#0x%x"; break; case BPF_JMP|BPF_JGT|BPF_X: op = "jgt"; fmt = "x"; break; case BPF_JMP|BPF_JGE|BPF_X: op = "jge"; fmt = "x"; break; case BPF_JMP|BPF_JEQ|BPF_X: op = "jeq"; fmt = "x"; break; case BPF_JMP|BPF_JSET|BPF_X: op = "jset"; fmt = "x"; break; case BPF_ALU|BPF_ADD|BPF_X: op = "add"; fmt = "x"; break; case BPF_ALU|BPF_SUB|BPF_X: op = "sub"; fmt = "x"; break; case BPF_ALU|BPF_MUL|BPF_X: op = "mul"; fmt = "x"; break; case BPF_ALU|BPF_DIV|BPF_X: op = "div"; fmt = "x"; break; case BPF_ALU|BPF_AND|BPF_X: op = "and"; fmt = "x"; break; case BPF_ALU|BPF_OR|BPF_X: op = "or"; fmt = "x"; break; case BPF_ALU|BPF_LSH|BPF_X: op = "lsh"; fmt = "x"; break; case BPF_ALU|BPF_RSH|BPF_X: op = "rsh"; fmt = "x"; break; case BPF_ALU|BPF_ADD|BPF_K: op = "add"; fmt = "#%d"; break; case BPF_ALU|BPF_SUB|BPF_K: op = "sub"; fmt = "#%d"; break; case BPF_ALU|BPF_MUL|BPF_K: op = "mul"; fmt = "#%d"; break; case BPF_ALU|BPF_DIV|BPF_K: op = "div"; fmt = "#%d"; break; case BPF_ALU|BPF_AND|BPF_K: op = "and"; fmt = "#0x%x"; break; case BPF_ALU|BPF_OR|BPF_K: op = "or"; fmt = "#0x%x"; break; case BPF_ALU|BPF_LSH|BPF_K: op = "lsh"; fmt = "#%d"; break; case BPF_ALU|BPF_RSH|BPF_K: op = "rsh"; fmt = "#%d"; break; case BPF_ALU|BPF_NEG: op = "neg"; fmt = ""; break; case BPF_MISC|BPF_TAX: op = "tax"; fmt = ""; break; case BPF_MISC|BPF_TXA: op = "txa"; fmt = ""; break; } /* end of switch */ ( void )sprintf( operand, fmt, v ); ( void )sprintf( image, ( BPF_CLASS( p->code ) == BPF_JMP && BPF_OP( p->code ) != BPF_JA ) ? "(%03d) %-8s %-16s jt %d\tjf %d" : "(%03d) %-8s %s", n, op, operand, n + 1 + p->jt, n + 1 + p->jf ); return image; } /* end of bpf_image */ static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen ) { u_long offset; int i, j, k; fprintf( stderr, "byteArray [ %lu bytes ] ----> \n", ( long unsigned int )byteArrayLen ); if ( byteArrayLen <= 0 ) { return; } i = 0; offset = 0; for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 ) { fprintf( stderr, "%08X ", ( unsigned int )offset ); for ( j = 0; j < 16; j++, i++ ) { if ( j == 8 ) { fprintf( stderr, "-%02X", byteArray[i] ); } else { fprintf( stderr, " %02X", byteArray[i] ); } } fprintf( stderr, " " ); i -= 16; for ( j = 0; j < 16; j++, i++ ) { /* if ( isprint( (int)byteArray[i] ) ) */ if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) ) { fprintf( stderr, "%c", byteArray[i] ); } else { fprintf( stderr, "." ); } } fprintf( stderr, "\n" ); } /* end of for */ k = byteArrayLen - i; if ( k <= 0 ) { return; } fprintf( stderr, "%08X ", ( unsigned int )offset ); for ( j = 0 ; j < k; j++, i++ ) { if ( j == 8 ) { fprintf( stderr, "-%02X", byteArray[i] ); } else { fprintf( stderr, " %02X", byteArray[i] ); } } i -= k; for ( j = 16 - k; j > 0; j-- ) { fprintf( stderr, " " ); } fprintf( stderr, " " ); for ( j = 0; j < k; j++, i++ ) { if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) ) { fprintf( stderr, "%c", byteArray[i] ); } else { fprintf( stderr, "." ); } } fprintf( stderr, "\n" ); return; } /* end of outputBinary */ static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet ) { outputBinary( ( u_char * )packet, ( size_t )( pcap_head->caplen ) ); return; } /* end of pcap_callback */ static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel ) { pcap_t * p = NULL; char errbuf[ PCAP_ERRBUF_SIZE ]; struct bpf_program bpf; bpf_u_int32 ip, mask; if ( dev == NULL ) { if ( ( dev = pcap_lookupdev( errbuf ) ) == NULL ) { fprintf( stderr, "%s\n", errbuf ); exit( FAILURE ); } } fprintf( stderr, "[ device --> %s ]\n", dev ); /* 1表示进入混杂模式 */ if ( ( p = pcap_open_live( dev, snaplen, 1, timeout, errbuf ) ) == NULL ) { fprintf( stderr, "%s\n", errbuf ); exit( FAILURE ); } if ( pcap_lookupnet( dev, &ip, &mask, errbuf ) == -1 ) { exit( FAILURE ); } /* 1表示优化过滤规则 */ if ( pcap_compile( p, &bpf, filter, 1, mask ) < 0 ) { /* for example, pcap_compile: unknown ip proto ... */ pcap_perror( p, "pcap_compile" ); exit( FAILURE ); } if ( dumplevel >= 0 ) { bpf_dump( &bpf, dumplevel ); exit( SUCCESS ); } else if ( pcap_setfilter( p, &bpf ) == -1 ) { exit( FAILURE ); } return( p ); } /* end of pcap_init */ static void pcap_read ( pcap_t * p ) { // static u_long count = 0; while ( 1 ) { pcap_dispatch( p, 1, pcap_callback, NULL ); // fprintf( stderr, "count = %lu\n", ( long unsigned int )count ); // count++; } /* end of while */ return; } /* end of pcap_read */ static void sig_end ( int signo ) { fprintf( stderr, "\n\nsig_end = %d\n", signo ); exit( SUCCESS ); } /* end of sig_end */ Sigfunc * signal ( int signo, Sigfunc * func ) { struct sigaction act, oact; act.sa_handler = func; sigemptyset( &act.sa_mask ); act.sa_flags = 0; if ( signo == SIGALRM ) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */ #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */ #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( SIG_ERR ); } return( oact.sa_handler ); } /* end of signal */ static Sigfunc * Signal ( int signo, Sigfunc * func ) /* for our signal() function */ { Sigfunc * sigfunc; if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR ) { exit( FAILURE ); } return( sigfunc ); } /* end of Signal */ static void terminate ( void ) { if ( pcap_fd != NULL ) { pcap_close( pcap_fd ); } fprintf( stderr, "\n" ); return; } /* end of terminate */ static void usage ( char * arg ) { fprintf( stderr, " Usage: %s [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout]\n", arg ); exit( FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { char * dev = NULL; char filter[300] = ""; /* "ip proto \\tcp and dst 192.168.8.90 and tcp[13] & 2 = 2" */ int snaplen = LIBNET_ETH_H + LIBNET_IP_H + LIBNET_TCP_H; int timeout = 0; /* 值为0是否表示不设置读超时 */ int dumplevel = -1; int c, i; opterr = 0; /* don't want getopt() writing to stderr */ while ( ( c = getopt( argc, argv, "d:hi:s:t:" ) ) != EOF ) { switch ( c ) { case 'd': dumplevel = atoi( optarg ); break; case 'i': dev = optarg; /* 指定网络接口设备 */ break; case 's': snaplen = atoi( optarg ); case 't': timeout = atoi( optarg ); break; case 'h': case '?': usage( argv[0] ); break; } /* end of switch */ } /* end of while */ argc -= optind; argv += optind; if ( argc > 0 ) { for ( i = 0; i < argc; i++ ) { if ( ( strlen( filter ) + strlen( argv[i] ) ) > 256 ) { fprintf( stderr, "Checking your filter.\n" ); return( FAILURE ); } strcat( filter, argv[i] ); strcat( filter, " " ); } } fprintf( stderr, "[ filter --> %s ]\n", filter ); Atexit( terminate ); for ( i = 1; i < 9; i++ ) { Signal( i, sig_end ); } Signal( SIGTERM, sig_end ); pcap_fd = pcap_init( dev, filter, snaplen, timeout, dumplevel ); pcap_read( pcap_fd ); return( SUCCESS ); } /* end of main */ /*----------------------------------------------------------------------*/ -------------------------------------------------------------------------- Usage: ./pcap [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout] libpcap的好处还是很多,比如不需要为解析过滤规则耗费精力。这个程序再次演示 了很多经典Unix编程技巧,比如getopt()、signal()、atexit(),回调函数部分没有 做什么实际工作,看你自己发挥了。顺便提一句,即使是个小程序,也应该保持良好 的风格,在华中看到太多不负责任的提问中的垃圾代码,实在是有辱C语言的传奇。 |