summaryrefslogtreecommitdiffstats
path: root/net/netlink
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1997-12-16 06:06:25 +0000
committerRalf Baechle <ralf@linux-mips.org>1997-12-16 06:06:25 +0000
commitaa944aa3453e47706685bc562711a9e87375941e (patch)
tree8fb37a65f205a90412917ca2b91c429263ef1790 /net/netlink
parent967c65a99059fd459b956c1588ce0ba227912c4e (diff)
Merge with Linux 2.1.72, part 2.
The new signal code with exception of the code for the rt signals. The definitions in <asm/siginfo.h> and <asm/ucontext.h> are currently just stolen from the Alpha and will need to be overhauled.
Diffstat (limited to 'net/netlink')
-rw-r--r--net/netlink/Makefile26
-rw-r--r--net/netlink/af_netlink.c1025
-rw-r--r--net/netlink/netlink_dev.c213
3 files changed, 1264 insertions, 0 deletions
diff --git a/net/netlink/Makefile b/net/netlink/Makefile
new file mode 100644
index 000000000..db134a98d
--- /dev/null
+++ b/net/netlink/Makefile
@@ -0,0 +1,26 @@
+#
+# Makefile for the netlink driver.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := netlink.o
+MOD_LIST_NAME := NET_MISC_MODULES
+
+O_OBJS :=
+OX_OBJS := af_netlink.o
+
+M_OBJS :=
+
+ifeq ($(CONFIG_NETLINK_DEV), y)
+ O_OBJS += netlink_dev.o
+endif
+
+ifeq ($(CONFIG_NETLINK_DEV), m)
+ M_OBJS += netlink_dev.o
+endif
+
+include $(TOPDIR)/Rules.make
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
new file mode 100644
index 000000000..81c53edda
--- /dev/null
+++ b/net/netlink/af_netlink.c
@@ -0,0 +1,1025 @@
+/*
+ * NETLINK Kernel-user communication protocol.
+ *
+ * Authors: Alan Cox <alan@cymru.net>
+ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/stat.h>
+#include <linux/socket.h>
+#include <linux/un.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/fs.h>
+#include <linux/malloc.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/scm.h>
+
+#define Nprintk(a...)
+
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+#define NL_EMULATE_DEV
+#endif
+
+static struct sock *nl_table[MAX_LINKS];
+static atomic_t nl_table_lock[MAX_LINKS];
+static struct wait_queue *nl_table_wait;
+
+#ifdef NL_EMULATE_DEV
+static struct socket *netlink_kernel[MAX_LINKS];
+#endif
+
+static int netlink_dump(struct sock *sk);
+static void netlink_destroy_callback(struct netlink_callback *cb);
+
+extern __inline__ void
+netlink_wait_on_table(int protocol)
+{
+ while (atomic_read(&nl_table_lock[protocol]))
+ sleep_on(&nl_table_wait);
+}
+
+extern __inline__ void
+netlink_lock_table(int protocol)
+{
+ atomic_inc(&nl_table_lock[protocol]);
+}
+
+extern __inline__ void
+netlink_unlock_table(int protocol, int wakeup)
+{
+#if 0
+ /* F...g gcc does not eat it! */
+
+ if (atomic_dec_and_test(&nl_table_lock[protocol]) && wakeup)
+ wake_up(&nl_table_wait);
+#else
+ atomic_dec(&nl_table_lock[protocol]);
+ if (atomic_read(&nl_table_lock[protocol]) && wakeup)
+ wake_up(&nl_table_wait);
+#endif
+}
+
+static __inline__ void netlink_lock(struct sock *sk)
+{
+ atomic_inc(&sk->protinfo.af_netlink.locks);
+}
+
+static __inline__ void netlink_unlock(struct sock *sk)
+{
+ atomic_dec(&sk->protinfo.af_netlink.locks);
+}
+
+static __inline__ int netlink_locked(struct sock *sk)
+{
+ return atomic_read(&sk->protinfo.af_netlink.locks);
+}
+
+static __inline__ struct sock *netlink_lookup(int protocol, pid_t pid)
+{
+ struct sock *sk;
+
+ for (sk=nl_table[protocol]; sk; sk=sk->next) {
+ if (sk->protinfo.af_netlink.pid == pid) {
+ netlink_lock(sk);
+ return sk;
+ }
+ }
+
+ return NULL;
+}
+
+extern struct proto_ops netlink_ops;
+
+static void netlink_insert(struct sock *sk)
+{
+ cli();
+ sk->next = nl_table[sk->protocol];
+ nl_table[sk->protocol] = sk;
+ sti();
+}
+
+static void netlink_remove(struct sock *sk)
+{
+ struct sock **skp;
+ for (skp = &nl_table[sk->protocol]; *skp; skp = &((*skp)->next)) {
+ if (*skp == sk) {
+ *skp = sk->next;
+ return;
+ }
+ }
+}
+
+static int netlink_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ sock->state = SS_UNCONNECTED;
+
+ if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
+ return -ESOCKTNOSUPPORT;
+
+ if (protocol<0 || protocol >= MAX_LINKS)
+ return -EPROTONOSUPPORT;
+
+ sock->ops = &netlink_ops;
+
+ sk = sk_alloc(AF_NETLINK, GFP_KERNEL);
+ if (!sk)
+ return -ENOMEM;
+
+ sock_init_data(sock,sk);
+ sk->destruct = NULL;
+
+ sk->mtu=4096;
+ sk->protocol=protocol;
+ return 0;
+}
+
+static void netlink_destroy_timer(unsigned long data)
+{
+ struct sock *sk=(struct sock *)data;
+
+ if (!netlink_locked(sk) && !atomic_read(&sk->wmem_alloc)
+ && !atomic_read(&sk->rmem_alloc)) {
+ sk_free(sk);
+ return;
+ }
+
+ sk->timer.expires=jiffies+10*HZ;
+ add_timer(&sk->timer);
+ printk(KERN_DEBUG "netlink sk destroy delayed\n");
+}
+
+static int netlink_release(struct socket *sock, struct socket *peer)
+{
+ struct sock *sk = sock->sk;
+
+ if (!sk)
+ return 0;
+
+ /* Wait on table before removing socket */
+ netlink_wait_on_table(sk->protocol);
+ netlink_remove(sk);
+
+ if (sk->protinfo.af_netlink.cb) {
+ netlink_unlock(sk);
+ sk->protinfo.af_netlink.cb->done(sk->protinfo.af_netlink.cb);
+ netlink_destroy_callback(sk->protinfo.af_netlink.cb);
+ sk->protinfo.af_netlink.cb = NULL;
+ }
+
+ /* OK. Socket is unlinked, and, therefore,
+ no new packets will arrive */
+ sk->state_change(sk);
+ sk->dead = 1;
+
+ skb_queue_purge(&sk->receive_queue);
+ skb_queue_purge(&sk->write_queue);
+
+ /* IMPORTANT! It is the major unpleasant feature of this
+ transport (and AF_UNIX datagram, when it will be repaired).
+
+ Someone could wait on our sock->wait now.
+ We cannot release socket until waiter will remove yourself
+ from wait queue. I choose the most conservetive way of solving
+ the problem.
+
+ We waked up this queue above, so that we need only to wait
+ when the readers release us.
+ */
+
+ while (netlink_locked(sk)) {
+ current->counter = 0;
+ schedule();
+ }
+
+ if (sk->socket) {
+ sk->socket = NULL;
+ sock->sk = NULL;
+ }
+
+ if (atomic_read(&sk->rmem_alloc) || atomic_read(&sk->wmem_alloc)) {
+ sk->timer.data=(unsigned long)sk;
+ sk->timer.expires=jiffies+HZ;
+ sk->timer.function=netlink_destroy_timer;
+ add_timer(&sk->timer);
+ printk(KERN_DEBUG "impossible 333\n");
+ return 0;
+ }
+
+ sk_free(sk);
+ return 0;
+}
+
+static int netlink_autobind(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct sock *osk;
+
+ netlink_wait_on_table(sk->protocol);
+
+ sk->protinfo.af_netlink.groups = 0;
+ sk->protinfo.af_netlink.pid = current->pid;
+
+retry:
+ for (osk=nl_table[sk->protocol]; osk; osk=osk->next) {
+ if (osk->protinfo.af_netlink.pid == sk->protinfo.af_netlink.pid) {
+ /* Bind collision, search negative pid values. */
+ if (sk->protinfo.af_netlink.pid > 0)
+ sk->protinfo.af_netlink.pid = -4096;
+ sk->protinfo.af_netlink.pid--;
+ goto retry;
+ }
+ }
+
+ netlink_insert(sk);
+ return 0;
+}
+
+static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sock *osk;
+ struct sockaddr_nl *nladdr=(struct sockaddr_nl *)addr;
+
+ if (nladdr->nl_family != AF_NETLINK)
+ return -EINVAL;
+
+ /* Only superuser is allowed to listen multicasts */
+ if (nladdr->nl_groups && !suser())
+ return -EPERM;
+
+ if (sk->protinfo.af_netlink.pid) {
+ if (nladdr->nl_pid != sk->protinfo.af_netlink.pid)
+ return -EINVAL;
+ sk->protinfo.af_netlink.groups = nladdr->nl_groups;
+ return 0;
+ }
+
+ if (nladdr->nl_pid == 0) {
+ netlink_autobind(sock);
+ sk->protinfo.af_netlink.groups = nladdr->nl_groups;
+ return 0;
+ }
+
+ netlink_wait_on_table(sk->protocol);
+
+ for (osk=nl_table[sk->protocol]; osk; osk=osk->next) {
+ if (osk->protinfo.af_netlink.pid == nladdr->nl_pid)
+ return -EADDRINUSE;
+ }
+
+ sk->protinfo.af_netlink.pid = nladdr->nl_pid;
+ sk->protinfo.af_netlink.groups = nladdr->nl_groups;
+ netlink_insert(sk);
+ return 0;
+}
+
+static int netlink_connect(struct socket *sock, struct sockaddr *addr,
+ int alen, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_nl *nladdr=(struct sockaddr_nl*)addr;
+
+ if (addr->sa_family == AF_UNSPEC)
+ {
+ sk->protinfo.af_netlink.dst_pid = 0;
+ sk->protinfo.af_netlink.dst_groups = 0;
+ return 0;
+ }
+ if (addr->sa_family != AF_NETLINK)
+ return -EINVAL;
+
+ /* Only superuser is allowed to send multicasts */
+ if (!suser() && nladdr->nl_groups)
+ return -EPERM;
+
+ sk->protinfo.af_netlink.dst_pid = nladdr->nl_pid;
+ sk->protinfo.af_netlink.dst_groups = nladdr->nl_groups;
+
+ if (!sk->protinfo.af_netlink.pid)
+ netlink_autobind(sock);
+ return 0;
+}
+
+static int netlink_getname(struct socket *sock, struct sockaddr *addr, int *addr_len, int peer)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_nl *nladdr=(struct sockaddr_nl *)addr;
+
+ nladdr->nl_family = AF_NETLINK;
+ *addr_len = sizeof(*nladdr);
+
+ if (peer) {
+ nladdr->nl_pid = sk->protinfo.af_netlink.dst_pid;
+ nladdr->nl_groups = sk->protinfo.af_netlink.dst_groups;
+ } else {
+ nladdr->nl_pid = sk->protinfo.af_netlink.pid;
+ nladdr->nl_groups = sk->protinfo.af_netlink.groups;
+ }
+ return 0;
+}
+
+int netlink_unicast(struct sock *ssk, struct sk_buff *skb, pid_t pid, int nonblock)
+{
+ struct sock *sk;
+ int len = skb->len;
+ int protocol = ssk->protocol;
+
+retry:
+ for (sk = nl_table[protocol]; sk; sk = sk->next) {
+ if (sk->protinfo.af_netlink.pid != pid)
+ continue;
+
+ netlink_lock(sk);
+
+#ifdef NL_EMULATE_DEV
+ if (sk->protinfo.af_netlink.handler) {
+ len = sk->protinfo.af_netlink.handler(protocol, skb);
+ netlink_unlock(sk);
+ return len;
+ }
+#endif
+
+ cli();
+ if (atomic_read(&sk->rmem_alloc) > sk->rcvbuf) {
+ if (nonblock) {
+ sti();
+ netlink_unlock(sk);
+ kfree_skb(skb, 0);
+ return -EAGAIN;
+ }
+ interruptible_sleep_on(sk->sleep);
+ netlink_unlock(sk);
+ sti();
+
+ if (signal_pending(current)) {
+ kfree_skb(skb, 0);
+ return -ERESTARTSYS;
+ }
+ goto retry;
+ }
+ sti();
+Nprintk("unicast_deliver %d\n", skb->len);
+ skb_orphan(skb);
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, len);
+ netlink_unlock(sk);
+ return len;
+ }
+ kfree_skb(skb, 0);
+ return -ECONNREFUSED;
+}
+
+static __inline__ int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
+{
+#ifdef NL_EMULATE_DEV
+ if (sk->protinfo.af_netlink.handler) {
+ sk->protinfo.af_netlink.handler(sk->protocol, skb);
+ return 0;
+ } else
+#endif
+ if (atomic_read(&sk->rmem_alloc) <= sk->rcvbuf) {
+Nprintk("broadcast_deliver %d\n", skb->len);
+ skb_orphan(skb);
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, skb->len);
+ return 0;
+ }
+ return -1;
+}
+
+void netlink_broadcast(struct sock *ssk, struct sk_buff *skb, pid_t pid,
+ unsigned group, int allocation)
+{
+ struct sock *sk;
+ struct sk_buff *skb2 = NULL;
+ int protocol = ssk->protocol;
+ int failure = 0;
+
+ /* While we sleep in clone, do not allow to change socket list */
+
+ netlink_lock_table(protocol);
+
+ for (sk = nl_table[protocol]; sk; sk = sk->next) {
+ if (ssk == sk)
+ continue;
+
+ if (sk->protinfo.af_netlink.pid == pid ||
+ !(sk->protinfo.af_netlink.groups&group))
+ continue;
+
+ if (failure) {
+ sk->err = -ENOBUFS;
+ sk->state_change(sk);
+ continue;
+ }
+
+ netlink_lock(sk);
+ if (skb2 == NULL) {
+ if (atomic_read(&skb->users) != 1) {
+ skb2 = skb_clone(skb, allocation);
+ } else {
+ skb2 = skb;
+ atomic_inc(&skb->users);
+ }
+ }
+ if (skb2 == NULL) {
+ sk->err = -ENOBUFS;
+ sk->state_change(sk);
+ /* Clone failed. Notify ALL listeners. */
+ failure = 1;
+ } else if (netlink_broadcast_deliver(sk, skb2)) {
+ sk->err = -ENOBUFS;
+ sk->state_change(sk);
+ } else
+ skb2 = NULL;
+ netlink_unlock(sk);
+ }
+
+ netlink_unlock_table(protocol, allocation == GFP_KERNEL);
+
+ if (skb2)
+ kfree_skb(skb2, 0);
+ kfree_skb(skb, 0);
+}
+
+void netlink_set_err(struct sock *ssk, pid_t pid, unsigned group, int code)
+{
+ struct sock *sk;
+ int protocol = ssk->protocol;
+
+Nprintk("seterr");
+ for (sk = nl_table[protocol]; sk; sk = sk->next) {
+ if (ssk == sk)
+ continue;
+
+ if (sk->protinfo.af_netlink.pid == pid ||
+ !(sk->protinfo.af_netlink.groups&group))
+ continue;
+
+ sk->err = -code;
+ sk->state_change(sk);
+ }
+}
+
+static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_nl *addr=msg->msg_name;
+ pid_t dst_pid;
+ unsigned dst_groups;
+ struct sk_buff *skb;
+ int err;
+
+ if (msg->msg_flags&MSG_OOB)
+ return -EOPNOTSUPP;
+
+ if (msg->msg_flags&~MSG_DONTWAIT) {
+ printk("1 %08x\n", msg->msg_flags);
+ return -EINVAL;
+ }
+
+ if (msg->msg_namelen) {
+ if (addr->nl_family != AF_NETLINK) {
+ printk("2 %08x\n", addr->nl_family);
+ return -EINVAL;
+ }
+ dst_pid = addr->nl_pid;
+ dst_groups = addr->nl_groups;
+ if (dst_groups && !suser())
+ return -EPERM;
+ } else {
+ dst_pid = sk->protinfo.af_netlink.dst_pid;
+ dst_groups = sk->protinfo.af_netlink.dst_groups;
+ }
+
+
+ if (!sk->protinfo.af_netlink.pid)
+ netlink_autobind(sock);
+
+ skb = sock_wmalloc(sk, len, 0, GFP_KERNEL);
+ if (skb==NULL)
+ return -ENOBUFS;
+
+ NETLINK_CB(skb).pid = sk->protinfo.af_netlink.pid;
+ NETLINK_CB(skb).groups = sk->protinfo.af_netlink.groups;
+ NETLINK_CB(skb).dst_pid = dst_pid;
+ NETLINK_CB(skb).dst_groups = dst_groups;
+ memcpy(NETLINK_CREDS(skb), &scm->creds, sizeof(struct ucred));
+ memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len);
+
+ if (dst_groups) {
+ atomic_inc(&skb->users);
+ netlink_broadcast(sk, skb, dst_pid, dst_groups, GFP_KERNEL);
+ }
+ err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
+ if (err < 0) {
+ printk("3\n");
+ }
+ return err;
+}
+
+static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, int len,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int noblock = flags&MSG_DONTWAIT;
+ int copied;
+ struct sk_buff *skb;
+ int err;
+
+ if (flags&(MSG_OOB|MSG_PEEK))
+ return -EOPNOTSUPP;
+
+ err = -sock_error(sk);
+ if (err)
+ return err;
+
+ skb = skb_recv_datagram(sk,flags,noblock,&err);
+ if (skb==NULL)
+ return err;
+
+ msg->msg_namelen = 0;
+
+ copied = skb->len;
+ if (len < copied) {
+ msg->msg_flags |= MSG_TRUNC;
+ copied = len;
+ }
+
+ skb->h.raw = skb->data;
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ if (msg->msg_name) {
+ struct sockaddr_nl *addr = (struct sockaddr_nl*)msg->msg_name;
+ addr->nl_family = AF_NETLINK;
+ addr->nl_pid = NETLINK_CB(skb).pid;
+ addr->nl_groups = NETLINK_CB(skb).dst_groups;
+ msg->msg_namelen = sizeof(*addr);
+ }
+
+ scm->creds = *NETLINK_CREDS(skb);
+ skb_free_datagram(sk, skb);
+
+ if (sk->protinfo.af_netlink.cb
+ && atomic_read(&sk->rmem_alloc) <= sk->rcvbuf/2)
+ netlink_dump(sk);
+ return err ? err : copied;
+}
+
+/*
+ * We export these functions to other modules. They provide a
+ * complete set of kernel non-blocking support for message
+ * queueing.
+ */
+
+struct sock *
+netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))
+{
+ struct socket *sock;
+ struct sock *sk;
+
+ if (unit<0 || unit>=MAX_LINKS)
+ return NULL;
+
+ if (!(sock = sock_alloc()))
+ return NULL;
+
+ sock->type = SOCK_RAW;
+
+ if (netlink_create(sock, unit) < 0) {
+ sock_release(sock);
+ return NULL;
+ }
+ sk = sock->sk;
+ if (input)
+ sk->data_ready = input;
+
+ netlink_insert(sk);
+ return sk;
+}
+
+static void netlink_destroy_callback(struct netlink_callback *cb)
+{
+ if (cb->skb)
+ kfree_skb(cb->skb, 0);
+ kfree(cb);
+}
+
+/*
+ * It looks a bit ugly.
+ * It would be better to create kernel thread.
+ */
+
+static int netlink_dump(struct sock *sk)
+{
+ struct netlink_callback *cb;
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+ int len;
+
+ skb = sock_rmalloc(sk, NLMSG_GOODSIZE, 0, GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ cb = sk->protinfo.af_netlink.cb;
+
+ len = cb->dump(skb, cb);
+
+ if (len > 0) {
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, len);
+ return 0;
+ }
+
+ nlh = __nlmsg_put(skb, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, NLMSG_DONE, sizeof(int));
+ nlh->nlmsg_flags |= NLM_F_MULTI;
+ memcpy(NLMSG_DATA(nlh), &len, sizeof(len));
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, skb->len);
+
+ cb->done(cb);
+ sk->protinfo.af_netlink.cb = NULL;
+ netlink_destroy_callback(cb);
+ netlink_unlock(sk);
+ return 0;
+}
+
+int netlink_dump_start(struct sock *ssk, struct sk_buff *skb,
+ struct nlmsghdr *nlh,
+ int (*dump)(struct sk_buff *skb, struct netlink_callback*),
+ int (*done)(struct netlink_callback*))
+{
+ struct netlink_callback *cb;
+ struct sock *sk;
+
+ cb = kmalloc(sizeof(*cb), GFP_KERNEL);
+ if (cb == NULL)
+ return -ENOBUFS;
+
+ memset(cb, 0, sizeof(*cb));
+ cb->dump = dump;
+ cb->done = done;
+ cb->nlh = nlh;
+ atomic_inc(&skb->users);
+ cb->skb = skb;
+
+ sk = netlink_lookup(ssk->protocol, NETLINK_CB(skb).pid);
+ if (sk == NULL) {
+ netlink_destroy_callback(cb);
+ return -ECONNREFUSED;
+ }
+ /* A dump is in progress... */
+ if (sk->protinfo.af_netlink.cb) {
+ netlink_destroy_callback(cb);
+ netlink_unlock(sk);
+ return -EBUSY;
+ }
+ sk->protinfo.af_netlink.cb = cb;
+ netlink_dump(sk);
+ return 0;
+}
+
+void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err)
+{
+ struct sk_buff *skb;
+ struct nlmsghdr *rep;
+ struct nlmsgerr *errmsg;
+ int size;
+
+ if (err == 0)
+ size = NLMSG_SPACE(sizeof(struct nlmsgerr));
+ else
+ size = NLMSG_SPACE(4 + nlh->nlmsg_len);
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ rep = __nlmsg_put(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq,
+ NLMSG_ERROR, sizeof(struct nlmsgerr));
+ errmsg = NLMSG_DATA(rep);
+ errmsg->error = err;
+ memcpy(&errmsg->msg, nlh, err ? nlh->nlmsg_len : sizeof(struct nlmsghdr));
+ netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+}
+
+
+#ifdef NL_EMULATE_DEV
+/*
+ * Backward compatibility.
+ */
+
+int netlink_attach(int unit, int (*function)(int, struct sk_buff *skb))
+{
+ struct sock *sk = netlink_kernel_create(unit, NULL);
+ if (sk == NULL)
+ return -ENOBUFS;
+ sk->protinfo.af_netlink.handler = function;
+ netlink_kernel[unit] = sk->socket;
+ return 0;
+}
+
+void netlink_detach(int unit)
+{
+ struct socket *sock = netlink_kernel[unit];
+ netlink_kernel[unit] = NULL;
+ sock_release(sock);
+}
+
+int netlink_post(int unit, struct sk_buff *skb)
+{
+ if (netlink_kernel[unit]) {
+ netlink_broadcast(netlink_kernel[unit]->sk, skb, 0, ~0, GFP_ATOMIC);
+ return 0;
+ }
+ return -EUNATCH;;
+}
+
+EXPORT_SYMBOL(netlink_attach);
+EXPORT_SYMBOL(netlink_detach);
+EXPORT_SYMBOL(netlink_post);
+
+#endif
+
+#if 0
+
+/* What a pity... It was good code, but at the moment it
+ results in unnecessary complications.
+ */
+
+/*
+ * "High" level netlink interface. (ANK)
+ *
+ * Features:
+ * - standard message format.
+ * - pseudo-reliable delivery. Messages can be still lost, but
+ * user level will know that they were lost and can
+ * recover (f.e. gated could reread FIB and device list)
+ * - messages are batched.
+ */
+
+/*
+ * Try to deliver queued messages.
+ */
+
+static void nlmsg_delayed_flush(struct sock *sk)
+{
+ nlmsg_flush(sk, GFP_ATOMIC);
+}
+
+static void nlmsg_flush(struct sock *sk, int allocation)
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ while ((skb=skb_dequeue(&sk->write_queue)) != NULL) {
+ if (skb->users != 1) {
+ skb_queue_head(&sk->write_queue, skb);
+ break;
+ }
+ restore_flags(flags);
+ netlink_broadcast(sk, skb, 0, NETLINK_CB(skb).dst_groups, allocation);
+ cli();
+ }
+ start_bh_atomic();
+ restore_flags(flags);
+ if (skb) {
+ if (sk->timer.function)
+ del_timer(&sk->timer)
+ sk->timer.expires = jiffies + (sk->protinfo.af_netlink.delay ? : HZ/2);
+ sk->timer.function = (void (*)(unsigned long))nlmsg_delayed_flush;
+ sk->timer.data = (unsigned long)sk;
+ add_timer(&sk->timer);
+ }
+ end_bh_atomic();
+}
+
+/*
+ * Allocate room for new message. If it is impossible, return NULL.
+ */
+
+void *nlmsg_broadcast(struct sock *sk, struct sk_buff **skbp,
+ unsigned long type, int len,
+ unsigned groups, int allocation)
+{
+ struct nlmsghdr *nlh;
+ struct sk_buff *skb;
+ int rlen;
+ unsigned long flags;
+
+ rlen = NLMSG_SPACE(len);
+
+ save_flags(flags);
+ cli();
+ skb = sk->write_queue.tail;
+ if (skb == sk->write_queue.head)
+ skb = NULL;
+ if (skb == NULL || skb_tailroom(skb) < rlen || NETLINK_CB(skb).dst_groups != groups) {
+ restore_flags(flags);
+
+ if (skb)
+ nlmsg_flush(sk, allocation);
+
+ skb = sock_wmalloc(rlen > NLMSG_GOODSIZE ? rlen : NLMSG_GOODSIZE,
+ sk, 0, allocation);
+
+ if (skb==NULL) {
+ printk (KERN_WARNING "nlmsg at unit %d overrunned\n", sk->protocol);
+ return NULL;
+ }
+
+ NETLINK_CB(skb).dst_groups = groups;
+ cli();
+ skb_queue_tail(&sk->write_queue, skb);
+ }
+ atomic_inc(&skb->users);
+ restore_flags(flags);
+
+ nlh = (struct nlmsghdr*)skb_put(skb, rlen);
+ nlh->nlmsg_type = type;
+ nlh->nlmsg_len = NLMSG_LENGTH(len);
+ nlh->nlmsg_seq = 0;
+ nlh->nlmsg_pid = 0;
+ *skbp = skb;
+ return nlh->nlmsg_data;
+}
+
+struct sk_buff* nlmsg_alloc(unsigned long type, int len,
+ unsigned long seq, unsigned long pid, int allocation)
+{
+ struct nlmsghdr *nlh;
+ struct sk_buff *skb;
+ int rlen;
+
+ rlen = NLMSG_SPACE(len);
+
+ skb = alloc_skb(rlen, allocation);
+ if (skb==NULL)
+ return NULL;
+
+ nlh = (struct nlmsghdr*)skb_put(skb, rlen);
+ nlh->nlmsg_type = type;
+ nlh->nlmsg_len = NLMSG_LENGTH(len);
+ nlh->nlmsg_seq = seq;
+ nlh->nlmsg_pid = pid;
+ return skb;
+}
+
+void nlmsg_release(struct sk_buff *skb)
+{
+ atomic_dec(skb->users);
+}
+
+
+/*
+ * Kick message queue.
+ * Two modes:
+ * - synchronous (delay==0). Messages are delivered immediately.
+ * - delayed. Do not deliver, but start delivery timer.
+ */
+
+void __nlmsg_transmit(struct sock *sk, int allocation)
+{
+ start_bh_atomic();
+ if (!sk->protinfo.af_netlink.delay) {
+ if (sk->timer.function) {
+ del_timer(&sk->timer);
+ sk->timer.function = NULL;
+ }
+ end_bh_atomic();
+ nlmsg_flush(sk, allocation);
+ return;
+ }
+ if (!sk->timer.function) {
+ sk->timer.expires = jiffies + sk->protinfo.af_netlink.delay;
+ sk->timer.function = (void (*)(unsigned long))nlmsg_delayed_flush;
+ sk->timer.data = (unsigned long)sk;
+ add_timer(&sk->timer);
+ }
+ end_bh_atomic();
+}
+
+#endif
+
+#ifdef CONFIG_PROC_FS
+static int netlink_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0;
+ off_t begin=0;
+ int len=0;
+ int i;
+ struct sock *s;
+
+ len+= sprintf(buffer,"sk Eth Pid Groups "
+ "Rmem Wmem Dump Locks\n");
+
+ for (i=0; i<MAX_LINKS; i++) {
+ for (s = nl_table[i]; s; s = s->next) {
+ len+=sprintf(buffer+len,"%p %-3d %-6d %08x %-8d %-8d %p %d",
+ s,
+ s->protocol,
+ s->protinfo.af_netlink.pid,
+ s->protinfo.af_netlink.groups,
+ atomic_read(&s->rmem_alloc),
+ atomic_read(&s->wmem_alloc),
+ s->protinfo.af_netlink.cb,
+ atomic_read(&s->protinfo.af_netlink.locks)
+ );
+
+ buffer[len++]='\n';
+
+ pos=begin+len;
+ if(pos<offset) {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ goto done;
+ }
+ }
+ *eof = 1;
+
+done:
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ return len;
+}
+#endif
+
+struct proto_ops netlink_ops = {
+ AF_NETLINK,
+
+ sock_no_dup,
+ netlink_release,
+ netlink_bind,
+ netlink_connect,
+ NULL,
+ NULL,
+ netlink_getname,
+ datagram_poll,
+ sock_no_ioctl,
+ sock_no_listen,
+ sock_no_shutdown,
+ NULL,
+ NULL,
+ sock_no_fcntl,
+ netlink_sendmsg,
+ netlink_recvmsg
+};
+
+struct net_proto_family netlink_family_ops = {
+ AF_NETLINK,
+ netlink_create
+};
+
+void netlink_proto_init(struct net_proto *pro)
+{
+#ifdef CONFIG_PROC_FS
+ struct proc_dir_entry *ent;
+#endif
+ struct sk_buff *dummy_skb;
+
+ if (sizeof(struct netlink_skb_parms) > sizeof(dummy_skb->cb)) {
+ printk(KERN_CRIT "netlink_proto_init: panic\n");
+ return;
+ }
+ sock_register(&netlink_family_ops);
+#ifdef CONFIG_PROC_FS
+ ent = create_proc_entry("net/netlink", 0, 0);
+ ent->read_proc = netlink_read_proc;
+#endif
+}
diff --git a/net/netlink/netlink_dev.c b/net/netlink/netlink_dev.c
new file mode 100644
index 000000000..cbd48c1c0
--- /dev/null
+++ b/net/netlink/netlink_dev.c
@@ -0,0 +1,213 @@
+/*
+ * NETLINK An implementation of a loadable kernel mode driver providing
+ * multiple kernel/user space bidirectional communications links.
+ *
+ * Author: Alan Cox <alan@cymru.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Now netlink devices are emulated on the top of netlink sockets
+ * by compatibility reasons. Remove this file after a period. --ANK
+ *
+ */
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/malloc.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+static unsigned open_map = 0;
+static struct socket *netlink_user[MAX_LINKS];
+
+/*
+ * Device operations
+ */
+
+static unsigned int netlink_poll(struct file *file, poll_table * wait)
+{
+ struct socket *sock = netlink_user[MINOR(file->f_dentry->d_inode->i_rdev)];
+
+ if (sock->ops->poll==NULL)
+ return 0;
+ return sock->ops->poll(sock, wait);
+}
+
+/*
+ * Write a message to the kernel side of a communication link
+ */
+
+static ssize_t netlink_write(struct file * file, const char * buf,
+ size_t count, loff_t *pos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ struct socket *sock = netlink_user[MINOR(inode->i_rdev)];
+ struct msghdr msg;
+ struct iovec iov;
+
+ iov.iov_base = (void*)buf;
+ iov.iov_len = count;
+ msg.msg_name=NULL;
+ msg.msg_namelen=0;
+ msg.msg_controllen=0;
+ msg.msg_flags=0;
+ msg.msg_iov=&iov;
+ msg.msg_iovlen=1;
+
+ return sock_sendmsg(sock, &msg, count);
+}
+
+/*
+ * Read a message from the kernel side of the communication link
+ */
+
+static ssize_t netlink_read(struct file * file, char * buf,
+ size_t count, loff_t *pos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ struct socket *sock = netlink_user[MINOR(inode->i_rdev)];
+ struct msghdr msg;
+ struct iovec iov;
+
+ iov.iov_base = buf;
+ iov.iov_len = count;
+ msg.msg_name=NULL;
+ msg.msg_namelen=0;
+ msg.msg_controllen=0;
+ msg.msg_flags=0;
+ msg.msg_iov=&iov;
+ msg.msg_iovlen=1;
+ if (file->f_flags&O_NONBLOCK)
+ msg.msg_flags=MSG_DONTWAIT;
+
+ return sock_recvmsg(sock, &msg, count, msg.msg_flags);
+}
+
+static loff_t netlink_lseek(struct file * file, loff_t offset, int origin)
+{
+ return -ESPIPE;
+}
+
+static int netlink_open(struct inode * inode, struct file * file)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ struct socket *sock;
+ struct sockaddr_nl nladdr;
+ int err;
+
+ if (minor>=MAX_LINKS)
+ return -ENODEV;
+ if (open_map&(1<<minor))
+ return -EBUSY;
+
+ open_map |= (1<<minor);
+ MOD_INC_USE_COUNT;
+
+ err = -EINVAL;
+ if (net_families[AF_NETLINK]==NULL)
+ goto out;
+
+ err = -ENFILE;
+ if (!(sock = sock_alloc()))
+ goto out;
+
+ sock->type = SOCK_RAW;
+
+ if ((err = net_families[AF_NETLINK]->create(sock, minor)) < 0)
+ {
+ sock_release(sock);
+ goto out;
+ }
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+ nladdr.nl_groups = ~0;
+ if ((err = sock->ops->bind(sock, (struct sockaddr*)&nladdr, sizeof(nladdr))) < 0) {
+ sock_release(sock);
+ goto out;
+ }
+
+ netlink_user[minor] = sock;
+ return 0;
+
+out:
+ open_map &= ~(1<<minor);
+ return err;
+}
+
+static int netlink_release(struct inode * inode, struct file * file)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ struct socket *sock = netlink_user[minor];
+
+ netlink_user[minor] = NULL;
+ open_map &= ~(1<<minor);
+ sock_release(sock);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+
+static int netlink_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ int retval = 0;
+
+ if (minor >= MAX_LINKS)
+ return -ENODEV;
+ switch ( cmd ) {
+ default:
+ retval = -EINVAL;
+ }
+ return retval;
+}
+
+
+static struct file_operations netlink_fops = {
+ netlink_lseek,
+ netlink_read,
+ netlink_write,
+ NULL, /* netlink_readdir */
+ netlink_poll,
+ netlink_ioctl,
+ NULL, /* netlink_mmap */
+ netlink_open,
+ netlink_release
+};
+
+__initfunc(int init_netlink(void))
+{
+ if (register_chrdev(NETLINK_MAJOR,"netlink", &netlink_fops)) {
+ printk(KERN_ERR "netlink: unable to get major %d\n", NETLINK_MAJOR);
+ return -EIO;
+ }
+ return 0;
+}
+
+#ifdef MODULE
+
+int init_module(void)
+{
+ printk(KERN_INFO "Network Kernel/User communications module 0.04\n");
+ return init_netlink();
+}
+
+void cleanup_module(void)
+{
+ unregister_chrdev(NET_MAJOR,"netlink");
+}
+
+#endif