網絡安全工具開發函數庫介紹
作者: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