个值得关注的是find_appropriate_src函数,它主要是在一个以bysource为首的链表中进行遍历查找,这是做什么?我们初始化NAT的时候,对bysource进行过处始化,所以,到目前为止,这个链表还是空的,我们暂时跳过对这个函数的讨论,函数接下来执行:
- /*将curr_tuple赋值给的new_tuple*/
- *tuple = *orig_tuple;
- find_best_ips_proto(tuple, range, conntrack, maniptype);
复制代码range中包含了规则中转换后地址的信息,find_best_ips_proto函数用转换后地址修改new_tuple:
- static void
- find_best_ips_proto(struct ip_conntrack_tuple *tuple,
- const struct ip_nat_range *range,
- const struct ip_conntrack *conntrack,
- enum ip_nat_manip_type maniptype)
- {
- u_int32_t *var_ipp;
- /* Host order */
- u_int32_t minip, maxip, j;
- if (!(range->flags & IP_NAT_RANGE_MAP_IPS))
- return;
-
- if (maniptype == IP_NAT_MANIP_SRC)
- var_ipp = &tuple->src.ip;
- else
- var_ipp = &tuple->dst.ip;
- /* Fast path: only one choice. */
- if (range->min_ip == range->max_ip) {
- *var_ipp = range->min_ip;
- return;
- }
- minip = ntohl(range->min_ip);
- maxip = ntohl(range->max_ip);
- j = jhash_2words(tuple->src.ip, tuple->dst.ip, 0);
- *var_ipp = htonl(minip + j %% (maxip - minip + 1));
- }
复制代码如果是源地址转换,var_ipp指针指向tuple中的源地址:
- var_ipp = &tuple->src.ip;
复制代码然后,用range中的转换后地址替换它:
- if (range->min_ip == range->max_ip) {
- *var_ipp = range->min_ip;
- return;
- }
复制代码这样,再返回至ip_nat_setup_info函数时,我们已经得到了一个根据规则中转换后地址修改过的new_tuple,接着,一个小判断,以确定确实是被修改过,然后,用它替换连接表中数据包的replay tuple:
- if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
- struct ip_conntrack_tuple reply;
- /* 根据new tuple取反,得到转换后地址的reply tuple */
- invert_tuplepr(&reply, &new_tuple);
- /*修改连接跟踪表中的reply tuple*/
- ip_conntrack_alter_reply(conntrack, &reply);
- /* 设置状态标志位 */
- if (maniptype == IP_NAT_MANIP_SRC)
- conntrack->status |= IPS_SRC_NAT;
- else
- conntrack->status |= IPS_DST_NAT;
- }
复制代码我们忽略了太多的细节,如指定协议后的端口转换、转换后地址为地址池的情况等等,但是,这并不影响我们了解整个NAT的全过程,还是继续我们的例子:一个192.186.0.1至100.100.100.100的WEB访问,被修改为100.100.100.1的连接跟随表被修改的流程如下图所示:
在修改了数据包对应的连接跟踪表后,函数将返回至ip_nat_fn中,在函数的后一句,调用了nat_packet修改数据包的来源地址:
- /* Do packet manipulations according to ip_nat_setup_info. */
- unsigned int nat_packet(struct ip_conntrack *ct,
- enum ip_conntrack_info ctinfo,
- unsigned int hooknum,
- struct sk_buff **pskb)
- {
- enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
- unsigned long statusbit;
- enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);
- /*根据转换类型,设置状态标位位*/
- if (mtype == IP_NAT_MANIP_SRC)
- statusbit = IPS_SRC_NAT;
- else
- statusbit = IPS_DST_NAT;
- /* 应答的情况,暂时不考虑它 */
- if (dir == IP_CT_DIR_REPLY)
- statusbit ^= IPS_NAT_MASK;
- /* Non-atomic: these bits don't change. */
- if (ct->status & statusbit) {
- struct ip_conntrack_tuple target;
- /* 取得修改后replay tuple,并取反,对于源地址转换,就应该中target中的源地址替换IP包中的源地址 */
- invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
- /*根据replay tuple中的地址信息,修改数据包*/
- if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
- return NF_DROP;
- }
- return NF_ACCEPT;
- }
复制代码数据包的修改工作,是在mainip_pkt中完成的:
- static int
- manip_pkt(u_int16_t proto,
- struct sk_buff **pskb,
- unsigned int iphdroff,
- const struct ip_conntrack_tuple *target,
- enum ip_nat_manip_type maniptype)
- {
- struct iphdr *iph;
-
- /*修改数据包,置相应标志位*/
- (*pskb)->nfcache |= NFC_ALTERED;
- if (!skb_ip_make_writable(pskb, iphdroff + sizeof(*iph)))
- return 0;
- /*取得IP首部*/
- iph = (void *)(*pskb)->data + iphdroff;
- /* 高层协议部份暂时不考虑 */
- if (!ip_nat_find_proto(proto)->manip_pkt(pskb, iphdroff,
- target, maniptype))
- return 0;
- iph = (void *)(*pskb)->data + iphdroff;
-
- /*源地址转换,修改IP包中的来源地址*/
- if (maniptype == IP_NAT_MANIP_SRC) {
- iph->check = ip_nat_cheat_check(~iph->saddr, target->src.ip,
- iph->check);
- iph->saddr = target->src.ip;
- } else {
- iph->check = ip_nat_cheat_check(~iph->daddr, target->dst.ip,
- iph->check);
- iph->daddr = target->dst.ip;
- }
- return 1;
- }
复制代码这样,转换后的数据就被发送出去了。但是,这只是出去的数据包,绝大多数情况下,回来的数据包将再次进入地址转换模块。
应答的包
对于应答的数据包,同样会进入ip_nat_fn函数,当判断了该数据包的连接状态,除了ICMP协议的应答需要特需处理外,数据包同样会进入nat_packet函数:
- switch (ctinfo) {
- ……
- }
- ……
- return nat_packet(ct, ctinfo, hooknum, pskb);
复制代码函数取得对应连接跟踪表的tuple,因为连接表中有转换前的地址信息,所以这里取反,用取反的tuple中的目的地址(即原来的来源地址)修改数据包。这样,整个NAT就建立起来了:
- /* Do packet manipulations according to ip_nat_setup_info. */
- unsigned int nat_packet(struct ip_conntrack *ct,
- enum ip_conntrack_info ctinfo,
- unsigned int hooknum,
- struct sk_buff **pskb)
- {
- enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
- unsigned long statusbit;
- enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);
- if (mtype == IP_NAT_MANIP_SRC)
- statusbit = IPS_SRC_NAT;
- else
- statusbit = IPS_DST_NAT;
- /* Invert if this is reply dir. */
- if (dir == IP_CT_DIR_REPLY)
- statusbit ^= IPS_NAT_MASK;
- /* Non-atomic: these bits don't change. */
- if (ct->status & statusbit) {
- struct ip_conntrack_tuple target;
- /* We are aiming to look like inverse of other direction. */
- invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
- if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
- return NF_DROP;
- }
- return NF_ACCEPT;
- }
复制代码目的地址转换的流程和原理与源地址转换是一样的。呵呵。
当然,这只是基本,简单的流程,做为一个完整的地址转换,存在一个地址对应多条连接(在查块初始化的时候,遇到过初始化hash表,呵呵,就是拿来做这个的),另外,如九贱在连接跟踪的实现中,叙述过动态协议的相关内容,NAT也要对此做相应的处理等等。
文章来源CU社区: Netfilter 地址转换的实现