标签:
1.NAT的原理
NAT会修改数据包的ip层的源或者目的ip地址。在实际应用中,NAT 主要用于实现私有网络访问公共网络的功能。
1.1 SNAT
源目的地址转换,即对ip数据包的源ip地址进行转换操作,典型的应用即是网关,网关的lan侧会下挂至少两台设备,而这两台设备的ip地址都是lan侧地址,而lan侧设备又要访问公网,这就需要SNAT大展身手了,通过将lan侧发送的ip数据包的源ip地址转换成公网地址即可以访问公网了。
1.2 DNAT
目的地址转换主要是将ip数据包的目的ip地址进行修改。典型的应用就是lan侧的设备需要作为服务器使用,比如网关的lan侧有一个设备用作ftp服务器,而作为服务器就需要一个公网地址。而对于服务器来说,只有客户端先向服务器发送请求后,服务器才会应答,这样的话,客户端发送的请求会首先到达网关,而此时的目的地址为公网地址,这就需要网关将目的ip地址修改成lan侧ftp的lan侧ip地址后才能发送给lan侧ftp服务器,这就需要使用DNAT操作,即DNAT的工作场景
本节我们分析nat表的创建及其hook函数分析,同样的 需要实例化xt_table与ipt_replace这两个结构体。创建filter表时同样需要这样做。
2.NAT模块的初始化
对于NAT模块的初始化,可以分为如下几个方面:
a)NAT表的初始化
b)NAT模块的SNAT与DNAT的target的注册
c)NAT的hook回调函数的初始化
d)四层协议中NAT模块相关的协议变量初始化
下面我们就从这几个方面进行分析
2. 1nat表的注册
首先是nat表的初始化,nat表的初始化与filter表的初始化大致相同,都是
下面是nat表实例化的这两个结构体
- #define NAT_VALID_HOOKS ((1<<NF_IP_PRE_ROUTING) | (1<<NF_IP_POST_ROUTING) | (1<<NF_IP_LOCAL_OUT))
-
- static struct
- {
- struct ipt_replace repl;
- struct ipt_standard entries[3];
- struct ipt_error term;
- } nat_initial_table __initdata = {
- .repl = {
- .name = "nat",
- .valid_hooks = NAT_VALID_HOOKS,
- .num_entries = 4,
- .size = sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
- .hook_entry = {
- [NF_IP_PRE_ROUTING] = 0,
- [NF_IP_POST_ROUTING] = sizeof(struct ipt_standard),
- [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 },
- .underflow = {
- [NF_IP_PRE_ROUTING] = 0,
- [NF_IP_POST_ROUTING] = sizeof(struct ipt_standard),
- [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 },
- },
- .entries = {
- {
- .entry = {
- .target_offset = sizeof(struct ipt_entry),
- .next_offset = sizeof(struct ipt_standard),
- },
- .target = {
- .target = {
- .u = {
- .target_size = IPT_ALIGN(sizeof(struct ipt_standard_target)),
- },
- },
- .verdict = -NF_ACCEPT - 1,
- },
- },
- {
- .entry = {
- .target_offset = sizeof(struct ipt_entry),
- .next_offset = sizeof(struct ipt_standard),
- },
- .target = {
- .target = {
- .u = {
- .target_size = IPT_ALIGN(sizeof(struct ipt_standard_target)),
- },
- },
- .verdict = -NF_ACCEPT - 1,
- },
- },
- {
- .entry = {
- .target_offset = sizeof(struct ipt_entry),
- .next_offset = sizeof(struct ipt_standard),
- },
- .target = {
- .target = {
- .u = {
- .target_size = IPT_ALIGN(sizeof(struct ipt_standard_target)),
- },
- },
- .verdict = -NF_ACCEPT - 1,
- },
- },
- },
- .term = {
- .entry = {
- .target_offset = sizeof(struct ipt_entry),
- .next_offset = sizeof(struct ipt_error),
- },
- .target = {
- .target = {
- .u = {
- .user = {
- .target_size = IPT_ALIGN(sizeof(struct ipt_error_target)),
- .name = IPT_ERROR_TARGET,
- },
- },
- },
- .errorname = "ERROR",
- },
- }
- };
-
- static struct xt_table nat_table = {
- .name = "nat",
- .valid_hooks = NAT_VALID_HOOKS,
- .lock = RW_LOCK_UNLOCKED,
- .me = THIS_MODULE,
- .af = AF_INET,
- };
-
对于这两个结构体,我们已经非常熟悉了,即创建了一个名字为nat的xt_table表,且只在NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING、NF_IP_LOCAL_OUT这3个hook点进行nat操作。且为该表创建了NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING、NF_IP_LOCAL_OUT等3个链,且为每一条链创建了一条target为accept的默认规则。
然后通过调用ipt_register_table即实现了nat表的初始化。
2.2 NAT模块的target注册
对于NAT模块来说,最主要的就是实现地址转换,而iptables中是通过-j实现地址转换的,这就说明NAT模块的注册的target模块一定很重要,那我们现在就分析一下这两个模块
2.2.1 SNAT target的注册
SNAT target的定义如下:
其中target的名称为SNAT,target函数ipt_snat_target,通过这个函数我们能够实现对连接跟踪项的SNAT操作。
- static struct xt_target ipt_snat_reg = {
- .name = "SNAT",
- .target = ipt_snat_target,
- .targetsize = sizeof(struct nf_nat_multi_range_compat),
- .table = "nat",
- .hooks = 1 << NF_IP_POST_ROUTING,
- .checkentry = ipt_snat_checkentry,
- .family = AF_INET,
- };
通过模块名称,我们可以知道怎样通过iptables添加SNAT,如下:
iptables -t nat -A POSTROUTING -s 192.168.1.123 -j SNAT --to-source 25.29.1.23
而ipt_snat_target是实现SNAT的重要函数,我们看下这个函数的定义:
功能:实现SNAT功能
1.调用nf_ct_get,获取传入数据包关联的nf_conn变量
2.此处进行SNAT只是设置连接跟踪项中的reply方向的nf_conntrack_tuple变量,
因此,
对于主连接,仅设置连接跟踪项的状态为NEW的SNAT操作,因为对于状态不为NEW的连接跟踪项,其reply方向的nf_conntrack_tuple结构的变量的目的地址和 端口号已经修改过了,不需要再次修改了;
对于期望连接来说,当期望连接刚建立时,其状态仅为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY,所以
也只对这两种情况的期望连接,进行SNAT操作。
3.调用nf_nat_setup_info,根据targinfo中的地址范围与端口值修改连接跟踪项的reply方向的nf_conntrack_tuple变量中的值。
执行这个target只是修改了数据包对应的连接跟踪项的reply方向的tuple变量,并没有修改数据包的ip地址,
而修改数据包的ip地址是nat模块的hook函数中执行的(在执行了target操作后才会执行)。
(疑问:为什么是期望连接时,状态为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY都认为是起始状态呢?
状态为IP_CT_RELATED时,认为是新创建的连接跟踪项,我是能理解的,但是IP_CT_RELATED+IP_CT_IS_REPLY也
做为新创建的连接跟踪项的依据,我没有搞懂? 而且我感觉不会出现状态为IP_CT_RELATED+IP_CT_IS_REPLY的
连接跟踪项
)
- static unsigned int ipt_snat_target(struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- unsigned int hooknum,
- const struct xt_target *target,
- const void *targinfo)
- {
- struct nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- const struct nf_nat_multi_range_compat *mr = targinfo;
-
- NF_CT_ASSERT(hooknum == NF_IP_POST_ROUTING);
-
- ct = nf_ct_get(*pskb, &ctinfo);
-
- NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
- ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
- NF_CT_ASSERT(out);
-
- return nf_nat_setup_info(ct, &mr->range[0], hooknum);
- }
这个函数主要是通过调用nf_nat_setup_info实现SNAT操作,而对于该函数的分析,我准备放到下一节里分析。
最后通过调用函数xt_register_target,将该target添加到xt[AF_INET].target链表中
2.2.2 DNAT target的注册
- static struct xt_target ipt_dnat_reg = {
- .name = "DNAT",
- .target = ipt_dnat_target,
- .targetsize = sizeof(struct nf_nat_multi_range_compat),
- .table = "nat",
- .hooks = (1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_OUT),
- .checkentry = ipt_dnat_checkentry,
- .family = AF_INET,
- };
该target的名称为DNAT,要使用该target,则在iptables命令中需要增加-j DNAT,下面是一个DNAT的命令:
iptables -t nat -A PREROUTING -i wan0 -j DNAT --to-destination 192.168.1.183
该target的处理函数为ipt_dnat_target,该函数的定义如下,与SNAT的处理函数的定义类似,最终都是通过调用函数nf_nat_setup_info实现DNAT转换。
- static unsigned int ipt_dnat_target(struct sk_buff **pskb,
- const struct net_device *in,
- const struct net_device *out,
- unsigned int hooknum,
- const struct xt_target *target,
- const void *targinfo)
- {
- struct nf_conn *ct;
- enum ip_conntrack_info ctinfo;
- const struct nf_nat_multi_range_compat *mr = targinfo;
-
- NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING ||
- hooknum == NF_IP_LOCAL_OUT);
-
- ct = nf_ct_get(*pskb, &ctinfo);
-
- NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
-
- if (hooknum == NF_IP_LOCAL_OUT &&
- mr->range[0].flags & IP_NAT_RANGE_MAP_IPS)
- warn_if_extra_mangle((*pskb)->nh.iph->daddr,
- mr->range[0].min_ip);
-
- return nf_nat_setup_info(ct, &mr->range[0], hooknum);
- }
在初始化函数里通过调用函数xt_register_target,将该target添加到xt[AF_INET].target链表中
2.3 NAT模块的hook函数的注册
Nat表主要对NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING、NF_IP_LOCAL_OUT、NF_IP_LOCAL_IN这4条hook点感兴趣, 那nat的hook函数同样也是注册在这4个hook点上的。下面分析下nat表的hook函数注册情况。
通过ip_nat_in_ops的定义,我发现其在NF_IP_LOCAL_IN这个hook点上也定义了nf_hook_ops,但是我们在nat表的valid_hooks中并没有发现NF_IP_LOCAL_IN呢。
我们知道nat表上是没有NF_IP_LOCAL_IN链的,即在NF_IP_LOCAL_IN点根本不存在任何规则,那即使在NF_IP_LOCAL_IN hook点注册了hook函数,也不会匹配任何规则,那除非其hook函数并不是遍历nat表,而是进行其他的操作。
/* We must be after connection tracking and before packet filtering. */
/*注册在NF_IP_PRE_ROUTING链上,实现DNAT转换的功能(或者对于SNAT时,对reply的数据包实现De-SNAT功能)*/
- static struct nf_hook_ops nf_nat_ops[] = {
- {
- .hook = nf_nat_in,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_PRE_ROUTING,
- .priority = NF_IP_PRI_NAT_DST,
- },
- {
- .hook = nf_nat_out,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_POST_ROUTING,
- .priority = NF_IP_PRI_NAT_SRC,
- },
- {
- .hook = nf_nat_adjust,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_POST_ROUTING,
- .priority = NF_IP_PRI_NAT_SEQ_ADJUST,
- },
- {
- .hook = nf_nat_local_fn,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_LOCAL_OUT,
- .priority = NF_IP_PRI_NAT_DST,
- },
- {
- .hook = nf_nat_fn,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_LOCAL_IN,
- .priority = NF_IP_PRI_NAT_SRC,
- },
- {
- .hook = nf_nat_adjust,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_LOCAL_IN,
- .priority = NF_IP_PRI_NAT_SEQ_ADJUST,
- },
- };
接着通过调用nf_register_hooks,即完成nat模块的hook点的注册。
2.4 NAT模块四层协议相关的结构注册
在NAT模块里,四层协议相关的函数与变量,抽象出了结构体nf_nat_protocol,该结构体的定义如下,主要包括四层协议号、转换数据包四层协议相关的关键字的函数、根据输入tuple通过修改四层协议相关的关键字返回一个唯一的未被使用的tuple变量的函数等。
- struct nf_nat_protocol
- {
- const char *name;
-
- unsigned int protonum;
- struct module *me;
-
- int (*manip_pkt)(struct sk_buff **pskb,
- unsigned int iphdroff,
- const struct nf_conntrack_tuple *tuple,
- enum nf_nat_manip_type maniptype);
-
- int (*in_range)(const struct nf_conntrack_tuple *tuple,
- enum nf_nat_manip_type maniptype,
- const union nf_conntrack_man_proto *min,
- const union nf_conntrack_man_proto *max);
-
-
- int (*unique_tuple)(struct nf_conntrack_tuple *tuple,
- const struct nf_nat_range *range,
- enum nf_nat_manip_type maniptype,
- const struct nf_conn *ct);
- int (*range_to_nfattr)(struct sk_buff *skb,
- const struct nf_nat_range *range);
-
- int (*nfattr_to_range)(struct nfattr *tb[],
- struct nf_nat_range *range);
- };
因为不同的协议,其 nf_nat_protocol变量可能不一样,因此对不同的协议需要定义一个nf_nat_protocol变量。NAT模块用nf_nat_protos[MAX_IP_NAT_PROTO]数组存储不同协议的nf_nat_protocol变量,可以通过函数nf_nat_protocol_register将一个新的nf_nat_protocol变量添加到nf_nat_protos[]数组中。Tcp、udp、icmp的nf_nat_protocol变量的定义以及注册函数如下:
- struct nf_nat_protocol nf_nat_protocol_tcp = {
- .name = "TCP",
- .protonum = IPPROTO_TCP,
- .me = THIS_MODULE,
- .manip_pkt = tcp_manip_pkt,
- .in_range = tcp_in_range,
- .unique_tuple = tcp_unique_tuple,
- #if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
- .range_to_nfattr = nf_nat_port_range_to_nfattr,
- .nfattr_to_range = nf_nat_port_nfattr_to_range,
- #endif
- };
-
- rcu_assign_pointer(nf_nat_protos[IPPROTO_TCP], &nf_nat_protocol_tcp);
- rcu_assign_pointer(nf_nat_protos[IPPROTO_UDP], &nf_nat_protocol_udp);
- rcu_assign_pointer(nf_nat_protos[IPPROTO_ICMP], &nf_nat_protocol_icmp);
-
-
以上就是四个方面的初始化,下面讲一下这个四个方面分别在哪些初始化函数里被调用。
A)nf_nat_init
该函数主要是进行nat相关的nf_nat_protocol变量的初始化,不同四层协议相关的nf_nat_protocol变量的实现可能不一样,nf_nat_protocol变量主要是实现四层协议相关的NAT转换等操作,即上述2.4是在这个函数里初始化的
B)nf_nat_standalone_init
该函数实现如下三个功能:
1.调用函数nf_conntrack_register_cache创建nat模块相关的nf_conn_help 的slab缓存
2.调用nf_nat_rule_init进行nat表与nat转换相关的target的初始化
3.调用nf_register_hooks进行NAT模块相关的hook函数的注册。
即2.3 是在该函数里初始化的,而2.1与2.2则是通过调用nf_nat_rule_init初始化的
C)nf_nat_rule_init
该函数主要实现如下几个功能:
1.注册nat表
2.调用xt_register_target注册SNAT的target,根据该target变量里的target函数,能够 实现SNAT
3.调用xt_register_target注册DNAT的target,根据该target变量里的target函数,能够 实现DNAT功能
即2.1 2.2是在这个函数里进行初始化的。
以上就是本节的主要内容,本节主要是介绍了NAT模块的初始化,下一节就开始分析NAT模块相关的hook函数,以及NAT转换的实现等功能。
Linux netfilter 学习笔记 之十一 ip层netfilter的NAT模块初始化以及NAT原理
标签:
原文地址:http://www.cnblogs.com/foonsun/p/5737945.html