网络安全工具开发函数库介绍
作者:backend <backend@nsfocus.com>
<http://www.nsfocus.com>
日期:2000-07-16
---[[ 前言 ]]--------------------------------------------
本文主要介绍几个在UNIX系统平台上开发网络安全工具时最常用的library。此外还提供一些如何使用这些开发库进行网络安全工具开发的设计框架和流程。希望能和对网络安全工具开发有兴趣的朋友共同交流,互相促进。
众所周知,基于socket的网络编程已成为当今不可替代的编程方法。这种编程思想将网络通讯当作“文件”描述字进行处理,对这个“网络文件”(即 socket,套接字/套接口)的操作从编程者的角度来讲与普通的文件操作(如读、写、打开、关闭等)大同小异,从而极大地简化了网络程序开发过程。
在众多的网络安全程序、工具和软件中都是基于socket设计和开发的。由于在安全程序中通常需要对网络通讯的细节(如连接双方地址/端口、服务类型、传输控制等)进行检查、处理或控制,象数据包截获、数据包头分析、数据包重写、甚至截断连接等,都几乎在每个网络安全程序中必须实现。为了简化网络安全程序的编写过程,提高网络安全程序的性能和健壮性,同时使代码更易重用与移植,最好的方法就是将最常用和最繁复的过程函数,如监听套接口的打开/关闭、数据包截获、数据包构造/发送/接收等,封装起来,以API library的方式提供给开发人员使用。
---[[ C开发库简介 ]]-------------------------------------
在Unix系统平台上的网络安全工具开发中,目前最为流行的C API library有libnet、libpcap、libnids和libicmp等。它们分别从不同层次和角度提供了不同的功能函数。使网络开发人员能够忽略网络底层细节的实现,从而专注于程序本身具体功能的设计与开发。其中,
* libnet提供的接口函数主要实现和封装了数据包的构造和发送过程。
* libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程。
* libnids提供的接口函数主要实现了开发网络入侵监测系统所必须的一些结构框架。
* libicmp等相对较为简单,它封装的是ICMP数据包的主要处理过程(构造、发送、接收等)。
利用这些C函数库的接口,网络安全工具开发人员可以很方便地编写出具有结构化强、健壮性好、可移植性高等特点的程序,如scanner、sniffer、firewall、IDS等。
---[[ libnet ]]------------------------------------------
libnet库的最新版本为1.0.0,它一共约7600行C源代码,33个源程序文件,12个C头文件,50余个自定义函数,提供的接口函数包含15种数据包生成器和两种数据包发送器(IP层和数据链路层)。目前只支持IPv4,不支持IPv6。已经过测试的系统平台包括:
* OpenBSD 2.6snap, 2.5, 2.4, 2.3, 2.2 (i386)
* FreeBSD 4.0-STABLE, 3.3-STABLE, 3.2-RELEASE, 3.1-CURRENT, 3.0, 2.2 (i386)
* NetBSD 1.3.2 (i386)
* BSD/OS 3.x (i386)
* BSDi 3.0 (i386)
* Linux 2.2.x, 2.0.3x, 2.1.124 (i386, alpha) (libc: 2.4.x, glibc: 2.0.x)
* Solaris 7 (SPARC, gcc 2.7.2[13], 2.8.2), 2.6 (SPARC, gcc 2.8.2),
2.5.x (SPARC, gcc 2.7.2[13])
* IRIX 6.2
* MacOS 5.3rhapsody (powerpc)
libnet提供的接口函数按其作用可分为四类:
* 内存管理(分配和释放)函数
* 地址解析函数
* 数据包构造函数
* 数据包发送函数
以下分别列出这些接口函数及其功能(其参数含义简单易懂,不再解释):
★ 内存管理函数
单数据包内存初始化:
int libnet_init_packet(u_short packet_size, u_char **buf);
单数据包内存释放:
void libnet_destroy_packet(u_char **buf);
多数据包内存初始化:
int libnet_init_packet_arena(struct libnet_arena **arena,
u_short packet_num, u_short packet_size);
访问多数据包内存中的下一个数据包:
u_char *libnet_next_packet_from_arena(struct libnet_arena **arena,
u_short packet_size);
多数据包内存释放:
void libnet_destroy_packet_arena(struct libnet_arena **arena);
★ 地址解析函数
解析主机名:
u_char *libnet_host_lookup(u_long ip, u_short use_name);
解析主机名(可重入函数):
void libnet_host_lookup_r(u_long ip, u_short use_name, u_char *buf);
域名解析:
u_long libnet_name_resolve(u_char *ip, u_short use_name);
获取接口设备IP地址:
u_long libnet_get_ipaddr(struct libnet_link_int *l,
const u_char *device, const u_char *ebuf);
获取接口设备硬件地址:
struct ether_addr *libnet_get_hwaddr(struct libnet_link_int *l,
const u_char *device,
const u_char *ebuf);
★ 数据包构造函数
ARP协议数据包:
int libnet_build_arp(u_short hrdw, u_short prot, u_short h_len,
u_short p_len, u_short op, u_char *s_ha,
u_char *s_pa, u_char *t_ha, u_char *t_pa,
const u_char *payload, int payload_len,
u_char *packet_buf);
DNS协议数据包:
int libnet_build_dns(u_short id, u_short flags, u_short num_q,
u_short num_answ_rr, u_short num_auth_rr,
u_short num_add_rr, const u_char * payload,
int payload_len, u_char *packet_buf);
以太网协议数据包:
int libnet_build_ethernet(u_char *daddr, u_char *saddr, u_short id,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_ECHO / ICMP_ECHOREPLY):
int libnet_build_icmp_echo(u_char type, u_char code, u_short id,
u_short seq, const u_char *payload,
int payload_len, u_char *packet_buf);
ICMP协议数据包(ICMP_MASKREQ / ICMP_MASKREPLY):
int libnet_build_icmp_mask(u_char type, u_char code, u_short id,
u_short seq, u_long mask,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_UNREACH):
int libnet_build_icmp_unreach(u_char type, u_char code,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_TIMEXCEED):
int libnet_build_icmp_timeexceed(u_char type, u_char code,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_REDIRECT):
int libnet_build_icmp_redirect(u_char type, u_char code, u_long gateway,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_TSTAMP / ICMP_TSTAMPREPLY):
int libnet_build_icmp_timestamp(u_char type, u_char code, u_short id,
u_short seq, n_time otime, n_time rtime,
n_time ttime, const u_char *payload,
int payload_len, u_char *packet_buf);
IGMP协议数据包:
int libnet_build_igmp(u_char type, u_char code, u_long ip,
const u_char *payload, int payload_len,
u_char *packet_buf);
IP协议数据包:
int libnet_build_ip(u_short len, u_char tos, u_short ip_id, u_short frag,
u_char ttl, u_char protocol, u_long saddr,
u_long daddr, const u_char *payload, int payload_len,
u_char *packet_buf);
OSPF路由协议数据包:
int libnet_build_ospf(u_short len, u_char type, u_long router_id,
u_long area_id, u_short auth_type,
const char *payload, int payload_s, u_char *buf);
OSPF路由协议数据包(Hello):
int libnet_build_ospf_hello(u_long netmask, u_short interval,
u_char options, u_char priority,
u_int dead_interval, u_long des_router,
u_long backup, u_long neighbor,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(DataBase Description (DBD)):
int libnet_build_ospf_dbd(u_short len, u_char options, u_char type,
u_int sequence_num, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Request (LSR)):
int libnet_build_ospf_lsr(u_int type, u_int ls_id, u_long adv_router,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State Update (LSU)):
int libnet_build_ospf_lsu(u_int num, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Acknowledgement (LSA)):
int libnet_build_ospf_lsa(u_short age, u_char options, u_char type,
u_int ls_id, u_long adv_router,
u_int sequence_num, u_short len,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(OSPF Link Sate NetworkLink State Router):
int libnet_build_ospf_lsa_net(u_long netmask, u_int router_id,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State Router):
int libnet_build_ospf_lsa_rtr(u_short flags, u_short num, u_int id,
u_int data, u_char type, u_char tos,
u_short metric, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Summary):
int libnet_build_ospf_lsa_sum(u_long netmask, u_int metric, u_int tos,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State AS External):
int libnet_build_ospf_lsa_as(u_long netmask, u_int metric,
u_long fwd_addr, u_int tag,
const char *payload, int payload_s,
u_char *buf);
RIP路由协议数据包:
int libnet_build_rip(u_char cmd, u_char ver, u_short domain,
u_short addr_fam, u_short route_tag, u_long ip,
u_long mask, u_long next_hop, u_long metric,
const u_char *payload, int payload_len,
u_char *packet_buf);
TCP协议数据包:
int libnet_build_tcp(u_short th_sport, u_short th_dport, u_long th_seq,
u_long th_ack, u_char th_flags, u_short th_win,
u_short th_urg, const u_char *payload,
int payload_len, u_char *packet_buf);
UDP协议数据包:
int libnet_build_udp(u_short sport, u_short dport, const u_char *payload,
int payload_len, u_char *packet_buf);
IP协议数据包选项:
int libnet_insert_ipo(struct ipoption *opt, u_char opt_len,
u_char *packet_buf);
TCP协议数据包选项:
int libnet_insert_tcpo(struct tcpoption *opt, u_char opt_len,
u_char *packet_buf);
★ 数据包发送函数
打开raw socket:
int libnet_open_raw_sock(int protocol);
关闭raw socket:
int libnet_close_raw_sock(int socket);
选择接口设备:
int libnet_select_device(struct sockaddr_in *sin,
u_char **device, u_char *ebuf);
打开链路层接口设备:
struct libnet_link_int *libnet_open_link_interface(char *device,
char *ebuf);
关闭链路层接口设备:
int libnet_close_link_interface(struct libnet_link_int *l);
发送IP数据包:
int libnet_write_ip(int socket, u_char *packet, int packet_size);
发送链路层数据包:
int libnet_write_link_layer(struct libnet_link_int *l,
const u_char *device, u_char *packet,
int packet_size);
检验和计算:
int libnet_do_checksum(u_char *packet, int protocol, int packet_size);
★ 相关的支持函数
随机数种子生成器:
int libnet_seed_prand();
获取随机数:
u_long libnet_get_prand(int modulus);
16进制数据输出:
void libnet_hex_dump(u_char * buf, int len, int swap, FILE *stream);
端口列表链初始化:
int libnet_plist_chain_new(struct libnet_plist_chain **plist,
char *token_list);
获取端口列表链的下一项(端口范围):
int libnet_plist_chain_next_pair(struct libnet_plist_chain *plist,
u_short *bport, u_short *eport);
端口列表链输出显示:
int libnet_plist_chain_dump(struct libnet_plist_chain *plist);
获取端口列表链:
u_char *libnet_plist_chain_dump_string(struct libnet_plist_chain *plist);
端口列表链内存释放:
void libnet_plist_chain_free(struct libnet_plist_chain *plist);
★ 数据常量
==================================================================================
数据包头大小定义:
常量名 数值(字节数)
LIBNET_ARP_H 28
LIBNET_DNS_H 12
LIBNET_ETH_H 14
LIBNET_ICMP_H 4
LIBNET_ICMP_ECHO_H 8
LIBNET_ICMP_MASK_H 12
LIBNET_ICMP_UNREACH_H 8
LIBNET_ICMP_TIMXCEED_H 8
LIBNET_ICMP_REDIRECT_H 8
LIBNET_ICMP_TS_H 20
LIBNET_IGMP_H 8
LIBNET_IP_H 20
LIBNET_RIP_H 24
LIBNET_TCP_H 20
LIBNET_UDP_H 8
==================================================================================
数据包内存常量:
常量名 含义
LIBNET_PACKET TCP/UDP数据包头 + IP数据包头使用的内存
LIBNET_OPTS IP或TCP选项使用的内存
LIBNET_MAX_PACKET IP_MAXPACKET (65535字节)使用的内存
==================================================================================
随机数发生器常量(libnet_get_prand()函数使用):
常量名 数值
LIBNET_PRAND_MAX 65535
LIBNET_PR2 0 - 2
LIBNET_PR8 0 - 255
LIBNET_PR16 0 - 32767
LIBNET_PRu16 0 - 65535
LIBNET_PR32 0 - 2147483647
LIBNET_PRu32 0 - 4294967295
==================================================================================
错误消息常量(libnet_error()函数使用):
常量名 含义
LIBNET_ERR_WARNING 警告类型消息
LIBNET_ERR_CRITICAL 紧急类型消息
LIBNET_ERR_FATAL 致命错误消息
==================================================================================
libnet_host_lookup()、libnet_host_lookup_r()和libnet_name_resolve()函数使用的常量:
常量名 含义
LIBNET_DONT_RESOLVE 不将IP地址解析为FQDN名
LIBNET_RESOLVE 尝试将IP地址解析为FQDN名
==================================================================================
宏定义
宏名 功能
LIBNET_GET_ARENA_SIZE(arena) 返回多数据包内存缓冲区大小(字节数)
LIBNET_GET_ARENA_REMAINING_BYTES(arena) 返回多数据包内存缓冲区剩余空间大小(字节数)
LIBNET_PRINT_ETH_ADDR(e) 输出显示ether_addr结构中的以太网地址
==================================================================================
---[[ libnet应用实例 ]]----------------------------------
利用libnet函数库开发应用程序的基本步骤非常简单:
1、数据包内存初始化;
2、网络接口初始化;
3、构造所需数据包;
4、计算数据包检验和;
5、发送数据包;
6、关闭网络接口;
7、释放数据包内存。
以下是四个使用了libnet接口函数编写的数据包发送程序。在编译前必须确保libnet库已成功安装。
============================ cut here ============================
/* Example 1 [raw socket api - TCP packet] */
/* gcc -Wall `libnet-config --defines` libnet-example-x.c -o libnet-example-x \
`libnet-config --libs` */
#include <libnet.h>
void usage(char *);
int main(int argc, char **argv)
{
int network, /* our network interface */
packet_size, /* packet size */
c; /* misc */
u_long src_ip, dst_ip; /* ip addresses */
u_short src_prt, dst_prt; /* ports */
u_char *cp, *packet; /* misc / packet */
printf("libnet example code:\tmodule 1\n\n");
printf("packet injection interface:\traw socket\n");
printf("packet type:\t\t\tTCP [no payload]\n");
src_ip = 0;
dst_ip = 0;
src_prt = 0;
dst_prt = 0;
while((c = getopt(argc, argv, "d:s:")) != EOF)
{
switch (c)
{
/*
* We expect the input to be of the form `ip.ip.ip.ip.port`. We
* point cp to the last dot of the IP address/port string and
* then seperate them with a NULL byte. The optarg now points to
* just the IP address, and cp points to the port.
*/
case 'd':
if (!(cp = strrchr(optarg, '.')))
{
usage(argv[0]);
}
*cp++ = 0;
dst_prt = (u_short)atoi(cp);
if (!(dst_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad destination IP address: %s\n", optarg);
}
break;
case 's':
if (!(cp = strrchr(optarg, '.')))
{
usage(argv[0]);
}
*cp++ = 0;
src_prt = (u_short)atoi(cp);
if (!(src_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad source IP address: %s\n", optarg);
}
break;
}
}
if (!src_ip || !src_prt || !dst_ip || !dst_prt)
{
usage(argv[0]);
exit(EXIT_FAILURE);
}
/*
* We're just going to build a TCP packet with no payload using the
* raw sockets API, so we only need memory for a TCP header and an IP
* header.
*/
packet_size = LIBNET_IP_H + LIBNET_TCP_H;
/*
* Step 1: Memory initialization (interchangable with step 2).
*/
libnet_init_packet(packet_size, &packet);
if (packet == NULL)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet failed\n");
}
/*
* Step 2: Network initialization (interchangable with step 1).
*/
network = libnet_open_raw_sock(IPPROTO_RAW);
if (network == -1)
{
libnet_error(LIBNET_ERR_FATAL, "Can't open network.\n");
}
/*
* Step 3: Packet construction (IP header).
*/
libnet_build_ip(LIBNET_TCP_H, /* size of the packet sans IP header */
IPTOS_LOWDELAY, /* IP tos */
242, /* IP ID */
0, /* frag stuff */
48, /* TTL */
IPPROTO_TCP, /* transport protocol */
src_ip, /* source IP */
dst_ip, /* destination IP */
NULL, /* payload (none) */
0, /* payload length */
packet); /* packet header memory */
/*
* Step 3: Packet construction (TCP header).
*/
libnet_build_tcp(src_prt, /* source TCP port */
dst_prt, /* destination TCP port */
0xa1d95, /* sequence number */
0x53, /* acknowledgement number */
TH_SYN, /* control flags */
1024, /* window size */
0, /* urgent pointer */
NULL, /* payload (none) */
0, /* payload length */
packet + LIBNET_IP_H); /* packet header memory */
/*
* Step 4: Packet checksums (TCP header only).
*/
if (libnet_do_checksum(packet, IPPROTO_TCP, LIBNET_TCP_H) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed\n");
}
/*
* Step 5: Packet injection.
*/
c = libnet_write_ip(network, packet, packet_size);
if (c < packet_size)
{
libnet_error(LN_ERR_WARNING,
"libnet_write_ip only wrote %d bytes\n", c);
}
else
{
printf("construction and injection completed, wrote all %d bytes\n", c);
}
/*
* Shut down the interface.
*/
if (libnet_close_raw_sock(network) == -1)
{
libnet_error(LN_ERR_WARNING,
"libnet_close_raw_sock couldn't close the interface");
}
/*
* Free packet memory.
*/
libnet_destroy_packet(&packet);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
void usage(char *name)
{
fprintf(stderr, "usage: %s -s s_ip.s_port -d d_ip.d_port\n", name);
}
============================ cut here ============================
/* Example 2 [link layer api - ICMP_MASK] */
/* gcc -Wall `libnet-config --defines` libnet-example-x.c -o libnet-example-x `libnet-config --libs` */
#include <libnet.h>
void usage(char *);
u_char enet_src[6] = {0x0d, 0x0e, 0x0a, 0x0d, 0x00, 0x00};
u_char enet_dst[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
int
main(int argc, char *argv[])
{
int packet_size, /* size of our packet */
c; /* misc */
u_long src_ip, dst_ip; /* source ip, dest ip */
u_char *packet; /* pointer to our packet buffer */
char err_buf[LIBNET_ERRBUF_SIZE]; /* error buffer */
u_char *device; /* pointer to the device to use */
struct libnet_link_int *network; /* pointer to link interface struct */
printf("libnet example code:\tmodule 2\n\n");
printf("packet injection interface:\tlink layer\n");
printf("packet type:\t\t\tICMP net mask [no payload]\n");
device = NULL;
src_ip = 0;
dst_ip = 0;
while ((c = getopt(argc, argv, "i:d:s:")) != EOF)
{
switch (c)
{
case 'd':
if (!(dst_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad destination IP address: %s\n", optarg);
}
break;
case 'i':
device = optarg;
break;
case 's':
if (!(src_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad source IP address: %s\n", optarg);
}
break;
default:
exit(EXIT_FAILURE);
}
}
if (!src_ip || !dst_ip)
{
usage(argv[0]);
exit(EXIT_FAILURE);
}
/*
* Step 1: Network Initialization (interchangable with step 2).
*/
if (device == NULL)
{
struct sockaddr_in sin;
/*
* Try to locate a device.
*/
if (libnet_select_device(&sin, &device, err_buf) == -1)
{
libnet_error(LIBNET_ERR_FATAL,
"libnet_select_device failed: %s\n", err_buf);
}
printf("device:\t\t\t\t%s\n", device);
}
if ((network = libnet_open_link_interface(device, err_buf)) == NULL)
{
libnet_error(LIBNET_ERR_FATAL,
"libnet_open_link_interface: %s\n", err_buf);
}
/*
* We're going to build an ICMP packet with no payload using the
* link-layer API, so this time we need memory for a ethernet header
* as well as memory for the ICMP and IP headers.
*/
packet_size = LIBNET_IP_H + LIBNET_ETH_H + LIBNET_ICMP_MASK_H;
/*
* Step 2: Memory Initialization (interchangable with step 1).
*/
if (libnet_init_packet(packet_size, &packet) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet failed\n");
}
/*
* Step 3: Packet construction (ethernet header).
*/
libnet_build_ethernet(enet_dst,
enet_src,
ETHERTYPE_IP,
NULL,
0,
packet);
/*
* Step 3: Packet construction (ICMP header).
*/
libnet_build_icmp_mask(ICMP_MASKREPLY, /* type */
0, /* code */
242, /* id */
0, /* seq */
0xffffffff, /* mask */
NULL, /* payload */
0, /* payload_s */
packet + LIBNET_ETH_H + LIBNET_IP_H);
/*
* Step 3: Packet construction (IP header).
*/
libnet_build_ip(ICMP_MASK_H,
0, /* IP tos */
242, /* IP ID */
0, /* Frag */
64, /* TTL */
IPPROTO_ICMP, /* Transport protocol */
src_ip, /* Source IP */
dst_ip, /* Destination IP */
NULL, /* Pointer to payload (none) */
0,
packet + LIBNET_ETH_H); /* Packet header memory */
/*
* Step 4: Packet checksums (ICMP header *AND* IP header).
*/
if (libnet_do_checksum(packet + ETH_H, IPPROTO_ICMP, LIBNET_ICMP_MASK_H) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed\n");
}
if (libnet_do_checksum(packet + ETH_H, IPPROTO_IP, LIBNET_IP_H) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed\n");
}
/*
* Step 5: Packet injection.
*/
c = libnet_write_link_layer(network, device, packet, packet_size);
if (c < packet_size)
{
libnet_error(LN_ERR_WARNING,
"libnet_write_link_layer only wrote %d bytes\n", c);
}
else
{
printf("construction and injection completed, wrote all %d bytes\n", c);
}
/*
* Shut down the interface.
*/
if (libnet_close_link_interface(network) == -1)
{
libnet_error(LN_ERR_WARNING,
"libnet_close_link_interface couldn't close the interface");
}
/*
* Free packet memory.
*/
libnet_destroy_packet(&packet);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
void usage(char *name)
{
fprintf(stderr, "usage: %s [-i interface] -s s_ip -d d_ip\n", name);
}
============================ cut here ============================
/* Example 3 [raw socket api - ICMP_ECHO using an arena] */
/* gcc -Wall `libnet-config --defines` libnet-example-x.c -o libnet-example-x \
`libnet-config --libs` */
#include <libnet.h>
void usage(char *);
int main(int argc, char **argv)
{
int network, n, c, number_of_packets, packet_size;
struct libnet_arena arena, *arena_p;
u_char *packets[10];
u_long src_ip, dst_ip;
printf("libnet example code:\tmodule 3\n\n");
printf("packet injection interface:\tlink layer\n");
printf("packet type:\t\t\tICMP_ECHO [no payload] using an arena\n");
src_ip = 0;
dst_ip = 0;
while((c = getopt(argc, argv, "d:s:")) != EOF)
{
switch (c)
{
case 'd':
if (!(dst_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad destination IP address: %s\n", optarg);
}
break;
case 's':
if (!(src_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad source IP address: %s\n", optarg);
}
break;
}
}
if (!src_ip || !dst_ip)
{
usage(argv[0]);
exit(EXIT_FAILURE);
}
/*
* We're just going to build an ICMP packet with no payload using the
* raw sockets API, so we only need memory for a ICMP header and an IP
* header.
*/
packet_size = LIBNET_IP_H + LIBNET_ICMP_ECHO_H;
/*
* Let's just build say, 10 packets.
*/
number_of_packets = 10;
arena_p = &arena;
if (libnet_init_packet_arena(&arena_p, number_of_packets, packet_size) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet_arena failed\n");
}
else
{
printf("Allocated an arena of %ld bytes..\n",
LIBNET_GET_ARENA_SIZE(arena));
}
network = libnet_open_raw_sock(IPPROTO_RAW);
if (network == -1)
{
libnet_error(LIBNET_ERR_FATAL, "Can't open the network.\n");
}
for (n = 0; n < number_of_packets; n++)
{
printf("%ld bytes remaining in arena\n",
LIBNET_GET_ARENA_REMAINING_BYTES(arena));
packets[n] = libnet_next_packet_from_arena(&arena_p, packet_size);
if (!packets[n])
{
libnet_error(LIBNET_ERR_WARNING, "Arena is empty\n");
continue;
}
libnet_build_ip(ICMP_ECHO_H, /* Size of the payload */
IPTOS_LOWDELAY | IPTOS_THROUGHPUT, /* IP tos */
242, /* IP ID */
0, /* frag stuff */
48, /* TTL */
IPPROTO_ICMP, /* transport protocol */
src_ip, /* source IP */
dst_ip, /* destination IP */
NULL, /* pointer to payload */
0, /* payload length */
packets[n]); /* packet header memory */
libnet_build_icmp_echo(ICMP_ECHO, /* type */
0, /* code */
242, /* id */
5, /* seq */
NULL, /* pointer to payload */
0, /* payload length */
packets[n] + LIBNET_IP_H); /* packet header memory */
if (libnet_do_checksum(packets[n], IPPROTO_ICMP, LIBNET_ICMP_ECHO_H) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed\n");
}
c = libnet_write_ip(network, packets[n], packet_size);
if (c < packet_size)
{
libnet_error(LN_ERR_WARNING,
"libnet_write_ip only wrote %d bytes\n", c);
}
else
{
printf("construction and injection of packet %d of %d completed, wrote all %d bytes\n",
n + 1, number_of_packets, c);
}
}
libnet_destroy_packet_arena(&arena_p);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
void usage(char *name)
{
fprintf(stderr, "usage: %s -s source_ip -d destination_ip\n ", name);
}
============================ cut here ============================
/* Example 4 [link-layer api - UDP packet using port list chaining] */
/* gcc -Wall `libnet-config --defines` libnet-example-x.c -o libnet-example-x \
`libnet-config --libs` */
#include <libnet.h>
#define MAX_PAYLOAD_SIZE 1024
void usage(char *);
u_char enet_src[6] = {0x0d, 0x0e, 0x0a, 0x0d, 0x00, 0x00};
u_char enet_dst[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
int main(int argc, char *argv[])
{
int packet_size, /* size of our packet */
payload_size, /* size of our packet */
c; /* misc */
u_long src_ip, dst_ip; /* source ip, dest ip */
u_short bport, eport; /* beginning and end ports */
u_short cport; /* current port */
u_char payload[MAX_PAYLOAD_SIZE]; /* packet payload */
u_char *packet; /* pointer to our packet buffer */
char err_buf[LIBNET_ERRBUF_SIZE]; /* error buffer */
u_char *device; /* pointer to the device to use */
struct libnet_link_int *network; /* pointer to link interface struct */
struct libnet_plist_chain plist; /* plist chain */
struct libnet_plist_chain *plist_p; /* plist chain pointer */
printf("libnet example code:\tmodule 4\n\n");
printf("packet injection interface:\tlink layer\n");
printf("packet type:\t\t\tUDP [with payload] using port list chaining\n");
plist_p = NULL;
device = NULL;
src_ip = 0;
dst_ip = 0;
while ((c = getopt(argc, argv, "i:d:s:p:")) != EOF)
{
switch (c)
{
case 'd':
if (!(dst_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad destination IP address: %s\n", optarg);
}
break;
case 'i':
device = optarg;
break;
case 's':
if (!(src_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
{
libnet_error(LIBNET_ERR_FATAL,
"Bad source IP address: %s\n", optarg);
}
break;
case 'p':
plist_p = &plist;
if (libnet_plist_chain_new(&plist_p, optarg) == -1)
{
libnet_error(LIBNET_ERR_FATAL,
"Could not build port list\n");
}
break;
default:
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
if (!src_ip || !dst_ip || !plist_p)
{
usage(argv[0]);
exit(EXIT_FAILURE);
}
c = argc - optind;
if (c != 1)
{
usage(argv[0]);
exit(EXIT_FAILURE);
}
memset(payload, 0, sizeof(payload));
strncpy(payload, argv[optind], strlen(argv[optind]));
/*
* Step 1: Network Initialization (interchangable with step 2).
*/
if (device == NULL)
{
struct sockaddr_in sin;
/*
* Try to locate a device.
*/
if (libnet_select_device(&sin, &device, err_buf) == -1)
{
libnet_error(LIBNET_ERR_FATAL,
"libnet_select_device failed: %s\n", err_buf);
}
printf("device:\t\t\t\t%s\n", device);
}
if ((network = libnet_open_link_interface(device, err_buf)) == NULL)
{
libnet_error(LIBNET_ERR_FATAL,
"libnet_open_link_interface: %s\n", err_buf);
}
/*
* Get the payload from the user. Hrm. This might fail on a Sparc
* if byte alignment is off...
*/
payload_size = strlen(payload);
/*
* We're going to build a UDP packet with a payload using the
* link-layer API, so this time we need memory for a ethernet header
* as well as memory for the ICMP and IP headers and our payload.
*/
packet_size = LIBNET_IP_H + LIBNET_ETH_H + LIBNET_UDP_H + payload_size;
/*
* Step 2: Memory Initialization (interchangable with step 1).
*/
if (libnet_init_packet(packet_size, &packet) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet failed\n");
}
/*
* Step 3: Packet construction (ethernet header).
*/
libnet_build_ethernet(enet_dst,
enet_src,
ETHERTYPE_IP,
NULL,
0,
packet);
/*
* Step 3: Packet construction (IP header).
*/
libnet_build_ip(LIBNET_UDP_H + payload_size,
0, /* IP tos */
242, /* IP ID */
0, /* Frag */
64, /* TTL */
IPPROTO_UDP, /* Transport protocol */
src_ip, /* Source IP */
dst_ip, /* Destination IP */
NULL, /* Pointer to payload (none) */
0,
packet + LIBNET_ETH_H); /* Packet header memory */
while (libnet_plist_chain_next_pair(plist_p, &bport, &eport))
{
while (!(bport > eport) && bport != 0)
{
cport = bport++;
/*
* Step 3: Packet construction (UDP header).
*/
libnet_build_udp(242, /* source port */
cport, /* dest. port */
payload, /* payload */
payload_size, /* payload length */
packet + LIBNET_ETH_H + LIBNET_IP_H);
/*
* Step 4: Packet checksums (ICMP header *AND* IP header).
*/
if (libnet_do_checksum(packet + ETH_H, IPPROTO_UDP, LIBNET_UDP_H + payload_size) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed\n");
}
if (libnet_do_checksum(packet + ETH_H, IPPROTO_IP, LIBNET_IP_H) == -1)
{
libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed\n");
}
/*
* Step 5: Packet injection.
*/
c = libnet_write_link_layer(network, device, packet, packet_size);
if (c < packet_size)
{
libnet_error(LN_ERR_WARNING,
"libnet_write_link_layer only wrote %d bytes\n", c);
}
else
{
printf("construction and injection completed, wrote all %d bytes, port %d\n",
c, cport);
}
}
}
/*
* Shut down the interface.
*/
if (libnet_close_link_interface(network) == -1)
{
libnet_error(LN_ERR_WARNING,
"libnet_close_link_interface couldn't close the interface");
}
/*
* Free packet memory.
*/
libnet_destroy_packet(&packet);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
}
void usage(char *name)
{
fprintf(stderr, "usage: %s [-i interface] -s s_ip -d d_ip -p port list payload\n", name);
}
---[[ libpcap ]]------------------------------------------
libpcap的英文意思是 Packet Capturelibrary,即数据包捕获函数库。该库提供的C函数接口可用于需要捕获经过网络接口(只要经过该接口,目标地址不一定为本机)数据包的系统开发上。由Berkeley大学Lawrence Berkeley National Laboratory研究院的Van Jacobson、CraigLeres和Steven McCanne编写,目前的最新版本为0.4。该函数库支持Linux、Solaris和*BSD系统平台。
主要接口函数说明如下:
pcap_t *pcap_open_live(char *device, int snaplen,
int promisc, int to_ms, char *ebuf)
获得用于捕获网络数据包的数据包捕获描述字。device参数为指定打开
的网络设备名。snaplen参数定义捕获数据的最大字节数。promisc指定
是否将网络接口置于混杂模式。to_ms参数指定超时时间(毫秒)。
ebuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递错误消
息。
pcap_t *pcap_open_offline(char *fname, char *ebuf)
打开以前保存捕获数据包的文件,用于读取。fname参数指定打开的文
件名。该文件中的数据格式与tcpdump和tcpslice兼容。"-"为标准输
入。ebuf参数则仅在pcap_open_offline()函数出错返回NULL时用于传
递错误消息。
pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)
打开用于保存捕获数据包的文件,用于写入。fname参数为"-"时表示
标准输出。出错时返回NULL。p参数为调用pcap_open_offline()或
pcap_open_live()函数后返回的pcap结构指针。fname参数指定打开
的文件名。如果返回NULL,则可调用pcap_geterr()函数获取错误消
息。
char *pcap_lookupdev(char *errbuf)
用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络
设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的
错误消息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,
bpf_u_int32 *maskp, char *errbuf)
获得指定网络设备的网络号和掩码。netp参数和maskp参数都是
bpf_u_int32指针。如果函数出错,则返回-1,同时errbuf中存放相
关的错误消息。
int pcap_dispatch(pcap_t *p, int cnt,
pcap_handler callback, u_char *user)
捕获并处理数据包。cnt参数指定函数返回前所处理数据包的最大值。
cnt=-1表示在一个缓冲区中处理所有的数据包。cnt=0表示处理所有
数据包,直到产生以下错误之一:读取到EOF;超时读取。callback
参数指定一个带有三个参数的回调函数,这三个参数为:一个从
pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构
的指针,和一个数据包大小的u_char指针。如果成功则返回读取到的
字节数。读取到EOF时则返回零值。出错时则返回-1,此时可调用
pcap_perror()或pcap_geterr()函数获取错误消息。
int pcap_loop(pcap_t *p, int cnt,
pcap_handler callback, u_char *user)
功能基本与pcap_dispatch()函数相同,只不过此函数在cnt个数据包
被处理或出现错误时才返回,但读取超时不会返回。而如果为
pcap_open_live()函数指定了一个非零值的超时设置,然后调用
pcap_dispatch()函数,则当超时发生时pcap_dispatch()函数会返回。
cnt参数为负值时pcap_loop()函数将始终循环运行,除非出现错误。
void pcap_dump(u_char *user, struct pcap_pkthdr *h,
u_char *sp)
向调用pcap_dump_open()函数打开的文件输出一个数据包。该函数可
作为pcap_dispatch()函数的回调函数。
int pcap_compile(pcap_t *p, struct bpf_program *fp,
char *str, int optimize, bpf_u_int32 netmask)
将str参数指定的字符串编译到过滤程序中。fp是一个bpf_program结
构的指针,在pcap_compile()函数中被赋值。optimize参数控制结果
代码的优化。netmask参数指定本地网络的网络掩码。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
指定一个过滤程序。fp参数是bpf_program结构指针,通常取自
pcap_compile()函数调用。出错时返回-1;成功时返回0。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
返回指向下一个数据包的u_char指针。
int pcap_datalink(pcap_t *p)
返回数据链路层类型,例如DLT_EN10MB。
int pcap_snapshot(pcap_t *p)
返回pcap_open_live被调用后的snapshot参数值。
int pcap_is_swapped(pcap_t *p)
返回当前系统主机字节与被打开文件的字节顺序是否不同。
int pcap_major_version(pcap_t *p)
返回写入被打开文件所使用的pcap函数的主版本号。
int pcap_minor_version(pcap_t *p)
返回写入被打开文件所使用的pcap函数的辅版本号。
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
向pcap_stat结构赋值。成功时返回0。这些数值包括了从开始
捕获数据以来至今共捕获到的数据包统计。如果出错或不支持
数据包统计,则返回-1,且可调用pcap_perror()或
pcap_geterr()函数来获取错误消息。
FILE *pcap_file(pcap_t *p)
返回被打开文件的文件名。
int pcap_fileno(pcap_t *p)
返回被打开文件的文件描述字号码。
void pcap_perror(pcap_t *p, char *prefix)
在标准输出设备上显示最后一个pcap库错误消息。以prefix参
数指定的字符串为消息头。
char *pcap_geterr(pcap_t *p)
返回最后一个pcap库错误消息。
char *pcap_strerror(int error)
如果strerror()函数不可用,则可调用pcap_strerror函数替代。
void pcap_close(pcap_t *p)
关闭p参数相应的文件,并释放资源。
void pcap_dump_close(pcap_dumper_t *p)
关闭相应的被打开文件。
<<< 待续 >>>
<<< 续前 >>>
---[[ libnids ]]------------------------------------------
一、简介
libnids的英文意思是 Network Intrusion Detect System library,即网络入侵监测系统函数库。它是在前面介绍的两种C函数接口库libnet和libpcap的基础上开发的,封装了开发NIDS所需的许多通用型函数。linids提供的接口函数监视流经本地的所有网络通信,检查数据包等。除此之外,还具有重组TCP数据段、处理IP分片包和监测TCP端口扫描的功能。利用libnids接口函数库,NIDS开发者不需要再编写底层的网络处理代码,只需专注于NIDS本身功能的实现即可。
libnids支持Linux、Solaris和*BSD系统平台,目前最新版本为1.13。
二、IP分片数据包
为了使libnids能接收所有的IP数据包(包括分片包、畸形包等),程序员需要定义如下的回调函数:
void ip_frag_func(struct ip * a_packet)
在调用nids_init()函数初始化后,使用nids的函数进行注册:
nids_register_ip_frag(ip_frag_func);
这样回调函数ip_frag_func会在适当的时候由libnids调用,参数a_packet指针将指向接收到的数据报。
类似地,如果仅接收目标主机会接受的数据包(如非碎片包、重组包或头部校验正确的数据包等),需要定义如下回调函数:
void ip_func(struct ip * a_packet)
然后注册:
nids_register_ip(ip_func);
三、TCP数据流重组
要接收TCP流在交换的数据,必须定义如下回调函数:
void tcp_callback(struct tcp_stream * ns, void ** param)
tcp_stream结构提供了一个TCP连接的所有信息。例如,它包含了客户端与服务器端的half_stream结构。下文会对该结构的字段进行解释。
tcp_stream结构有一个名为nids_state的字段。此字段的数值将决定tcp_callback的操作。
(a) ns->nids_state==NIDS_JUST_EST时,ns表示一个刚刚建立的连接。
tcp_callback可以据此决定是否对该连接的后续数据进行检查。如
需要检查,tcp_callback回调函数将通知libnids它希望接收哪些
数据(如到客户端的数据、到服务器端的数据、到客户端的紧急数
据或到服务器端的紧急数据等),然后返回。
(b) ns->nids_state==NIDS_DATA时,表示ns连接接收到新的数据。
half_stream结构中的缓冲区用于存放这些数据。
(c) nids_state字段为其它数值(NIDS_CLOSE、NIDS_RESET、
NIDS_TIMEOUT)时,表示该连接已经关闭了。tcp_callback函数应
释放相关资源。
四、一个简单的实例
下面的源代码是一个非常简单的程序,它将libnids捕获的所有TCP连接交换的数据输出显示到标准输出设备上。
-----------------------BEGINING OF CODE--------------------------------
#include "nids.h"
#include <string.h>
#include <stdio.h>
extern char * inet_ntoa(unsigned long);
// tuple4结构包含了TCP连接两端的IP地址和端口,以下函数将它们转换为字符串
// 格式,如10.0.0.1,1024, 10.0.0.2,23
char *
adres (struct tuple4 addr)
{
static char buf[256];
strcpy (buf, inet_ntoa (addr.saddr));
sprintf (buf + strlen (buf), ",%i,", addr.source);
strcat (buf, inet_ntoa (addr.daddr));
sprintf (buf + strlen (buf), ",%i", addr.dest);
return buf;
}
void
tcp_callback (struct tcp_stream *a_tcp, void ** this_time_not_needed)
{
char buf[1024];
strcpy (buf, adres (a_tcp->addr)); // we put conn params into buf
if (a_tcp->nids_state == NIDS_JUST_EST)
{
// a_tcp所定义的连接已经建立。此处可视程序需要添加额外
// 的判断处理。如if (a_tcp->addr.dest != 23) return;表
// 示不处理目标端口为23的数据包。
// 本例需要处理(显示)所有数据包,故:
a_tcp->client.collect++; // 需要处理客户端接收的数据
a_tcp->server.collect++; // 和服务器端接收的数据
a_tcp->server.collect_urg++; // 需要处理服务器端接收的紧急数据
#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT
a_tcp->client.collect_urg++; // 需要处理客户端接收的紧急数据
// (打开编译选项才有效)
#endif
fprintf (stderr, "%s established\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_CLOSE)
{
// TCP连接正常关闭
fprintf (stderr, "%s closing\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_RESET)
{
// TCP连接因RST数据包而关闭
fprintf (stderr, "%s reset\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_DATA)
{
// 接收到新数据,下面判断决定是否显示
struct half_stream *hlf;
if (a_tcp->server.count_new_urg)
{
// 服务器端接收的紧急数据
strcat(buf,"(urgent->)");
buf[strlen(buf)+1]=0;
buf[strlen(buf)]=a_tcp->server.urgdata;
write(1,buf,strlen(buf));
return;
}
#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT
if (a_tcp->client.count_new_urg)
{
// 客户端接收的紧急数据
strcat(buf,"(urgent->)");
buf[strlen(buf)+1]=0;
buf[strlen(buf)]=a_tcp->server.urgdata;
write(1,buf,strlen(buf));
return;
}
#endif
if (a_tcp->client.count_new)
{
// 客户端接收的数据
hlf = &a_tcp->client; // 准备显示客户端接收的数据
strcat (buf, "(<-)"); // 指示数据流方向
}
else
{
hlf = &a_tcp->server; // 准备显示服务器端接收的数据
strcat (buf, "(->)"); // 指示数据流方向
}
fprintf(stderr,"%s",buf); // 首先输出显示连接双方的IP地址、端口
// 和数据流方向
write(2,hlf->data,hlf->count_new); // 输出显示接收到的新数据
}
return ;
}
int
main ()
{
// 此处可自定义libnids的全局变量,如:
// nids_params.n_hosts=256;
if (!nids_init () )
{
fprintf(stderr,"%s\n",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
nids_run ();
// NOT REACHED
return 0;
}
---------------------------END OF CODE------------------------------------
五、libnids的数据结构及接口函数
libnids库的所有数据结构及接口函数都在"nids.h"头文件中声明。
struct tuple4 // TCP连接参数
{
unsigned short source,dest; // 客户端和服务器端的端口号
unsigned long saddr,daddr; // 客户端和服务器端的IP地址
};
struct half_stream // TCP连接一端的数据结构
{
char state; // 套接字状态(如TCP_ESTABLISHED)
char collect; // 如果大于0,则保存其数据到缓冲区中,否则忽略
char collect_urg; // 如果大于0,则保存紧急数据,否则忽略
char * data; // 正常数据的缓冲区
unsigned char urgdata; // 紧急数据缓冲区
int count; // 自从连接建立以来保存到"data"缓冲区的数据字节
// 数总和
int offset; // 保存到"data"缓冲区的首字节数据偏移量
int count_new; // 最近一次接收到的数据字节数;如果为0,则无数
// 到达
char count_new_urg; // 如果非0,表示有新的紧急数据到达
... // libnids库使用的辅助字段
};
struct tcp_stream
{
struct tuple4 addr; // TCP连接参数(saddr, daddr, sport, dport)
char nids_state; // TCP连接的逻辑状态
struct half_stream client,server; // 描述客户端与服务器端的数据结构
... // libnids库使用的辅助字段
};
在上面的实例程序中,回调函数tcp_callback输出显示hlf->data缓冲区中的数据到标准输出设备上。这些数据在tcp_callback函数返回后,由libnids自动释放这些数据所占用的内存空间。同时,hlf->offset字段将增加被丢弃数据的字节数,而新接收到的数据则存放到"data"缓冲区的起始处。
如果在其它应用中不进行如上例的操作(例如,数据处理过程至少需要N个字节的输入数据,而libnids只接收到的数据字节数count_new<N),则需要在tcp_callback函数返回前调用如下函数:
void nids_discard(struct tcp_stream * a_tcp, int num_bytes)
此时,当回调函数tcp_callback返回后linids将"data"缓冲区的前num_bytes字节数据,同时计算调整offset字段的数值,并将剩余数据移动到缓冲区的起始处。
如果始终不调用nids_discard()函数(如上面实例),hlf->data缓冲区中将包含hlf->count_new字节数据。通常情况下,在hlf->data缓冲区中的数据字节数等于hlf->count - hlf->offset。
有了nids_discard()函数,程序员就不必拷贝接收到的数据到另外的缓冲区中,hlf->data缓冲区将总是尽可能保存足够的数据。然后,有时会有保留数据包特定数据的需要。例如,我们希望能监测到针对wu-ftpd服务器的"CWD"溢出攻击,就需要跟踪检查ftp客户端发送的"CWD"命令。此时就需要tcp_callback回调函数具有第二个参数了。此参数是某TCP连接私有数据的指针。处理过程如下:
void
tcp_callback_2 (struct tcp_stream * a_tcp, struct conn_param **ptr)
{
if (a_tcp->nids_state==NIDS_JUST_EST)
{
struct conn_param * a_conn;
if the connection is uninteresting, return;
a_conn=malloc of some data structure
init of a_conn
*ptr=a_conn // this value will be passed to tcp_callback_2 in future
// calls
increase some of "collect" fields
return;
}
if (a_tcp->nids_state==NIDS_DATA)
{
struct conn_param *current_conn_param=*ptr;
using current_conn_param and the newly received data from the net
we search for attack signatures, possibly modyfying
current_conn_param
return ;
}
...
}
nids_register_tcp和nids_register_ip*函数可被任意次调用。在同一个TCP连接中使用两种不同的回调函数是允许的。
libnids库定义了一个全局变量结构nids_params,其声明如下:
struct nids_prm
{
int n_tcp_streams; // 存放tcp_stream结构的hash表大小。
// 缺省值:1024
int n_hosts; // 存放IP分片信息的hash表大小
// 缺省值:256
char * device; // libnids监听的接口设备名
// 缺省值 == NULL,即由pcap_lookupdev函数确定
int sk_buff_size; // (Linux内核)sk_buff结构大小
// 缺省值:168
int dev_addon; // sk_buff为网络接口保留的字节数
// 如果dev_addon==-1,则由nids_init函数确定
// 缺省值:-1
void (*syslog)(); // 日志函数指针
int syslog_level; // 如果nids_params.syslog==nids_syslog,则此字段值
// 将确定日志等级loglevel
// 缺省值:LOG_ALERT
int scan_num_hosts;// 存放端口扫描信息的hash表大小。
// 如果为0,则关闭端口扫描监测功能。
// 缺省值:256
int scan_num_ports;// 来自同一IP地址所扫描的TCP端口数上限
// 缺省值:10
int scan_delay; // 在两次端口扫描中的间隔时间上限(毫秒)
// 缺省值:3000
void (*no_mem)(); // 内存不足时被调用,此时应终止当前进程
int (*ip_filter)(struct ip*); // 当接收到一个IP数据包时调用。如返回值
// 非零,则处理该数据包,否则忽略。
// 缺省为(nids_ip_filter)且总返回1
char *pcap_filter; // 传递给pcap过滤器的字符串。
// 缺省值:NULL
} nids_params;
nids_params的syslog字段缺省时指向nids_syslog函数,声明如下:
void nids_syslog (int type, int errnum, struct ip *iph, void *data);
nids_params.syslog函数用于记录异常情况,如端口扫描企图,无效TCP头标志等。该字段应指向自定义的日志处理函数。nids_syslog()仅作为一个例子。nids_syslog()函数向系统守护服务syslogd发送日志消息。
使用nids_run有一个缺陷:应用程序将完全由数据包驱动(运行)。有时需要在没有数据包到达时也能处理一些任务,则作为nids_run()函数的替代,程序员可使用如下函数:
int nids_next()
此函数将调用pcap_next()函数(而不是pcap_loop()函数)。(详细资料请参阅《网络安全工具开发函数库介绍之二 ——libpcap》。) nids_next()函数成功时返回1,出错时返回0,且nids_errbuf缓冲区存放相应错误消息。
典型地,当使用nids_next()函数时,应用程序调用I/O复用函数select()阻塞,监听套接字fd在“读”描述字集合fd_set中设置。该套接字可通过如下函数获得:
int nids_getfd()
成功时返回一个文件描述字,出错时返回-1,且nids_errbuf缓冲区存放相应错误消息。
---[[ libnids应用实例 ]]----------------------------------
1、nids_next()函数的应用
============================ cut here ============================
/*
This is an example how one can use nids_getfd() and nids_next() functions.
You can replace printall.c's function main with this file.
*/
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main ()
{
// here we can alter libnids params, for instance:
// nids_params.n_hosts=256;
int fd;
int time = 0;
fd_set rset;
struct timeval tv;
if (!nids_init ())
{
fprintf(stderr,"%s\n",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
fd = nids_getfd ();
for (;;)
{
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO (&rset);
FD_SET (fd, &rset);
// add any other fd we need to take care of
if (select (fd + 1, &rset, 0, 0, &tv))
{
if (FD_ISSET(fd,&rset) // need to test it if there are other
// fd in rset
if (!nids_next ()) break;
}
else
fprintf (stderr, "%i ", time++);
}
return 0;
}
============================ cut here ============================
2、Simple sniffer
============================ cut here ============================
/*
Copyright (c) 1999 Rafal Wojtczuk <nergal@avet.com.pl>. All rights reserved.
See the file COPYING for license details.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <fcntl.h>
#include "nids.h"
#define LOG_MAX 100
#define SZLACZEK "\n--------------------------------------------------\n"
#define int_ntoa(x) inet_ntoa(*((struct in_addr *)&x))
char *
adres (struct tuple4 addr)
{
static char buf[256];
strcpy (buf, int_ntoa (addr.saddr));
sprintf (buf + strlen (buf), ",%i,", addr.source);
strcat (buf, int_ntoa (addr.daddr));
sprintf (buf + strlen (buf), ",%i : ", addr.dest);
return buf;
}
int logfd;
void
do_log (char *adres_txt, char *data, int ile)
{
write (logfd, adres_txt, strlen (adres_txt));
write (logfd, data, ile);
write (logfd, SZLACZEK, strlen (SZLACZEK));
}
void
sniff_callback (struct tcp_stream *a_tcp, void **this_time_not_needed)
{
int dest;
if (a_tcp->nids_state == NIDS_JUST_EST)
{
dest = a_tcp->addr.dest;
if (dest == 21 || dest == 23 || dest == 110 || dest == 143 || dest == 513)
a_tcp->server.collect++;
return;
}
if (a_tcp->nids_state != NIDS_DATA)
{
// seems the stream is closing, log as much as possible
do_log (adres (a_tcp->addr), a_tcp->server.data,
a_tcp->server.count - a_tcp->server.offset);
return;
}
if (a_tcp->server.count - a_tcp->server.offset < LOG_MAX)
{
// we haven't got enough data yet; keep all of it
nids_discard (a_tcp, 0);
return;
}
// enough data
do_log (adres (a_tcp->addr), a_tcp->server.data, LOG_MAX);
// Now procedure sniff_callback doesn't want to see this stream anymore.
// So, we decrease all the "collect" fields we have previously increased.
// If there were other callbacks following a_tcp stream, they would still
// receive data
a_tcp->server.collect--;
}
int
main ()
{
logfd = open ("./logfile", O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (logfd < 0)
{
perror ("opening ./logfile:");
exit (1);
}
if (!nids_init ())
{
fprintf (stderr, "%s\n", nids_errbuf);
exit (1);
}
nids_register_tcp (sniff_callback);
nids_run ();
return 0;
}
============================ cut here ============================
3、Wu-FTPd overflow attack detector
============================ cut here ============================
/*
Copyright (c) 1999 Rafal Wojtczuk <nergal@avet.com.pl>. All rights reserved.
See the file COPYING for license details.
*/
/*
This code attempts to detect attack against imapd (AUTHENTICATE hole) and
wuftpd (creation of deep directory). This code is to ilustrate use of libnids;
in order to improve readability, some simplifications were made, which enables
an attacker to bypass this code (note, the below routines should be improved,
not libnids)
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include "nids.h"
#define int_ntoa(x) inet_ntoa(*((struct in_addr *)&x))
char *
adres (struct tuple4 addr)
{
static char buf[256];
strcpy (buf, int_ntoa (addr.saddr));
sprintf (buf + strlen (buf), ",%i,", addr.source);
strcat (buf, int_ntoa (addr.daddr));
sprintf (buf + strlen (buf), ",%i", addr.dest);
return buf;
}
/*
if we find a pattern AUTHENTICATE {an_int} in data stream sent to an imap
server, where an_int >1024, it means an buffer overflow attempt. We kill the
connection.
*/
#define PATTERN "AUTHENTICATE {"
#define PATLEN strlen(PATTERN)
void
detect_imap (struct tcp_stream *a_tcp)
{
char numbuf[30];
int i, j, datalen, numberlen;
struct half_stream *hlf;
if (a_tcp->nids_state == NIDS_JUST_EST)
{
if (a_tcp->addr.dest == 143)
{
a_tcp->server.collect++;
return;
}
else
return;
}
if (a_tcp->nids_state != NIDS_DATA)
return;
hlf = &a_tcp->server;
datalen = hlf->count - hlf->offset;
if (datalen < PATLEN)
{
// we have too small amount of data to work on. Keep all data in buffer.
nids_discard (a_tcp, 0);
return;
}
for (i = 0; i <= datalen - PATLEN; i++)
if (!memcmp (PATTERN, hlf->data + i, PATLEN)) //searching for a pattern
break;
if (i > datalen - PATLEN)
{
// retain PATLEN bytes in buffer
nids_discard (a_tcp, datalen - PATLEN);
return;
}
for (j = i + PATLEN; j < datalen; j++) // searching for a closing '}'
if (*(hlf->data + j) == '}')
break;
if (j > datalen)
{
if (datalen > 20)
{
//number too long, perhaps we should log it, too
}
return;
}
numberlen = j - i - PATLEN;
memcpy (numbuf, hlf->data + i + PATLEN, numberlen); //numbuf contains
// AUTH argument
numbuf[numberlen] = 0;
if (atoi (numbuf) > 1024)
{
// notify admin
syslog(nids_params.syslog_level,
"Imapd exploit attempt, connection %s\n",adres(a_tcp->addr));
// kill the connection
nids_killtcp (a_tcp);
}
nids_discard (a_tcp, datalen - PATLEN);
return;
}
// auxiliary structure, needed to keep current dir of ftpd daemon
struct supp
{
char *currdir;
int last_newline;
};
// the below function adds "elem" string to "path" string, taking care of
// ".." and multiple '/'. If the resulting path is longer than 768,
// return value is 1, otherwise 0
int
add_to_path (char *path, char *elem, int len)
{
int plen;
char * ptr;
if (len > 768)
return 1;
if (len == 2 && elem[0] == '.' && elem[1] == '.')
{
ptr = rindex (path, '/');
if (ptr != path)
*ptr = 0;
}
else if (len > 0)
{
plen = strlen (path);
if (plen + len + 1 > 768)
return 1;
if (plen==1)
{
strncpy(path+1,elem,len);
path[1+len]=0;
}
else
{
path[plen] = '/';
strncpy (path + plen + 1, elem, len);
path[plen + 1 + len] = 0;
}
}
return 0;
}
void
do_detect_ftp (struct tcp_stream *a_tcp, struct supp **param_ptr)
{
struct supp *p = *param_ptr;
int index = p->last_newline + 1;
char *buf = a_tcp->server.data;
int offset = a_tcp->server.offset;
int n_bytes = a_tcp->server.count - offset;
int path_index, pi2, index2, remcaret;
for (;;)
{
index2 = index;
while (index2 - offset < n_bytes && buf[index2 - offset] != '\n')
index2++;
if (index2 - offset >= n_bytes)
break;
if (!strncasecmp (buf + index - offset, "cwd ", 4))
{
path_index = index + 4;
if (buf[path_index - offset] == '/')
{
strcpy (p->currdir, "/");
path_index++;
}
for (;;)
{
pi2 = path_index;
while (buf[pi2 - offset] != '\n' && buf[pi2 - offset] != '/')
pi2++;
if (buf[pi2-offset]=='\n' && buf[pi2-offset-1]=='\r')
remcaret=1;
else remcaret=0;
if (add_to_path (p->currdir, buf + path_index-offset, pi2 - path_index-remcaret))
{
// notify admin
syslog(nids_params.syslog_level,
"Ftpd exploit attempt, connection %s\n",adres(a_tcp->addr));
nids_killtcp (a_tcp);
return;
}
if (buf[pi2 - offset] == '\n')
break;
path_index = pi2 + 1;
}
}
index = index2 + 1;
}
p->last_newline = index - 1;
nids_discard (a_tcp, index - offset);
}
void
detect_ftpd (struct tcp_stream *a_tcp, struct supp **param)
{
if (a_tcp->nids_state == NIDS_JUST_EST)
{
if (a_tcp->addr.dest == 21)
{
struct supp *one_for_conn;
a_tcp->server.collect++;
one_for_conn = (struct supp *) malloc (sizeof (struct supp));
one_for_conn->currdir = malloc (1024);
strcpy (one_for_conn->currdir, "/");
one_for_conn->last_newline = 0;
*param=one_for_conn;
}
return;
}
if (a_tcp->nids_state != NIDS_DATA)
{
free ((*param)->currdir);
free (*param);
return;
}
do_detect_ftp (a_tcp, param);
}
int
main ()
{
if (!nids_init ())
{
fprintf(stderr,"%s\n",nids_errbuf);
exit(1);
}
nids_register_tcp (detect_imap);
nids_register_tcp (detect_ftpd);
nids_run ();
return 0;
}
============================ cut here ============================
<<< 待续 >>>
libpcap使用举例
作者:小四 < mailto: scz@nsfocus.com >
主页:http://www.nsfocus.com
日期:2001-01-10
URL :http://www.nsfocus.net/index.php?act=magazine&do=view&mid=760
我们曾经提供过<<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语言的传奇。
<待续>
from:http://blog.csdn.net/heiyeshuwu/archive/2007/05/03/1595629.aspx