#include #include #include #include #include #include #include #include #include #include #include #define MAX_AVM_NET_TRACE_DEVICES 10 static char *ntd_to_minor_map[] = { "WLAN", "DSL", NULL }; struct avm_net_trace avm_net_trace[MAX_AVM_NET_TRACE_DEVICES]; static unsigned int ntd_to_minor (struct avm_net_trace_device* ntd) { int i = 0; while (ntd_to_minor_map[i] != NULL) { if (!memcmp (ntd_to_minor_map[i], ntd->name, strnlen (ntd_to_minor_map[i], IFNAMSIZ))) return i; i++; } return ((unsigned int) 0) - 1; } int __avm_net_trace_func ( struct avm_net_trace *ant, struct sk_buff *skb, int skb_property, int direction) { if ((direction != AVM_NET_TRACE_DIRECTION_OUTGOING) && (direction != AVM_NET_TRACE_DIRECTION_HOST)) return -1; switch (skb_property) { case AVM_NET_TRACE_USE_SKB: break; case AVM_NET_TRACE_CLONE_SKB: skb = skb_clone (skb, GFP_ATOMIC); break; case AVM_NET_TRACE_COPY_SKB: skb = skb_copy (skb, GFP_ATOMIC); break; default: return -1; } if (skb == NULL) return -1; skb->pkt_type = direction == AVM_NET_TRACE_DIRECTION_OUTGOING ? PACKET_OUTGOING : PACKET_HOST; skb->protocol = ant->ntd->pcap_protocol; skb_queue_tail (&ant->recvqueue, skb); wake_up_interruptible (&ant->recvwait); return 0; } EXPORT_SYMBOL(__avm_net_trace_func); struct pcap_hdr_mgc { u32 magic; u16 version_major; u16 version_minor; u32 thiszone; u32 sigfigs; u32 snaplen; u32 network; }; struct pcaprec_hdr { u32 ts_sec; u32 ts_usec; u32 incl_len; u32 orig_len; }; struct pcaprec_modified_hdr { struct pcaprec_hdr hdr; u32 ifindex; u16 protocol; u8 pkt_type; u8 pad; }; static struct pcap_hdr_mgc pcap_hdr = { magic: 0xa1b2cd34, version_major: 2, version_minor: 4, thiszone: 0, sigfigs: 0, snaplen: 2048, network: 0 }; /* process context */ static ssize_t avm_net_device_read ( struct file *file, char *buf, size_t count, loff_t *ppos) { struct avm_net_trace *ant = (struct avm_net_trace *) file->private_data; struct sk_buff *skb; char *to = buf; if ((skb = skb_dequeue (&ant->recvqueue)) == NULL) { if (file->f_flags & O_NONBLOCK) return -EAGAIN; if (wait_event_interruptible (ant->recvwait, (skb = skb_dequeue(&ant->recvqueue)))) return -ERESTARTNOHAND; #if 0 for (;;) { interruptible_sleep_on(&ant->recvwait); if ((skb = skb_dequeue(&ant->recvqueue)) != 0) break; if (signal_pending(current)) break; } if (skb == 0) return -ERESTARTNOHAND; #endif } if (!ant->got_header) { if (count < sizeof (pcap_hdr)) return -EMSGSIZE; skb_queue_head (&ant->recvqueue, skb); if (ant->ntd->pcap_encap) pcap_hdr.network = ant->ntd->pcap_encap; else pcap_hdr.network = skb->protocol; if (copy_to_user (buf, &pcap_hdr, sizeof (pcap_hdr))) return -EFAULT; ant->got_header = 1; return sizeof (pcap_hdr); } do { struct pcaprec_modified_hdr hdr; if (count < sizeof (hdr) + skb->len) { skb_queue_head(&ant->recvqueue, skb); if (to == buf) return -EMSGSIZE; break; } #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18) { struct timeval stamp; skb_get_timestamp(skb, &stamp); hdr.hdr.ts_sec = stamp.tv_sec; hdr.hdr.ts_usec = stamp.tv_usec; } #else hdr.hdr.ts_sec = skb->stamp.tv_sec; hdr.hdr.ts_usec = skb->stamp.tv_usec; #endif hdr.hdr.incl_len = skb->len; hdr.hdr.orig_len = skb->len; hdr.ifindex = 0; hdr.protocol = skb->protocol; hdr.pkt_type = skb->pkt_type; hdr.pad = 0; if (copy_to_user(to, &hdr, sizeof (hdr))) { skb_queue_head (&ant->recvqueue, skb); return -EFAULT; } to += sizeof (hdr); count -= sizeof (hdr); if (copy_to_user (to, skb->data, skb->len)) { skb_queue_head (&ant->recvqueue, skb); return -EFAULT; } to += skb->len; count -= skb->len; kfree_skb (skb); } while ((skb = skb_dequeue (&ant->recvqueue)) != 0); return to - buf; } static int avm_net_device_open(struct inode *inode, struct file *file) { unsigned int minor = MINOR (file->f_dentry->d_inode->i_rdev); struct avm_net_trace *ant = NULL; int i; spinlock_t lock = SPIN_LOCK_UNLOCKED; unsigned long flags; if (file->private_data) return -EEXIST; spin_lock_irqsave (&lock, flags); for (i = 0; i < MAX_AVM_NET_TRACE_DEVICES; i++) { if ((avm_net_trace[i].ntd) && (avm_net_trace[i].minor == minor)) { ant = avm_net_trace + i; break; } } if (i == MAX_AVM_NET_TRACE_DEVICES) { spin_unlock_irqrestore (&lock, flags); return -ENXIO; } ant->is_open = 1; ant->got_header = 0; ant->dropped = 0; init_waitqueue_head (&ant->recvwait); skb_queue_head_init (&ant->recvqueue); file->private_data = (void *) ant; printk (KERN_DEBUG "Starting new trace on device '%s'.\n", ant->ntd->name); spin_unlock_irqrestore (&lock, flags); return 0; } static int avm_net_device_release(struct inode *inode, struct file *file) { struct avm_net_trace *ant = (struct avm_net_trace *) file->private_data; spinlock_t lock = SPIN_LOCK_UNLOCKED; unsigned long flags; spin_lock_irqsave (&lock, flags); file->private_data = NULL; ant->is_open = 0; printk (KERN_DEBUG "avm_net_trace: Stopping trace on device '%s' (%lu pakets dropped).\n", ant->ntd->name, ant->dropped); spin_unlock_irqrestore (&lock, flags); return 0; } static unsigned int avm_net_device_poll(struct file *file, poll_table * wait) { struct avm_net_trace *ant = (struct avm_net_trace *) file->private_data; unsigned int mask = 0; poll_wait (file, &ant->recvwait, wait); if (skb_queue_len (&ant->recvqueue)) mask |= POLLIN | POLLRDNORM; return mask; } static struct file_operations avm_net_trace_fops = { owner: THIS_MODULE, read: avm_net_device_read, open: avm_net_device_open, release: avm_net_device_release, poll: avm_net_device_poll, }; int register_avm_net_trace_device (struct avm_net_trace_device *ntd) { int i; spinlock_t lock = SPIN_LOCK_UNLOCKED; unsigned long flags; spin_lock_irqsave (&lock, flags); for (i = 0; i < MAX_AVM_NET_TRACE_DEVICES; i++) { if (ntd == avm_net_trace[i].ntd) return -EEXIST; } for (i = 0; i< MAX_AVM_NET_TRACE_DEVICES; i++) { if (avm_net_trace[i].ntd == NULL) { struct avm_net_trace *ant = avm_net_trace + i; ant->ntd = ntd; ant->minor = ntd_to_minor (ntd); if (ant->minor == (((unsigned int) 0) - 1)) { ant->ntd = NULL; return -1; } ant->is_open = 0; ntd->ant = ant; printk (KERN_INFO "avm_net_trace: New net trace device '%s' registered with minor %d.\n", ntd->name, ant->minor); spin_unlock_irqrestore (&lock, flags); return 0; } } spin_unlock_irqrestore (&lock, flags); return -ENFILE; } EXPORT_SYMBOL(register_avm_net_trace_device); void deregister_avm_net_trace_device (struct avm_net_trace_device *ntd) { int i; spinlock_t lock = SPIN_LOCK_UNLOCKED; unsigned long flags; spin_lock_irqsave (&lock, flags); for (i = 0; i< MAX_AVM_NET_TRACE_DEVICES; i++) { if (avm_net_trace[i].ntd == ntd) { avm_net_trace[i].ntd = NULL; ntd->ant = NULL; } } spin_unlock_irqrestore (&lock, flags); } EXPORT_SYMBOL(deregister_avm_net_trace_device); static int __init avm_net_trace_init (void) { int ret; if ((ret = register_chrdev (AVM_NET_TRACE_MAJOR, "avm_net_trace", &avm_net_trace_fops)) < 0) { printk (KERN_ERR "avm_net_trace: register_chrdev failed\n"); return -EIO; } memset (&avm_net_trace, 0, sizeof (struct avm_net_trace)); printk (KERN_INFO "avm_net_trace: Up and running.\n"); return 0; } static void __exit avm_net_trace_exit (void) { int i; spinlock_t lock = SPIN_LOCK_UNLOCKED; unsigned long flags; spin_lock_irqsave (&lock, flags); for (i = 0; i< MAX_AVM_NET_TRACE_DEVICES; i++) { struct avm_net_trace *ant = avm_net_trace + i; skb_queue_purge(&ant->recvqueue); ant->ntd = NULL; } spin_unlock_irqrestore (&lock, flags); unregister_chrdev(AVM_NET_TRACE_MAJOR, "avm_net_device"); } module_init (avm_net_trace_init); module_exit (avm_net_trace_exit);