今天再测试ipt_time的时候发现即使系统是GMT时间对 INPUT chain 还是有问题,时间还是不准,但 OUTPUT chain 和 FORWARD chain 却十分准确。

后来再次仔细的看了一次/usr/src/linux/net/ipv4/netfilter/ipt_time.c,发现ipt_time只在FORWARD和OUTPUT的时候使用kerneltime(内核时间),如果是INPUT使用的则是skb->stamp ,相关代码如下:

       /* we use the kerneltime if we are in forward or output */
       info->kerneltime = 1;
       if (hook_mask & ~((1 << NF_IP_FORWARD) | (1 << NF_IP_LOCAL_OUT)))
               /* we use the skb time */
               info->kerneltime = 0;

在规则匹配函数match()中:

       /* if kerneltime=1, we don't read the skb->timestamp but kernel time instead */
       if (info->kerneltime)
       {
               do_gettimeofday(&kerneltimeval);
               packet_local_time = kerneltimeval.tv_sec;
       }
       else
               packet_local_time = skb->stamp.tv_sec;
 

而skb->stamp是在什么时候生成的呢?找了很久,终于在/usr/src/linux/net/core/dev.c中找到答案:

/**
*      netif_rx        -       post buffer to the network code
*      @skb: buffer to post
*
*      This function receives a packet from a device driver and queues it for
*      the upper (protocol) levels to process.  It always succeeds. The buffer
*      may be dropped during processing for congestion control or by the
*      protocol layers.
*
*      return values:
*      NET_RX_SUCCESS  (no congestion)
*      NET_RX_CN_LOW   (low congestion)
*      NET_RX_CN_MOD   (moderate congestion)
*      NET_RX_CN_HIGH  (high congestion)
*      NET_RX_DROP     (packet was dropped)
*
*/

int netif_rx(struct sk_buff *skb)
{
       int this_cpu;
       struct softnet_data *queue;
       unsigned long flags;

#ifdef CONFIG_NETPOLL
       if (skb->dev->netpoll_rx && netpoll_rx(skb)) {
               kfree_skb(skb);
               return NET_RX_DROP;
       }
#endif

       if (!skb->stamp.tv_sec)
               net_timestamp(&skb->stamp);
 

而net_timestamp()的代码为:

/* When > 0 there are consumers of rx skb time stamps */
static atomic_t netstamp_needed = ATOMIC_INIT(0);

static inline void net_timestamp(struct timeval *stamp)
{
       if (atomic_read(&netstamp_needed))
               do_gettimeofday(stamp);
       else {
               stamp->tv_sec = 0;
               stamp->tv_usec = 0;
       }
}
 

看来netstamp_needed默认为0,那么skb->stamp.tv_sec就等于0,所以在ipt_time用于的INPUT chain时时间老是不对,看来在ipt_time.c上动手改造最方便了,将/usr/src/linux/net/ipv4/netfilter/ipt_time.c中的:

       /* we use the kerneltime if we are in forward or output */
       info->kerneltime = 1;
       if (hook_mask & ~((1 << NF_IP_FORWARD) | (1 << NF_IP_LOCAL_OUT)))
               /* we use the skb time */
               info->kerneltime = 0;

改成:

       /* we use the kerneltime if we are in forward or output */
       info->kerneltime = 1;

现在ipt_time将对所有的chain使用kerneltime,重新编译内核,如果现在再测试用于INPUT链的time匹配就可以正常工作了。

但还是存在一点问题,就是如果网络流量特别大的时候(包特别多),则规则匹配的将是ipt_time处理该包时的时间,而不是收到该包的时间,所以存在一定差距。如果将/usr/src/net/core/dev.c中的netstamp_needed 默认设置为1可能将是最终解决方法(这时就不要更改ipt_time.c )。但这种设置可能会对网络的性能有一点影响,因为即使你不使用ipt_time模块时,系统也会对每个收到的包加上timestamp值。

后来又仔细的看了一下net/core/dev.c中的代码,终于找到了一个比较好的解决方法。/usr/src/linux/net/core/dev.c中有个函数net_enable_timestamp()用来开启在每个包中记录timestamp的功能,而且这个函数是EXPORT_SYMBOL的,也就是说我们可以在module中调用它。

接下来的工作就简单了,只要将改/usr/src/linux/net/ipv4/netfilter/ipt_time.c中的:

static int __init init(void)
{
       printk("ipt_time loading\n");
       return ipt_register_match(&time_match);
}

static void __exit fini(void)
{
       ipt_unregister_match(&time_match);
       printk("ipt_time unloaded\n");
}

module_init(init);
module_exit(fini);

修改为:

static int __init init(void)
{
       printk("ipt_time loading\n");
       net_enable_timestamp();
       return ipt_register_match(&time_match);
}

static void __exit fini(void)
{
       ipt_unregister_match(&time_match);
       printk("ipt_time unloaded\n");
       net_disable_timestamp();
}

module_init(init);
module_exit(fini);

现在所有的问题都解决了,只有当你加载ipt_time模块时才会开启记录数据包timestamp的功能,当卸载ipt_time时又会自动关闭timestamp记录功能,现在总算完美了,最终还是在ipt_time.c上动手脚(注:这时就不要修改net/core/dev.c中的代码了,即不要将 netstamp_needed 默认设置为1)。^_^

BTW:我这儿的linux kernel源代码版本为 2.6.11.10,patch-o-matic-ng为 20050519