依旧在ip_conntrack_in()函数中,只不过我们这次的侧重点不同。由于该报文是某条连接的第一个数据包,ip_conntrack_find_get()函数中根据该数据包的tuple在连接跟踪表ip_conntrack_hash中肯定找不到对应的连接跟踪记录,然后重任就交给了init_conntrack()函数:
如果连接跟踪数已满,或没有足够的内存时,均会返回错误。否则,将新连接跟踪记录的引用计数置为1,设置连接跟踪记录“初始”和“应答”方向的tuple链,同时还设置了连接跟踪记录被销毁和超时的回调处理函数destroy_conntrack()和death_by_timeout()等。
至此,我们新的连接记录ip_conntrack{}就华丽丽滴诞生了。每种协议必须对其“新连接记录项”提供一个名为new()的回调函数。该函数的主要作用就是针对不同协议,什么样的报文才被称为“new”状态必须由每种协议自身去考虑和实现。具体我就不深入分析了,大家只要知道这里有这么一出戏就可以了,感兴趣的朋友可以去研究研究。当然,这需要对协议字段和意义有比较透彻清晰的了解才能完全弄明白别人为什么要那么设计。毕竟我们不是去为TCP、UDP或ICMP开发连接跟踪,开源界的信条就是“永远不要重复发明车轮”。如果你想深入研究现有的东西,目的只有一个:那就是学习别人的优点和长处,要有重点,有主次的去学习,不然会让自己很累不说,还会打击求知的积极性和动力。
闲话不都说,我们继续往下分析。如果该数据包所属的协议集提供了helper接口,那么将其挂到conntrack->helper的回调接口上。最后,将该连接跟踪项“初始”方向的tuple链添加到一条名为unconfirmed的全局链表中,该链表里存储的都是截止到目前为止还未曾收到“应答”方向数据包的连接跟踪记录。
费了老半天劲儿,状态防火墙终于出来和大家见面了:
在ip_ct_get_tuple()函数里初始化时tuple.dst.dir就被设置为了IP_CT_DIR_ORIGINAL,因为我们讨论的就是NEW状态的连接,tuple.dst.dir字段到目前为止还未被改变过,与此同时,ip_conntrack.status位图自从被创建之日起经过memset()操作后就一直为全0状态,才有最后的skb->nfctinfo=*ctinfo=IP_CT_NEW和skb->nfct = &ip_conntrack->ct_general。
继续回到ip_conntrack_in()函数里,此时调用协议所提供的回调packet()函数。在博文六中我们曾提及过,该函数承担着数据包生死存亡的使命。这里我们有必要注意一下packet()函数最后给Netfilter框架返回值的一些细节:
-1,其实就是-NF_ACCEPT,意思是:连接跟踪出错了,该数据包不是有效连接的一部分,Netfilter不要再对这类报文做跟踪了,调用前面的回调函数destroy()清除已经为其设置的连接跟踪项记录项,释放资源。最后向Netfilter框架返回ACCEPT,让该数据包继续传输。
0,就是NF_DROP,返回给Netfilter框架的也是该值,那么这数据包就挂在这里了。
1,就是NF_ACCEPT,同样,该数据包已经被正确跟踪了,通知Netfilter框架继续传输该数据包。
对于像TCP这样非常复杂的协议才用到了NF_DROP操作,像UDP、ICMP、GRE、SCTP等协议都没有到,但不排除你的项目中使用NF_DROP的情形。
在连接跟踪的出口处的ip_conntrack_confirm()函数中,如果已经为该数据包skb创建了连接跟踪记录ip_conntrack{}(即skb->nfct有值),则做如下处理:
如果该连接还没有收到回复报文----明显如此;
如果该连接没有挂掉----毫无疑问。
因为是新连接,因此在全局链表数组ip_conntrack_hash[]就没有记录该连接“初始”和“应答”方向的tuplehash链。然后,紧接着我把该连接初始方向的tuplehash链ip_conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list从unconfirmed链表上卸下来。并且,将该连接初始和应答方向的tuplehash链表根据其各自的hash之加入到ip_conntrack_hash[]里,最后启动连接跟踪老化时间定时器,修改引用计数,将ip_conntrack.status状态位图更新为IPS_CONFIRMED_BIT,并向Netfilter框架返回NF_ACCEPT。数据包离开Netfilter继续在协议栈中传递。