#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>

#define NETLINK_VFW 18
#define VFW_GROUP 0
#define MSG_SIZE NLMSG_SPACE(1024)

static struct sock *nl_sk = NULL;

static void nltest_rcv(struct sock *sk, int len)
{
	struct sk_buff *nl_skb;
	struct nlmsghdr *nl_hdr;
	int pid;
	
	while ((nl_skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
		nl_hdr = (struct nlmsghdr *)nl_skb->data;
		pid = nl_hdr->nlmsg_pid;
		printk(KERN_ALERT "nltest: message from user (pid = %d) = %s\n", pid, (char *)NLMSG_DATA(nl_hdr));
		nl_skb = alloc_skb(MSG_SIZE, in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
		skb_put(nl_skb, MSG_SIZE);
		nl_hdr = (struct nlmsghdr *)nl_skb->data;
		nl_hdr->nlmsg_len = MSG_SIZE;
		nl_hdr->nlmsg_pid = 0;
		nl_hdr->nlmsg_flags = 0;
		strcpy(NLMSG_DATA(nl_hdr), "hello user abcd1234");
		NETLINK_CB(nl_skb).pid = 0;
		NETLINK_CB(nl_skb).dst_pid = pid;
		NETLINK_CB(nl_skb).dst_group = VFW_GROUP;
		netlink_unicast(nl_sk, nl_skb, pid, 0);
		kfree_skb(nl_skb);
	}
}

static int __init nltest_init(void)
{
	printk(KERN_ALERT "nltest: init\n");
	
	nl_sk = netlink_kernel_create(NETLINK_VFW, VFW_GROUP, nltest_rcv, THIS_MODULE);
	if (!nl_sk) {
		printk(KERN_ALERT "nltest: netlink_kernel_create() failed\n");
		return -1;
	}
	
	return 0;
}

static void __exit nltest_exit(void)
{
	printk(KERN_ALERT "nltest: exit\n");
	
	sock_release(nl_sk->sk_socket);
	
	return;
}

module_init(nltest_init);
module_exit(nltest_exit);

MODULE_DESCRIPTION("Netlink Test");
MODULE_AUTHOR("Nguyen Minh Nhat <ngmnhat@gmail.com>");
MODULE_LICENSE("GPL");
