变量
nids_tcp_timeouts
双向链表nids_tcp_timeouts按结点的timeout从小到大排列,
tcp_latest, tcp_oldest
static struct tcp_stream *tcp_latest = 0, *tcp_oldest = 0;
tcp_latest指向最新的TCP连接,tcp_oldest指向最老的连接,所有TCP连接组成一个双向链表,前驱是更新的连接,后继是更旧的连接。
tcp_stream_table
static struct tcp_stream **tcp_stream_table;
使用哈希表tcp_stream_table保存所有跟踪的TCP连接,发生碰撞的连接通过双向链表保存。
streams_pool, free_streams
1 | static struct tcp_stream *streams_pool; |
streams_pool保存为流分配的内存的首地址,free_streams指向下一个可使用的流空间。
tcp_procs
1 | struct proc_node *tcp_procs; |
为处理TCP连接注册的所有回调函数。
结构体
skbuff
1 | struct skbuff |
tcp_stream
1 | struct tcp_stream |
half_stream
1 | struct half_stream |
lurker_node
1 | struct lurker_node // 潜伏结点,用于处理TCP连接 |
函数
tcp_init
1 | int tcp_init(int size) |
TCP的相关初始化,为哈希表分配tcp_stream_table_size大小的空间,默认是1040,可跟踪的最多连接是max_stream = 3 * tcp_stream_table_size / 4个,然后为连接池分配max_stream + 1个tcp_stream的空间,接下来将这些元素通过next_free连接起来,next_free表示下一个可用的结点,全局变量free_streams指向刚刚分配的空间,然后初始化哈希,下一个循环处理处于超时关闭状态的TCP连接,删除这些连接并释放内存。
add_tcp_closing_timeout
1 | static void add_tcp_closing_timeout(struct tcp_stream *a_tcp) |
将a_tcp添加到双向链表nids_tcp_timeouts中(如果不存在)并使其依然有序。
del_tcp_closing_timeout
1 | static void del_tcp_closing_timeout(struct tcp_stream *a_tcp) |
将a_tcp对应的结点从nids_tcp_timeouts链表中删除并释放内存。
nids_free_tcp_stream
1 | void nids_free_tcp_stream(struct tcp_stream *a_tcp) |
将a_tcp从哈希表中删除,释放占用内存,调整前驱后继,并使可用的流空间指向该地址。
tcp_check_timeouts
1 | void tcp_check_timeouts(struct timeval *now) |
将所有超时的连接删除。
get_ts
1 | static int get_ts(struct tcphdr *this_tcphdr, unsigned int *ts) |
遍历TCP头部的选项,获取时间戳。看一下TCP头部选项的格式就能理解。
get_wscale
1 | static int get_wscale(struct tcphdr *this_tcphdr, unsigned int *ws) |
获取窗口的扩大因子。
add_new_tcp
1 | static void add_new_tcp(struct tcphdr *this_tcphdr, struct ip *this_iphdr) |
添加新的TCP连接,如果连接个数超过最大值,删除最老的连接。
然后在可用的内存池free_streams里分配一个tcp_stream空间给a_tcp,并调整可用内存池的首地址。接下来初始化a_tcp,第一个报文段的序列号是初始序列号this_tcphdr->th_seq加1。如果哈希表相应位置已存在TCP连接,则将a_tcp插到链表头部。然后调整最新的TCP连接和最老的TCP连接。
add2buf
1 | static void add2buf(struct half_stream *rcv, char *data, int datalen) |
将本次收到的数据加入缓冲区,如果会造成溢出,根据缓冲区已有数据和本次收到数据大小,以2的幂方式增长缓冲区。
ride_lurkers
1 | static void ride_lurkers(struct tcp_stream *a_tcp, char mask) |
根据潜伏结点的whatto与mask调用回调函数,并调整whatto值。
notify
1 | static void notify(struct tcp_stream *a_tcp, struct half_stream *rcv) |
如果要处理紧急数据,根据数据的方向设定mask并通知给a_tcp的潜伏结点,然后调整潜伏结点,删除所有忽略正常数据及紧急数据的潜伏结点。
如果需要处理正常数据,通知相应的潜伏结点,并适当地调整缓冲区,将上次未处理的数据移至缓冲区起始处,如果nids_params.one_loop_less非零,libnids假定只有一个回调函数,于是循环直到没有未处理的新数据或者不再读取到数据,
add_from_skb
1 | static void add_from_skb(struct tcp_stream *a_tcp, struct half_stream *rcv, |
接收方从skbuff中添加数据,EXP_SEQ是接收方上次发送的ACK。
首先判断是否设置紧急标志,紧急指针在EXP_SEQ(含)之后,并且接收方之前没有看到紧急标志或者本次紧急指针在上次设置的紧急指针之后,如果条件为真,设置紧急指针的相应变量。
再判断是否看到紧急标志,且紧急指针在这次数据中,如果满足条件,设置需要处理的数据大小to_copy,紧急数据的范围是[EXP_SEQ, rcv->urg_ptr),如果大于0,再判断rcv->collect,如果需要存储数据,则将EXP_SEQ之后的to_copy字节其加入缓冲区,再通知相关连接,否则调整rcv->count并清空缓冲区,根据rcv->offset定义,它是缓冲区第一字节在TCP流中的偏移,将其设为接收方收到的总字节数,意味着缓冲区已经指向了数据流末尾,没有数据。接下来rcv->urgdata存储了一字节紧急数据,调整变量,设置正常数据大小to_copy2,其范围是(rcv->urg_ptr, this_seq + datalen)。
如果先前的判断为假,再判断本次数据是否包含EXP_SEQ之后的数据,有则加入缓冲区。
如果设置FIN,修改snd状态为FIN_SENT,如果rcv状态已经是CLOSING,则将a_tcp加入超时关闭的链表中。
tcp_queue
1 | static void tcp_queue(struct tcp_stream *a_tcp, struct tcphdr *this_tcphdr, |
如果this_seq <= EXP_SEQ,再判断是否this_seq + datalen + (this_tcphdr->th_flags & TH_FIN) > EXP_SEQ,也就是判断本次数据中是否有未确认的数据,如果有,设置时间戳并从skbuff中添加数据。接下来处理之前未被确认的数据(如果有)。
- 如果TCP队列中有数据,遍历链表,将
EXP_SEQ在[pakiet->seq, pakiet->seq + pakiet->len + pakiet->fin]中的数据加入,如果EXP_SEQ <= pakiet->seq,以后再处理这些报文段,如果pakiet->seq + pakiet->len + pakiet->fin <= EXP_SEQ,该报文段已经处理过。然后调整未处理报文段的链表并释放所占用的空间。
如果这次数据均已被确认,直接返回。
现在处理报文段序列号在ACK之后的情况,等待确认,为报文段分配相应空间,调整rcv->rmem_alloc,如果设置FIN,调整发送方状态为TCP_CLOSING,如果接收方状态是FIN_SENT或者FIN_CONFIRMED,将该连接加入超时关闭的队列。剩下的工作是将该报文段插入缓存的TCP队列中,根据序列号从小到大排序。
handle_ack
1 | static void handle_ack(struct half_stream *snd, u_int acknum) |
如果确认了更新的数据,调整ACK。
find_stream
1 | struct tcp_stream *find_stream(struct tcphdr *this_tcphdr, struct ip *this_iphdr, |
根据TCP首部和IP首部,调用nids_find_tcp_stream在哈希表中寻找连接,this_addr是与this_tcphdr同方向的四元组,reversed则是反向,先寻找同向的连接,没找到则寻找反向连接,仍未找到则返回0。
nids_find_tcp_stream
1 | struct tcp_stream *nids_find_tcp_stream(struct tuple4 *addr) |
计算哈希值,在对应位置的链表中顺序搜索addr,没有则返回0。
tcp_exit
1 | void tcp_exit(void) |
遍历哈希表,将TCP连接的nids状态设为nids_state并调用所有的潜伏结点进行处理,然后释放该连接。释放哈希表,释放为流分配的空间,调整相关变量。
process_tcp
1 | void process_tcp(u_char *data, int skblen) |
处理TCP连接,data参数指向IP包的起始处,TCP包的偏移在this_iphdr->ip_hl * 4。检测IP包的长度,检测TCP数据的大小,检测IP全0的情况,如果没有设置ACK,调用detect_scan检测扫描。然后如果有必要,进行校验和计算。
如果没有找到该连接,如果是发起连接建立请求的报文段,那么添加新的TCP连接,否则返回。然后根据from_client决定报文段方向,client->server或者server->client。
如果设置了SYN,这是三次握手的第二个报文段,因为如果是第一个报文段,那么在之前已经处理(通过add_new_tcp添加新的TCP连接)。此时client应该处于SYN_SENT,server处于CLOSED,并且设置ACK,如果不满足任一条件那么连接建立失败。第二个报文段需要对第一个报文段进行确认,如果ack与ISN(c)+1不同,连接建立失败。一切正常的话,将server状态转到SYN_RECV,设定第一字节序列号与确认号及其他选项,如果client与server中有一端不支持时间戳或者窗口扩大选项,则关闭对应选项。
如果数据长度不为0或者序列号不等于确认号,并且报文段序列号在接收窗口之外或者整个报文段数据均已确认,则直接返回。
如果设置了RST标志,调用所有的潜伏结点最后一次处理该TCP连接,然后释放掉该连接并返回。
接下来PAWS(Protection Against Wrapping Sequence),处理序列号回绕的情况,根据时间戳(如果有),如果报文段时间戳在发送方当前时间之前,那么这是之前迷失在网络中的报文段到达了发送方,这种情况下直接丢弃。
如果设置了ACK,判断是否为三次握手的第三个报文段,再判断确认号是否正确,如果正确,那么TCP连接就此建立,修改nids状态为NIDS_JUST_EST,调用回调函数,这里用未初始化的局部data隐藏了形参data,如果该回调函数处理数据,修改whatto,如果设置了nids_params.one_loop_less,那么只允许一个回调函数处理TCP连接,再根据whatto建立潜伏结点,如果该连接没有潜伏结点来处理,释放为该连接分配的资源。最后修改nids状态为NIDS_DATA。
再次判断是否设置ACK,并调整确认号,如果接收方状态为FIN_SENT,那么这已经是四次挥手第二个报文段,将接收方状态修改为FIN_CONFIRMED,接下来判断是否完成四次挥手,满足条件就释放该连接。
如果有任何数据或者是FIN报文段,将其加入TCP队列等待处理。
接下来设置发送方的窗口,如果接收方缓存的数据过多,将其丢弃。如果该TCP连接不再被监听,则将其释放。
nids_discard
1 | void nids_discard(struct tcp_stream *a_tcp, int num) |
如果满足条件,修改已读取的数据为num。
process_icmp
1 | void process_icmp(u_char *data) |
处理目的不可达导致的ICMP报文,为该TCP连接调用潜伏结点并释放。
github其他关于libnids的注释
见zhyq/Libnids,本文部分参考其中tcp.c的注释。。