如笔记连接跟踪子系统之AF_INET初始化所述,为了支持连接跟踪子系统,AF_INET协议族在4个HOOK点共注册了8个钩子函数,每个HOOK点有两个。这些钩子函数可分为入口(PRE_ROUTING和LOCAL_IN)和出口(LOCAL_OUT和POST_ROUTING)两大类,这篇笔记来看看这些钩子函数的实现。
相关代码文件有:
代码路径 | 说明 |
---|---|
net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c | AF_INET协议族在连接跟踪子系统中L3协议的实现 |
net/netfilter/nf_conntrack_core.c | 连接跟踪子系统框架对连接跟踪流程的核心实现 |
1. ipv4_conntrack_defrag()
连接跟踪子系统的两个入口处(PRE_ROUTING和LOCAL_IN)都注册了该钩子函数。
static unsigned int ipv4_conntrack_defrag(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
//对于loopback这样的环回设备,连接跟踪模块在LOCAL_OUT点已经处理过了该skb
if (skb->nfct)
return NF_ACCEPT;
//输入的skb是一个IP报文的分片
if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
//尝试将skb和已缓存skb进行IP片段重组
if (nf_ct_ipv4_gather_frags(skb, hooknum == NF_INET_PRE_ROUTING ?
IP_DEFRAG_CONNTRACK_IN : IP_DEFRAG_CONNTRACK_OUT)) {
//重组失败,给Netfilter框架返回NF_STOLEN,表示该skb被钩子内部消耗了,
//无需协议族进一步处理,这会导致Netfilter框架结束整个HOOK点的遍历
return NF_STOLEN;
}
}
//skb是一个完整的IP报文或者重组成功,返回NF_ACCEPT
return NF_ACCEPT;
}
可以看出,ipv4_conntrack_defrag()完成的工作就是重组IP报文,该钩子函数以较高优先级工作在连接跟踪子系统的入口处,这样可以保证连接跟踪子系统后面见到的报文都是完整的IP报文,也就是说连接跟踪子系统不处理IP分片。
2. ipv4_conntrack_in()
该钩子函数是连接跟踪子系统在PRE_ROUTING处注册的第二个钩子,它的优先级比ipv4_conntrack_defrag()低,它将完成AF_INET协议族数据包在连接跟踪子系统入口处要做的所有事情。
static unsigned int ipv4_conntrack_in(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
//调用连接跟踪子系统框架的输入处理接口
return nf_conntrack_in(PF_INET, hooknum, skb);
}
直接调用连接跟踪子系统框架的入口处理函数,该函数的具体分析见笔记连接跟踪子系统之核心实现。这里要做的事情简单来说就是对该数据包进行跟踪,使其属于某个“连接”。
3. ipv4_conntrack_local()
该钩子函数是连接跟踪子系统在LOCAL_OUT处注册的第二个钩子。
static unsigned int ipv4_conntrack_local(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
//skb长度比IP首部还小,这应该属于RAW socket,不跟踪这种数据包
if (skb->len < sizeof(struct iphdr) ||
ip_hdrlen(skb) < sizeof(struct iphdr)) {
if (net_ratelimit())
printk("ipt_hook: happy cracking.\n");
return NF_ACCEPT;
}
//同样调用连接跟踪子系统框架的入口处理函数对数据包进行跟踪
return nf_conntrack_in(PF_INET, hooknum, skb);
}
4. ipv4_conntrack_help()
该钩子函数是连接跟踪子系统在出口处注册的第一个钩子函数,两个出口LOCAL_OUT和POST_ROUTING都注册了该钩子。
static unsigned int ipv4_conntrack_help(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
const struct nf_conn_help *help;
const struct nf_conntrack_helper *helper;
//在入口处,skb应该已经找到了它的连接跟踪信息块
ct = nf_ct_get(skb, &ctinfo);
//没有ct,说明该skb没有被跟踪或者不属于任何一个连接。或者skb属于一个期望连接。
//这两种情况,不会对该skb执行help()回调
if (!ct || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY)
return NF_ACCEPT;
//获取skb的help信息,该信息在连接跟踪的入口处就已经指定了(如果有的话)
help = nfct_help(ct);
if (!help)
return NF_ACCEPT;
/* rcu_read_lock()ed by nf_hook_slow */
helper = rcu_dereference(help->helper);
if (!helper)
return NF_ACCEPT;
//执行help()回调,回调函数的返回值将作为给Netfilter框架的返回值
return helper->help(skb, skb_network_offset(skb) + ip_hdrlen(skb), ct, ctinfo);
}
从实现上可以看出,help()回调是可选的,可以有也可以没有。关于连接跟踪子系统给的helper机制,会有专门的笔记分析。
5. ipv4_confirm()
该钩子函数是连接跟踪子系统在出口处注册的第二个钩子函数,两个出口LOCAL_OUT和POST_ROUTING都注册了该钩子。
static unsigned int ipv4_confirm(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{
//直接调用连接跟踪子系统框架的出口确认函数处理
return nf_conntrack_confirm(skb);
}
nf_conntrack_confirm()的具体分析见笔记连接跟踪子系统之核心实现。简单来说,这里要做的事情就是将“新连接”的连接跟踪信息块加入到全局的连接信息块哈希表(nf_conntrack_hash)中,之所以需要这一步是因为新连接的识别是在PRE_ROUTING和LOCAL_OUT处,并且这两处的钩子函数优先级较高,此时数据包还没有过防火墙,而确认钩子函数以较低优先级位于防火墙之后,到了这里,说明数据包不会被丢弃了,此时再将其连接加入全局的连接跟踪哈希表是最合适的。