在前面的笔记中有看到,连接跟踪子系统框架只是提供了一种连接的抽象,并且为跟踪一个连接在数据包的传输路径上提供了一种通用的拦截方法,但是要想真正的完成连接的跟踪,还需要具体L3和L4协议的参与,之前的笔记已经详细分析了AF_INET协议族的L3协议ipv4具体是如何实现连接跟踪的,这篇笔记以udp协议为例来看看L4协议是如何和连接跟踪子系统框架配合的。
之所以选udp,是因为它足够简单,便于理解。tcp、udp和icmp作为标准的L4协议,是默认支持连接跟踪的,没有任何开关控制。udp协议相关的代码文件如下:
代码路径 | 说明 |
---|---|
net/netfilter/nf_conntrack_udp.c | 连接跟踪子系统中udp协议的实现 |
1. 初始化
作为内置支持的L4协议,udp协议的注册是在AF_INET协议族的连接跟踪初始化过程中完成的,具体如下:
static int __init nf_conntrack_l3proto_ipv4_init(void)
{
...
//ipv4向连接跟踪子系统注册自己支持的L4协议,分别有tcp、udp、icmp
ret = nf_conntrack_l4proto_register(&nf_conntrack_l4proto_udp4);
if (ret < 0) {
printk("nf_conntrack_ipv4: can't register udp.\n");
goto cleanup_tcp;
}
...
}
AF_INET协议族中,udp协议对应的连接跟踪L4协议定义如下:
struct nf_conntrack_l4proto nf_conntrack_l4proto_udp4 __read_mostly =
{
.l3proto = PF_INET,
.l4proto = IPPROTO_UDP,
.name = "udp",
.pkt_to_tuple = udp_pkt_to_tuple,
.invert_tuple = udp_invert_tuple,
.print_tuple = udp_print_tuple,
.packet = udp_packet,
.new = udp_new,
.error = udp_error,
#if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
.tuple_to_nlattr = nf_ct_port_tuple_to_nlattr,
.nlattr_to_tuple = nf_ct_port_nlattr_to_tuple,
.nla_policy = nf_ct_port_nla_policy,
#endif
#ifdef CONFIG_SYSCTL
.ctl_table_users = &udp_sysctl_table_users,
.ctl_table_header = &udp_sysctl_header,
.ctl_table = udp_sysctl_table,
#ifdef CONFIG_NF_CONNTRACK_PROC_COMPAT
.ctl_compat_table = udp_compat_sysctl_table,
#endif
#endif
};
核心就是一组回调函数,这些回调部分是可选的,部分是必须要提供的。
2. 回调函数
下面来看一些核心回调函数的实现。
2.1 udp_pkt_to_tuple()
在连接跟踪的入口处,会回调该函数确定数据包tuple结构中的L4地址部分,如下,对于udp来讲,源端口和目的端口作为其L4地址再合适不过了。
static int udp_pkt_to_tuple(const struct sk_buff *skb,
unsigned int dataoff,
struct nf_conntrack_tuple *tuple)
{
const struct udphdr *hp;
struct udphdr _hdr;
/* Actually only need first 8 bytes. */
hp = skb_header_pointer(skb, dataoff, sizeof(_hdr), &_hdr);
if (hp == NULL)
return 0;
tuple->src.u.udp.port = hp->source;
tuple->dst.u.udp.port = hp->dest;
return 1;
}
对应的,tuple地址反转回调为udp_invert_tuple(),很简单。
2.2 udp_packet()
packet()回调,当连接跟踪处理完毕后,会调用该回调,该回调的返回值将作为连接跟踪子系统返回给Netfilter框架的判决结果。个人理解:通常,应该返回NF_ACCEPT,因为该回调的本意应该是连接跟踪框架给L4协议一个改变连接跟踪信息块中内容的机会,而并非过滤数据包。
/* Returns verdict for packet, and may modify conntracktype */
static int udp_packet(struct nf_conn *ct, const struct sk_buff *skb, unsigned int dataoff,
enum ip_conntrack_info ctinfo, int pf, unsigned int hooknum)
{
/* If we've seen traffic both ways, this is some kind of UDP
stream. Extend timeout. */
if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
//如果该连接上双向数据包都已经出现,那么用
//nf_ct_upd_timeout_stream更新超时时间
nf_ct_refresh_acct(ct, ctinfo, skb, nf_ct_udp_timeout_stream);
/* Also, more likely to be important, and not a probe */
if (!test_and_set_bit(IPS_ASSURED_BIT, &ct->status))
nf_conntrack_event_cache(IPCT_STATUS, skb);
} else
//依然是单向连接,用nf_ct_udp_timeout更新超时时间
nf_ct_refresh_acct(ct, ctinfo, skb, nf_ct_udp_timeout);
return NF_ACCEPT;
}
参数nf_ct_udp_timeout_stream和nf_ct_udp_timeout是两个系统控制参数,在/proc/sys/net/netfilter目录下有对应的节点,这两个内核变量的默认为如下:
static unsigned int nf_ct_udp_timeout __read_mostly = 30*HZ;
static unsigned int nf_ct_udp_timeout_stream __read_mostly = 180*HZ;
即默认情况下,连接跟踪子系统中,单向的udp连接可以保持30s静默,双向的可以保持180s。
2.3 udp_new() && udp_error()
在收到一个新的连接时,回调udp_new(),对于udp来说,该回调不会做任何事情,直接返回1。
连接跟踪子系统对每个入口处的数据包都会调用L4协议的error()回调对数据包进行校验,如果校验失败,连接跟踪子系统将不会继续处理该数据包,并且将error()回调返回值的相反数返回给Netfilter框架(这并不一定会导致丢包,具体要看L4协议的error回调返回值),udp提供的error()回调就是udp_error()。
static int udp_error(struct sk_buff *skb, unsigned int dataoff,
enum ip_conntrack_info *ctinfo, int pf, unsigned int hooknum)
{
unsigned int udplen = skb->len - dataoff;
const struct udphdr *hdr;
struct udphdr _hdr;
//获取udp报文的首部指针
hdr = skb_header_pointer(skb, dataoff, sizeof(_hdr), &_hdr);
if (hdr == NULL)
return -NF_ACCEPT;
//数据包长度太小,是错误数据包
if (ntohs(hdr->len) > udplen || ntohs(hdr->len) < sizeof(*hdr))
return -NF_ACCEPT;
//udp首部没有使用校验和,校验对于udp协议是可选的
if (!hdr->check)
return NF_ACCEPT;
/* Checksum invalid? Ignore.
* We skip checking packets on the outgoing path
* because the checksum is assumed to be correct.
* FIXME: Source route IP option packets --RR */
//检查数据包的校验和是否正确
if (nf_conntrack_checksum && hooknum == NF_INET_PRE_ROUTING &&
nf_checksum(skb, hooknum, dataoff, IPPROTO_UDP, pf)) {
return -NF_ACCEPT;
}
return NF_ACCEPT;
}
可以看出,udp的错误检查就是检查数据包长度是否正确,以及校验和是否正确。此外,对于异常情况,返回的也是-NF_ACCEPT,连接跟踪子系统再次取反,所以最终返回给Netfilter框架的还是NF_ACCEPT,所以udp的错误检查不会导致丢包。