Vim源码分析环境搭建好了here,今天拿libnids练练手,虽然cscope命令才掌握几个常用的,但是阅读速度已经很快了,相对于source insight,至少不用担心成为鼠标手了。
Libnids performs:
a) assembly of TCP segments into TCP streams
b) IP defragmentation
c) TCP port scan detection
即Libnids的功能有
下面围绕第一点[重组TCP流会话]来分析一下源码,我主要有两个问题
main函数一般是下面这个样子
int main ()
{
if (!nids_init ())
{
fprintf(stderr,"%s\n",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
nids_run ();
return 0;
}
nids_init
主要进行
init_procs()初始化全局回调函数链表,包括ip_frag_procs, ip_procs, tcp_procs, udp_procs; ip_frag_procs链表默认有一个处理函数gen_ip_frag_proc; ip_procs链表默认有一个处理函数gen_ip_proc; tcp_procs和udp_procs为空;
tcp_init()
nids_register_tcp(tcp_callback)
将回调函数插入到链表tcp_procs头部;
nids_run
主要的工作就是调用pcap_loop循环抓包,将数据交付nids_pcap_handler回调函数处理。
该回调函数首先处理一下数据包头部,然后交于ip_frag_procs
链表中的每个函数来处理,这时链表中只一个处理函数gen_ip_frag_proc
gen_ip_frag_proc判断该IP包是不是分片包(IPF_NOTF, IPF_NOTF),是不是第一个到达的分片包(IPF_NEW),然后调用ip_procs链表上的全部函数,此时只有一个gen_ip_proc
gen_ip_proc根据应用层协议类型调用相应的process_tcp/udp/icmp函数
process_tcp(data, skblen)
find_stream()返回当前数据包匹配的tcp_stream nids_find_tcp_stream根据tuple4的哈希值查找tcp_stream_table
struct tcp_stream *
nids_find_tcp_stream(struct tuple4 *addr)
{
int hash_index;
struct tcp_stream *a_tcp;
hash_index = mk_hash_index(*addr);
for (a_tcp = tcp_stream_table[hash_index];
a_tcp && memcmp(&a_tcp->addr, addr, sizeof (struct tuple4));
a_tcp = a_tcp->next_node);
return a_tcp ? a_tcp : 0;
}
从这个函数可以看出,它是使用单独链表法解决哈希冲突/碰撞,tcp_stream_table第一维存储的是直接哈希值,第二维是相同哈希值的不同tuple4的单链表,用于处理碰撞。
建立时NIDS_JUST_EST会将tcp_procs链表上的所有函数变成tcp_stream的listensers,然后在a_tcp->nids_state==NIDS_DATA时 调用tcp_procs链表上的所有函数(此时有一个我们自己挂载的函数tcp_callback)
最后处理完成数据,调用notify函数通知tcp_procs链表上的函数来处理,函数调用路线如下:
process_tcp
tcp_queue
add_from_skb
notify
ride_lurkers
tcp_callback
src/tcp.c中的函数add_from_skb()中有三处
rcv->offset = rcv->count; /* clear the buffer */
我看了n遍还是很奇怪,为什么这一句能够清除缓冲区?
终于在看第n+1遍时,终于发现玄机就在add2buf函数中!
static void
add2buf(struct half_stream * rcv, char *data, int datalen)
{
int toalloc;
if (datalen + rcv->count - rcv->offset > rcv->bufsize) {
if (!rcv->data) {
if (datalen < 2048)
toalloc = 4096;
else
toalloc = datalen * 2;
rcv->data = malloc(toalloc);
rcv->bufsize = toalloc;
}
else {
if (datalen < rcv->bufsize)
toalloc = 2 * rcv->bufsize;
else
toalloc = rcv->bufsize + 2*datalen;
rcv->data = realloc(rcv->data, toalloc);
rcv->bufsize = toalloc;
}
if (!rcv->data)
nids_params.no_mem("add2buf");
}
memcpy(rcv->data + rcv->count - rcv->offset, data, datalen);
rcv->count_new = datalen;
rcv->count += datalen;
}
虽然前面写了申请内存,或者扩展内存,容易误导我们,但是注意
memcpy(rcv->data + rcv->count - rcv->offset, data, datalen);
如果rev->count == recv->offset,那么这条语句就造价于
memcpy(rcv->data, data, datalen);
也就是覆盖了同一会话中前面到达的数据!所以在同一TCP会话后面就无法找到前续数据,好像nids_discard保留一些TCP包的数据,但是也有数量限制, 最安全的办法就是在自定义的tcp_callback函数中存储会话数据,然后再通过tuple4, offset, count, count_new加以组合.