2.6.17中ip_nat_info结构的变化

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

1. 前言
struct ip_nat_info结构是连接结构struct ip_conntrack在NAT功能打开的情况下的直接组成部分,非指针,在2.4和2.6.17中差异变化较大,本文分析其变化后的使用情况。
以下代码版本2.4的是2.4.26,2.6的是2.6.17.11。
2. 结构变化
/* include/linux/netfilter_ipv4/ip_nat.h */
2.4.26:
/* The structure embedded in the conntrack structure. */
struct ip_nat_info
{
    /* Set to zero when conntrack created: bitmask of maniptypes */
    int initialized;
   
    unsigned int num_manips;
   
    /* Manipulations to be done on this conntrack. */
    struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];
   
    /* The mapping type which created us (NULL for null mapping). */
    const struct ip_nat_mapping_type *mtype;
   
    struct ip_nat_hash bysource, byipsproto;
    /* Helper (NULL if none). */
    struct ip_nat_helper *helper;
    struct ip_nat_seq seq[IP_CT_DIR_MAX];
};
 
2.6.17.11:
struct ip_nat_info
{
 struct list_head bysource;
 struct ip_nat_seq seq[IP_CT_DIR_MAX];
};
两个结构差异巨大,但明显新结构大小比以前的小了很多。
3. 差异分析
3.1 initialized
在新结构中已经没有了initalized参数,该参数原先是用来检测数据包是否进行过某类NAT操作了,包括(SNAT、DNAT),新版中没有了此参数,但提供了以下函数来完成同样的功能,也就是实际上这些信息已经包含在连接结构的status参数中:
/* include/linux/netfilter_ipv4/ip_conntrack.h */
static inline int ip_nat_initialized(struct ip_conntrack *conntrack,
         enum ip_nat_manip_type manip)
{
 if (manip == IP_NAT_MANIP_SRC)
  return test_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);
 return test_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
}

3.2 bysource
bysource既是ip_nat_info结构中的参数,它其实只是用于挂接链表;同名的还有一个静态的全局变量,是个动态分配HASH数组指针,数组中每个元素表示一个连接链表,先看看这个参数记录的是什么样的连接:
分配:
/* net/ipv/netfilter/ip_nat_core.c */
static int __init ip_nat_init(void)
{
......
 bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size);
 if (!bysource)
  return -ENOMEM;
......
bysource这个数组大小和连接HASH数组大小是一样的

元素增加:
unsigned int
ip_nat_setup_info(struct ip_conntrack *conntrack,
    const struct ip_nat_range *range,
    unsigned int hooknum)
{
......
 int have_to_hash = !(conntrack->status & IPS_NAT_DONE_MASK);
......
 if (have_to_hash) {
  unsigned int srchash
   = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
          .tuple);
  write_lock_bh(&ip_nat_lock);
  list_add(&info->bysource, &bysource[srchash]);
  write_unlock_bh(&ip_nat_lock);
 }
......
也就是说加入此HASH数组中的连接是那些还没有进行任何NAT设置(包括SNAT和DNAT)的连接,也就是新连接,因此这个HASH数组中的元素个数和连接数应该是相同的,但不占用新的内存。
而struct ip_nat_info中的bysource参数就是链表的连接指针。

元素删除:
链表元素的删除是由以下函数完成的:
static void ip_nat_cleanup_conntrack(struct ip_conntrack *conn)
{
 if (!(conn->status & IPS_NAT_DONE_MASK))
  return;
 write_lock_bh(&ip_nat_lock);
 list_del(&conn->nat.info.bysource);
 write_unlock_bh(&ip_nat_lock);
}
这个函数实际就是ip_conntrack_destroyed:
 ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;
而ip_conntrack_destroyed函数又是在destroy_conntrack(struct nf_conntrack *nfct)时调用的,也就是在连接删除的时候才删除。
3.3 manips
2.6.17中一个最明显的变化就是struct ip_nat_info_manip结构消失了,struct ip_nat_info中也没有了相应2.4中的结构参数:struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS]。2.4中这些参数是用来描述进行NAT的操作类型以及变化前后的IP地址信息和传输层参数信息等。现在既然在2.6.17中没有了这个参数,netfilter又是如何查找NAT修改后信息的呢?这需要从函数ip_nat_setup_info()说起。

因为无论是SNAT还是DNAT规则都要调用ip_nat_setup_info()函数
unsigned int
ip_nat_setup_info(struct ip_conntrack *conntrack,
    const struct ip_nat_range *range,
    unsigned int hooknum)
{
 struct ip_conntrack_tuple curr_tuple, new_tuple;
 struct ip_nat_info *info = &conntrack->nat.info;
 int have_to_hash = !(conntrack->status & IPS_NAT_DONE_MASK);
 enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);
 IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
       || hooknum == NF_IP_POST_ROUTING
       || hooknum == NF_IP_LOCAL_IN
       || hooknum == NF_IP_LOCAL_OUT);
 BUG_ON(ip_nat_initialized(conntrack, maniptype));
 /* What we've got will look like inverse of reply. Normally
    this is what is in the conntrack, except for prior
    manipulations (future optimization: if num_manips == 0,
    orig_tp =
    conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */
// 通过连接反方向的tuple获取连接正方向的tuple值存到curr_tuple中,
// 也就是NAT转换前的tuple
 invert_tuplepr(&curr_tuple,
         &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);
// 获取地址转换后的新的tuple值到new_tuple中,已经包括了传输层上的转换
 get_unique_tuple(&new_tuple, &curr_tuple, range, conntrack, maniptype);
// 检查转换前后的tuple值是否相同,new_tuple是NAT后的新的原始方向的tuple
 if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
// 不同,进行NAT转换
  struct ip_conntrack_tuple reply;
  /* Alter conntrack table so will recognize replies. */
// 获取转换后的连接响应方向的tuple值到reply中
  invert_tuplepr(&reply, &new_tuple);
// 修改连接中的响应方向的tuple值
// 即conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = reply
  ip_conntrack_alter_reply(conntrack, &reply);
  /* Non-atomic: we own this at the moment. */
// 设置NAT操作标志
  if (maniptype == IP_NAT_MANIP_SRC)
   conntrack->status |= IPS_SRC_NAT;
  else
   conntrack->status |= IPS_DST_NAT;
 }
 /* Place in source hash if this is the first time. */
// 连接到基于起始方向源IP的HASH链表中
 if (have_to_hash) {
  unsigned int srchash
   = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
          .tuple);
  write_lock_bh(&ip_nat_lock);
  list_add(&info->bysource, &bysource[srchash]);
  write_unlock_bh(&ip_nat_lock);
 }
// 在连接的状态值中设置源或目的NAT完成标志
 /* It's done. */
 if (maniptype == IP_NAT_MANIP_DST)
  set_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
 else
  set_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);
 return NF_ACCEPT;
}
由此可见,连接中tuple值中记录了转换前后的参数,因此在进行NAT修改的函数ip_nat_packet()函数中就直接利用tuple 中的值进行修改了,不需要再用ip_nat_info_manip结构记录转换前后的地址端口信息。优点是减少了内存消耗,也比较直观。
/* Do packet manipulations according to ip_nat_setup_info. */
unsigned int ip_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. */
// 根据当前数据的反方向tuple获取转换后的地址端口的tuple信息到target中
  invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
// 根据target中信息修改当前包中的信息
  if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
   return NF_DROP;
 }
 return NF_ACCEPT;
}

3.4 helper
在2.6.17中struct ip_nat_helper整个也被取消,因此也不会包括了。

3.5 seq
seq参数算是唯一NAT价值的保留参数,和原来一样,也是在记录连接的TCP序列号的变化情况的。

4. 结论
struct ip_nat_info的变化表示了在2.6.17.11中NAT的转换信息已经不再用专门的数据进行保存,而是完全根据连接的tuple值进行NAT。

发表于: 2006-09-11,修改于: 2006-09-11 08:48,已浏览3068次,有评论5条 推荐 投诉
	网友: 本站网友 	时间:2007-03-07 10:37:53 IP地址:218.5.3.★
	

因为我没找到2.6的代码,所以想请教一个问题:

在2.6中是通过conntrack节点来完成大部分操作的,特别是status域.在你上面提到的应该多了IPS_SRC_NAT_DONE_BIT和IPS_DST_NAT_DONE_BIT宏,但你另外一篇介绍2.4和2.6的不同的时候,并没有说这些有变化?


	网友: yfydz 	时间:2007-03-08 09:08:52 IP地址:218.247.216.★
	

以前那篇是针对2.6.8.1的,那时和2.4的还差不多,2.6.1*后才改的


	网友: 本站网友 	时间:2008-03-05 23:10:22 IP地址:121.34.72.★
	

分析不错,不过我还是对中间那些转换有疑问,为什么看到代码中老是通过取得方向tuple的反转tuple,而不是直接使用该ct的原始IPC_DIR_ORIG呢?

还有,比如这种:

invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);

为什么要这样去呢?直接取得该取得该ct的对应方向的tuple不就可以了吗?

不知道为什么要反转来反转去的,脑袋都搞晕了。。。。


	网友: 本站网友 	时间:2008-03-05 23:10:30 IP地址:121.34.72.★
	

分析不错,不过我还是对中间那些转换有疑问,为什么看到代码中老是通过取得方向tuple的反转tuple,而不是直接使用该ct的原始IPC_DIR_ORIG呢?

还有,比如这种:

invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);

为什么要这样去呢?直接取得该取得该ct的对应方向的tuple不就可以了吗?

不知道为什么要反转来反转去的,脑袋都搞晕了。。。。


	网友: yfydz 	时间:2008-03-06 20:26:59 IP地址:58.31.246.★
	

因为NAT情况下反向tuple的IP和正向的不一定相同


你可能感兴趣的:(数据结构,linux)