Понимание спин-блокировок в хуке netfilter

Я пишу небольшой модуль ядра для измерения времени, которое требуется сетевому пакету для выхода из узла. Этот модуль представляет собой хук в библиотеке netfilter.

Для каждого полученного пакета он вычисляет хэш, получает tstamp от skbuff и фактическую временную метку и сохраняет все эти данные в связанном списке. Чтобы передать эти данные в пользовательское пространство, я создал proc-устройство, и когда пользователь читает с устройства, я отправляю одну из записей связанного списка.

Для внесения изменений в список (чтение и запись) у меня есть спин-блокировка. Проблема в том, что иногда, когда я читаю с proc устройства во время обработки пакетов, происходит сбой системы.

Я думаю, что проблема в функции "dump_data_to_proc", точнее при попытке получить спин-блокировку. Я сделал несколько тестов, и он дает сбой (мягкая блокировка) только при работе на маршрутизаторе tplink. Когда я запускаю модуль на «обычном» ПК (одноядерном), он не падает,

#include <linux/module.h>    /* Needed by all modules */ 
#include <linux/kernel.h>   /* Needed for KERN_INFO */ 
#include <linux/init.h>   /* Needed for the macros */ 
#include <linux/skbuff.h> 
#include <linux/netfilter.h> 
#include <linux/netfilter_ipv4.h> 
#include <linux/ip.h> 
#include <linux/spinlock.h> 

#include <net/ipv6.h>

#include <linux/proc_fs.h>  /* Necessary because of proc fs */
#include <asm/uaccess.h>    /* for copy_from_user */

#include "kmodule_measure_process_time.h" 
#include "hash.c" 

//DEBUG >=5 is very slow in the tplink
#define DEBUG 2 
#define PROCFS_MAX_SIZE     64
#define PROCFS_NAME         "measures"
#define MAXIMUM_SAMPLES     10000


static struct nf_hook_ops nfho;
unsigned int total_packets_processed= 0;
unsigned int total_packets_discarded=0;
int temp_counter=0;

struct values_list *HEAD;

spinlock_t list_lock  ;


static int hello_proc(struct seq_file *m, void *v) {
  seq_printf(m, " stats Mod initialized.\n");
  return 0;
}

static int proc_open(struct inode *inode, struct  file *file) {
  return single_open(file, hello_proc, NULL);
}



ssize_t dump_data_to_proc(struct file *filp, char  *buffer, size_t length, loff_t *offset){

  int bytesRead = 0;
  struct values_list *temp=NULL;
  int bytesError=0;
  char buff[PROCFS_MAX_SIZE];

  spin_lock(&list_lock);
  temp=HEAD;
  if(temp!=NULL){
    HEAD = temp->next;
}
    spin_unlock(&list_lock);


if(temp!=NULL){
    bytesRead = snprintf(buff, PROCFS_MAX_SIZE ,"%u|%llu|%llu\n", temp->hash,temp->arrival_timestap, temp->exit_timestap);
    length = length - bytesRead+1;
    kfree(temp);
    temp_counter--;
}

bytesError= copy_to_user(buffer, buff, bytesRead);

if(bytesError!=0){
#if DEBUG >0
  printk(KERN_INFO "Error: failed to copy to user");
#endif
}
return bytesRead;
}


static const struct file_operations proc_fops = {
  .owner = THIS_MODULE,
  .open = proc_open,
  .read = dump_data_to_proc,
  .llseek = seq_lseek,
  .release = single_release,
};


static unsigned int hook_func(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
{   

    uint32_t hash=0;
    ktime_t now_timeval;
    struct timespec now;
    u64 timestamp_arrival_time=0;
    u64 timestamp_now=0;
    struct ipv6hdr * ipheader;
    struct values_list *node;
    int number_of_samples=0;

    spin_lock(&list_lock);   
    number_of_samples=temp_counter;        
    spin_unlock(&list_lock);           

    if(number_of_samples > MAXIMUM_SAMPLES){
        #if DEBUG > 5
        printk(KERN_INFO "Discarded one sample because the list is full.\n");
        #endif
        total_packets_discarded++; // probably this should be inside a spinlock
        return NF_ACCEPT;
    }

    //calculate arrival time and actual time in ns
    timestamp_arrival_time =  ktime_to_ns(skb->tstamp);
    getnstimeofday(&now);
    now_timeval = timespec_to_ktime(now);
    timestamp_now =  ktime_to_ns(now_timeval);

    //get Ipv6 addresses
    ipheader = (struct ipv6hdr *)skb_network_header(skb);

    hash=simple_hash((char *)&ipheader->saddr,sizeof(struct in6_addr)*2,hash);
    total_packets_processed++;


    node = (struct values_list *) kmalloc(sizeof(struct values_list),GFP_ATOMIC);
    if(!node){
        #if DEBUG >0
        printk(KERN_INFO "Error cannot malloc\n");
        #endif
        return NF_ACCEPT;
    }

    node->hash=hash;
    node->arrival_timestap=timestamp_arrival_time;
    node->exit_timestap=timestamp_now;

    spin_lock(&list_lock);           
    node->next=HEAD;
    HEAD=node;
    temp_counter++;
    spin_unlock(&list_lock); 

    return NF_ACCEPT;

}

static int __init init_main(void)
{
    nfho.hook = hook_func;
    nfho.hooknum = NF_INET_POST_ROUTING;
    nfho.pf = PF_INET6;
    nfho.priority = NF_IP_PRI_FIRST;
    nf_register_hook(&nfho);
#if DEBUG >0
    printk(KERN_INFO " kernel module: Successfully inserted protocol module into kernel.\n");
#endif 

    proc_create(PROCFS_NAME, 0, NULL, &proc_fops);

    spin_lock_init(&list_lock);

    //Some distros/devices disable timestamping of packets
    net_enable_timestamp(); 

    return 0;

}


static void __exit cleanup_main(void)
{

   struct values_list *temp;

    nf_unregister_hook(&nfho);
#if DEBUG >0
    printk(KERN_INFO " kernel module: Successfully unloaded protocol module.\n");
    printk(KERN_INFO "Number of packets processed:%d\n",total_packets_processed);
    printk(KERN_INFO "Number of packets discarded:%d\n",total_packets_discarded);
#endif

    remove_proc_entry(PROCFS_NAME, NULL);

    while(HEAD!=NULL){
        temp=HEAD;
        HEAD= HEAD->next;
        kfree(temp);
    }


}


module_init(init_main);
module_exit(cleanup_main);
/* *    Declaring code as GPL. */ 
MODULE_LICENSE("GPLv3");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

person Hugo Alves    schedule 17.07.2014    source источник
comment
В dump_data_to_proc() snprintf() возвращает количество символов, необходимое для записи полной строки, не считая нулевой завершающий символ. Вы не проверяете, что возвращаемое значение равно ‹ PROCFS_MAX_SIZE... предположительно потому, что вы уверены, что буфер достаточно велик, но если он будет слишком коротким, то copy_to_user() превысит конец buff. Я не вижу, чтобы length использовался для чего-либо... но я с подозрением отношусь к length = length - bytesRead+1;. Я заметил, что hook_func() добавляет элементы в начало списка HEAD, которые обрабатываются в порядке поступления.   -  person    schedule 18.07.2014
comment
Спасибо. Я добавил проверку, чтобы проверить, достаточно ли велик буфер copy_to_user.   -  person Hugo Alves    schedule 18.07.2014


Ответы (1)


В вашем коде есть 2 проблемы:

  1. Используйте макрос ядра Linux для своего кода. http://makelinux.com/ldd3/chp-11-sect-5 . Просто добавьте struct list_head в качестве элемента к вашему struct values_list и используйте list_entry, list_add и другие

  2. Обработчики Netfilter запускаются в контексте softirq, поэтому вы должны использовать spin_lock_irqsave и spin_unlock_irqrestore. Это наиболее вероятная причина, по которой ваша система дает сбой из-за softlockup. Внимательно прочитайте http://makelinux.com/ldd3/chp-5-sect-5

person Alexander Dzyoba    schedule 18.07.2014
comment
Спасибо. Задача решена! 1. Я не знал, что в ядре есть реализация универсального списка. Это довольно полезно 2. Теперь я использую spin_lock_irqsave, и все работает нормально. Спасибо за вашу помощь - person Hugo Alves; 18.07.2014