Libnids-API(中文版)

来源:互联网 发布:java string转map方法 编辑:程序博客网 时间:2024/05/16 12:06

Libnids-API(中文版)
====================
libnids-1.16
====================


1. 简介
2. IP碎片重组
3. TCP流还原
4. 例子程序
5. 基本的Libnids结构和函数
6. 其它有用的技巧


1. 简介

Libnids定义的数据结构和函数的声明集中在头文件nids.h中。使用Libnids的应用程序
必须包含这个文件,并且要与Libnids.a静态库进行连接。


应用程序的Main函数一般是这样的形式:
main()
{
应用程序的私有处理,与Libnids无关;
可选择的Libnids参数的修改;
if(!nids_init())那里出了问题,结束;
出测回调函数;
nids_run();
//正常情况下不可达;
}

另外的方法将在以后提及。

2.IP碎片重组

为了接收到Libnids看到的所有的IP包(包括分片的;带有无效校验和的IP包)程序员应该
定义一个如下类型的回调函数:

void ip_frag_func(struct ip * a_packet, int len)

在调用nids_init之后,这个函数应该使用Libnids进行注册:

nids_register_ip_frag(ip_frag_func);

函数ip_frag_func 将被Libnids调用;参数a_packet将指向一个收到的数据报,len是其长度。

类似的,为了接受到适当地数据包,这些包是要被目标主机接收的(就是,没有分片的包或者已经
完成重组的分片包;头部的正确性已经校验过),应该定义一个回调函数:

void ip_func(struct ip * a_packet)

并注册它:

nids_register_ip(ip_func);

3. TCP流的重组

为了接收到一个TCP流中交换的数据,必须定义一个回调函数:

void tcp_callback(struct tcp_stream * ns, void ** param)

tcp_stream结构包含了所有的关于一个TCP连接的信息,例如:它包含两个half_stream(名为
client和server)结构类型的域,每一个描述了一侧的连接,我们将在后面解释所有的域。

tcp_stream的一个域的名字为nids_state,tcp_callback的行为依赖于这个域的值。

* ns->nids_state==NIDS_JUST_EST

这里,ns描述了一个已经建立的连接。Tcp_callback必须判断当将来这个连接中
的数据到达的时候,它是否希望被通知。所有的连接参数都是可用的(ip地址,端
口号等等)。如果这个连接是我们感兴趣的,tcp_callback函数通知Libnids它希
望接收什么数据(发到客户端的数据;发到服务器的数据;发到客户端的紧急数据;
发到服务器的紧急数据),然后函数返回。


* ns->nids_state==NIDS_DATA

这里,新的数据已经到达,half_stream结构(tcp_stream结构的成员)包含数据
缓冲区。

* nids_state域的其它值(NIDS_CLOSE, NIDS_RESET,NIDS_TIMEOUT)意味着连接
已经被关闭。Tcp_callback应给释放其所分配的资源,即使它需要。

4. 例子程序

现在,让我们看看一个简单的例子程序,这个程序在stderr中显示Libnids所监视到的所
有TCP连接所交换的数据。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include "nids.h"

#define int_ntoa(x) inet_ntoa(*((struct in_addr *)&x))

// struct tuple4 包含TCP连接的地址和端口号
// 下面的辅助函数生成一个类似10.0.0.1,1024,10.0.0.2,23的字符串

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;
}

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;
// 在本程序中我们跟踪所有的流所以。。。。。:
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 establishedn", buf);
return;
}
if (a_tcp->nids_state == NIDS_CLOSE)
{
// 连接已经正常结束
fprintf (stderr, "%s closingn", buf);
return;
}
if (a_tcp->nids_state == NIDS_RESET)
{
// 连接已经通过RST关闭。
fprintf (stderr, "%s resetn", 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;
}
// 我们不必必须检查是否客户端的紧急数据已经到达
// 因为我们没有增加a_tcp->client.collect_urg的值
// 因此,我们还有一些正常的数据关心

if (a_tcp->client.count_new)
{
//客户端的新数据
hlf = &a_tcp->client; // 现在我们将处理hlf变量
// 这个变量指向客户端一边的连接。
strcat (buf, "(<-)"); // 数据的符号方向
}
else
{
hlf = &a_tcp->server; // 类似的
strcat (buf, "(->)");
}
fprintf(stderr,"%s",buf); // 我们打印连接参数(saddr, daddr, sport, dport)
// 和数据流向(-> or <-)

write(2,hlf->data,hlf->count_new); // 我们打印最新到达的数据

}
return ;
}

int
main ()
{
// 这里我们可以改变Libnids的params,例如 nids_params.n_hosts=256;
if (!nids_init ())
{
fprintf(stderr,"%sn",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
nids_run ();
return 0;
}

5. 基本的Libnids结构和函数

现在是时候仔细描述一些提到的Libnids的结构的时候了,它们在头文件nids.h中声明。

struct tuple4 // TCP连接参数
{
unsigned short source,dest; // 客户端和服务器端口号
unsigned long saddr,daddr; // 客户端和服务器IP地址
};


struct half_stream // TCP连接一侧的描述结构
{
char state; // socket 状态 (例如:TCP_ESTABLISHED )

char collect; // if >0, 那么数据应该被存放到data缓冲区中。否则,
// 这个方向的数据流将被忽略
// 看一下samples/sniff.c文件如何使用这个域

char collect_urg; // 类似的,判断是否为紧急数据。

char * data; // 正常数据缓冲区

unsigned char urgdata; // one-byte buffer for urgent data

int count; // 自连接建立以来已经有多少字节已经发送到data缓冲区中

int offset; // offset (in data stream) of first byte stored in
// the "data" buffer; additional explanations
// follow

int count_new; // 多少字节将被发送到data缓冲区中last (this) time;
// if == 0, 没有新数据到达。

char count_new_urg; // if != 0,新的紧急数据到达

... // other fields are auxiliary for libnids

};


struct tcp_stream
{
struct tuple4 addr; // 连接参数(saddr, daddr, sport, dport)
char nids_state; // 连接的逻辑状态
struct half_stream client,server; // 描述连接的客户端和服务器端的结构
... // other fields are auxiliary for libnids
};

在上面的例子程序中,函数tcp_callback打印hlf->data缓冲区的数据到stderr上,
并且这些数据不再需要了。在tcp_callback返回后,默认情况下Libnids释放这些数
据占用的空间。hlf->offset域将增加丢弃的字节数,并接新的数据将存储到data缓冲区
的开始。如果上面的操作不是我们所希望的,(例如,数据处理器需要至少N字节的输入来
处理,并且迄今为止Libnids接受到的count_new<N字节)应该调用程序:

void nids_discard(struct tcp_stream * a_tcp, int num_bytes)

在tcp_callback返回前。结果,在tcp_callback返回后,Libnids将丢弃data缓冲区中至
多num_bytes字节的前面的内容(更新offset域,移动剩下的数据到data缓冲区的开始位置)。
如果nids_discard函数没有被调用(像上面的例子程序),缓冲区hlf->data包含
hlf->count_new字节的内容。一般情况下,缓冲区hlf->data中的字节数等于
hlf->count-hlf->offset.

感谢函数nids_discard,程序员不得不必须把接受到的数据拷贝到一个独立的缓冲区中。
hlf->data将一直包含尽可能多的数据。可是,经常发生对每一个ibnids_callback, tcp
stream对维护其辅助数据结构的请求。例如,如果我们希望检测一个针对wu-ftpd的攻击(这个
攻击包括在服务器上建立深层目录)我们需要将Ftpd守护进程的目录存储到某一个地方。
通过Ftp客户端的CWD指令怾改变。这就是tcp_callback的第二个参数的目的。它是一个指向
每一个(libnids_callback, tcp stream)对所私有的数据的指针。

典型情况如下:

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;
如果连接是我们不感兴趣的,返回;
a_conn=malloc of some data structure//内存分配
init of a_conn//初始化
*ptr=a_conn // 这个值在将来的调用中将被传递给tcp_callback_2
增加一些 "collect" 域
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_callback相似的函数允许跟踪同一个TCP流。(with acertain non-default exception).

Libnids参数可以通过全局变量nids_params的域的改变来修改。如下声明:
struct nids_prm
{
int n_tcp_streams; // 用于存储tcp_stream结构信息的Hash表的大小
// libnis将只能同时跟踪 3/4 * n_tcp_streams连接的数据流
// 默认值:1040
// 如果设置为0,Libnids将不再进行TCP流的组装
int n_hosts; // 用于存储关于IP碎片重组的信息的Hash表的大小
// 默认值: 256
char * device; // Libnids用于监听数据包的设备接口
// 默认值 :NULL, 将通过调用pcap_lookupdev函数来接决定;
// 特殊值all将致使Libnids试图通过所有的设备接口截获数据包
// (这个参数在高于2.2.0Linux 核心版本有效)
// 参见 doc/NEW_LIBPCAP
int sk_buff_size; // 结构sk_buff的大小,这个结构是由Linux核心定义的,核心用于
// 数据包排列,如果这个参数和sizeof(struct sk_buff)地值不同,
// Libnids可以通过攻击其资源管理而被绕过。见TEST文件。
// 如果你是一个喜欢妄想的人,那么检查你网络中主机的sizeof(sk_buff)
// 并调整这个参数,默认值:168
int dev_addon; // 在sk_buff结构中保留了多少字节用于存储网络接口信息;如果dev_addon==-1,
// 将在nids_init()中根据Libnids监听的接口的类型进行改正。it
// 默认值: -1.
void (*syslog)(); // 参见nids_params定义部分的描述
int syslog_level; // 如果 nids_params.syslog==nids_syslog,那么这个域将决定
// 系统守护进程syslogd报告事件所使用的等级loglevel.
// 默认值: LOG_ALERT
int scan_num_hosts;// 用于存储关于端口扫描的信息的Hash表的大小;Libndis能够检测
// 到的同时发生的端口扫描企图。如果设置为0,端口扫描检测将被关闭
// 默认值:256
int scan_num_ports;// 多少个TCP端口必须被同一个源地址扫描
// 默认值: 10
int scan_delay; // 两个端口之间最大的扫描间隔
// 用于使Libnids可以报告端口扫描企图
// 默认值:3000
void (*no_mem)(); // 当Libndis的内存资源耗尽时调用此函数
// 它应该终止当前进程
int (*ip_filter)(struct ip*); // 这个参数当IP数据包到达时才会被考虑
// 如果ip_filter返回non-zero, 处理这个包否则忽略掉
// 通过这种方式,可以只监控所选中的主机,而不是整个子网
// 默认函数: (nids_ip_filter) 一般返回值为:1
char *pcap_filter; // 用于pcap地过滤字符串,默认情况下为NULL。
// 需要了解的是这强应用到link-layer,所以象"tcp dst port 23"
// 一样的过滤器无法控制碎片传输。
int promisc; // 如果非零,Libnids读取数据包的设备将被设置为混杂模式
// 默认为:1
int one_loop_less; // 默认情况下不可用
} 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(在libnids.c中定义)举例说明了如何解读传递给nids_params.syslog
的参数。
Nids_syslog记录信息道系统守护进程syslogd中。忽视像消息速率和可用磁盘空间之类的事情。

如果对UDP数据报感兴趣,应该声明:
void udp_callback(struct tuple4 * addr, char * buf, int len, struct ip
* iph);

并注册它:

nids_register_udp(udp_callback)

参数addr包含地址信息,buf指针指向UDP数据包携带的数据,len是数据长度,iph是包
含此UDP数据报的Ip包的指针。校验和已经进行过。

6. 其他有用的技巧

void nids_killtcp(struct tcp_stream * a_tcp)

通过发送RST结束一个a_tcp描述的TCP连接。


使用nids_run()存在一个不利的因素,应用程序变成了完全的包驱动,有时在没有包到达的
时候执行某些操作也是必须得。代替nids_run(),我们可以使用函数:

int nids_next()

这个函数调用 pcap_next() 而不是 pcap_loop, 就是说它之处理一个包,如果没有包可用,
程序将处于睡眠状态,Nids_next()成功时返回1;出错返回0。(nids_errbuf 包含相关联
的信息)

标准情况下,当时用nids_next()时,因各应用程序将睡眠在一个select()函数中,同时在fd_set
中引入了一个监听得socket fd. 这个fd可以通过下面的调用获得:

int nids_getfd()

它返回一个文件描述符,失败的时候返回-1。(nids_errbuf包含出错信息)。


头文件nids.h定义了常量 NIDS_MAJOR (1) 和NIDS_MINOR (16), 这些常量用于判定Libnids的
现行版本。如果HAVE_NEW_PCAP(同样在nids.h中定义) 设置为1,则Libnids已被编译为支持
通过所有设备截获数据包。(见 NEW_LIBPCAP 文件)


典型情况下,TCP流携带的数据可以被分成协议依赖(protocol-dependent)记录(say, lines of
input)。一个tcp_callback函数可以接收一定数量的数据,这些数据可以包含多余一个地记录。因此
tcp_callback函数应该对所有接受到的数据重复进行协议解析程序。这增加了代码的复杂性。

如果nids_params.one_loop_less是非零的,Libnids的行为会发生轻微的变化。如果一个callback
消耗了部分而不是全部的新到达的数据,Libnids立刻再次调用它。只有在缓冲区中不再有未处理的保
留数据,并且rcv->count_new相应减少。因而,callback能一次处理一个记录,Libnids将再次调用
它,知道没有新的数据剩余或者没有数据可以处理。不幸的,在两个以上的callback函数读取同一个
TCP流的一半的时候,这一行为导致了可怕的语法问题。所以,如果nids_params.one_loop_less如果
为非零,你不允许分配两个或者更多callback函数处理同一个TCP流的一半。不幸的是现存的接口不能
向callback函数宣布这个错误,因此你必须自己小心,你已经被警告过了。

原创粉丝点击