linux 2.6.11.10 + ipt_time 问题最终解决过程
今天再测试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