commit 834f8a8dca1f508a67dbb36422549901a6df62fc Author: Jason Baron Date: Wed May 24 01:32:33 2017 +0000 delay close diff --git a/include/net/af_unix.h b/include/net/af_unix.h index fd60ecc..739cddf 100644 --- a/include/net/af_unix.h +++ b/include/net/af_unix.h @@ -27,6 +27,15 @@ struct unix_address { struct sockaddr_un name[0]; }; +struct unix_persist { + struct path path; + struct list_head link; + /* queue all packets on here */ + struct sk_buff_head receive_queue; + struct delayed_work dw; + int delay; +}; + struct unix_skb_parms { struct pid *pid; /* Skb credentials */ kuid_t uid; @@ -63,6 +72,7 @@ struct unix_sock { #define UNIX_GC_MAYBE_CYCLE 1 struct socket_wq peer_wq; wait_queue_t peer_wake; + struct unix_persist *persist; }; static inline struct unix_sock *unix_sk(const struct sock *sk) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 6a7fe76..18a7924 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -124,7 +124,8 @@ EXPORT_SYMBOL_GPL(unix_socket_table); DEFINE_SPINLOCK(unix_table_lock); EXPORT_SYMBOL_GPL(unix_table_lock); static atomic_long_t unix_nr_socks; - +LIST_HEAD(unix_persist_head); +DEFINE_SPINLOCK(unix_persist_lock); static struct hlist_head *unix_sockets_unbound(void *addr) { @@ -508,6 +509,27 @@ static void unix_sock_destructor(struct sock *sk) #endif } +static void unix_persist_delayed_work(struct work_struct *work) +{ + struct delayed_work *delay = to_delayed_work(work); + struct unix_persist *persist = container_of(delay, struct unix_persist, dw); + bool del = false; + + spin_lock(&unix_persist_lock); + if (!list_empty(&persist->link)) { + del = true; + list_del_init(&persist->link); + } + spin_unlock(&unix_persist_lock); + + if (!del) + return; + + skb_queue_purge(&persist->receive_queue); + path_put(&persist->path); + kfree(persist); +} + static void unix_release_sock(struct sock *sk, int embrion) { struct unix_sock *u = unix_sk(sk); @@ -515,6 +537,8 @@ static void unix_release_sock(struct sock *sk, int embrion) struct sock *skpair; struct sk_buff *skb; int state; + struct unix_persist *persist; + bool do_persist = false; unix_remove_socket(sk); @@ -550,12 +574,29 @@ static void unix_release_sock(struct sock *sk, int embrion) unix_peer(sk) = NULL; } - /* Try to flush out this socket. Throw out buffers at least */ + persist = u->persist; + if (persist) { + if (persist->delay && path.dentry) { + do_persist = true; + path_get(&path); + persist->path = path; + skb_queue_head_init(&persist->receive_queue); + INIT_DELAYED_WORK(&persist->dw, unix_persist_delayed_work); + schedule_delayed_work(&persist->dw, msecs_to_jiffies(persist->delay)); + } else + kfree(persist); + } + /* Try to flush out this socket. Throw out buffers at least */ while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) { + /* FIXME: persists anything special for listen ??? */ if (state == TCP_LISTEN) unix_release_sock(skb->sk, 1); /* passed fds are erased in the kfree_skb hook */ + if (do_persist) { + skb_queue_tail(&persist->receive_queue, skb); + continue; + } UNIXCB(skb).consumed = skb->len; kfree_skb(skb); } @@ -563,6 +604,12 @@ static void unix_release_sock(struct sock *sk, int embrion) if (path.dentry) path_put(&path); + if (do_persist) { + spin_lock(&unix_persist_lock); + list_add(&u->persist->link, &unix_persist_head); + spin_unlock(&unix_persist_lock); + } + sock_put(sk); /* ---- Socket is dead now and most probably destroyed ---- */ @@ -671,6 +718,93 @@ static int unix_set_peek_off(struct sock *sk, int val) return 0; } +#define UNIX_DELAY_CLOSE 1 +#define UNIX_REBIND 2 +#define SOL_UNIX 5 + +int unix_setsockopt(struct socket *sock, int level, int optname, + char __user *optval, unsigned int optlen) +{ + struct sock *sk = sock->sk; + struct unix_sock *u = unix_sk(sk); + int val; + int err = 0; + + /* FIXME lock sock for allocations */ + + if (level != SOL_UNIX) { + /* FIXME: check return */ + return -ENOPROTOOPT; + } + + if (optlen < sizeof(int)) + return -EINVAL; + + if (get_user(val, (int __user *)optval)) + return -EFAULT; + + switch(optname) { + case UNIX_DELAY_CLOSE: { + struct unix_persist *tmp; + /* limit to 1 minute? */ + if (val <= 0 || val > 60000) + err = -EINVAL; + printk("set delay: %d\n", val); + if (!u->persist) { + tmp = kmalloc(sizeof(struct unix_persist), GFP_KERNEL); + if (!tmp) { + err = -ENOMEM; + break; + } + if (cmpxchg(&u->persist, NULL, tmp)) + kfree(tmp); + } + u->persist->delay = val; + break; + } + default: + err = -ENOPROTOOPT; + break; + } + return err; +} + +int unix_getsockopt(struct socket *sock, int level, int optname, + char __user *optval, int __user *optlen) + +{ + struct sock *sk = sock->sk; + struct unix_sock *u = unix_sk(sk); + int val, len; + + if (get_user(len, optlen)) + return -EFAULT; + + if (len < 0) + return -EINVAL; + + switch(optname) { + case UNIX_DELAY_CLOSE: + if (u->persist) + val = u->persist->delay; + else + val = 0; + break; + default: + return -ENOPROTOOPT; + } + + if (len > sizeof(int)) + len = sizeof(int); + + if (copy_to_user(optval, &val, len)) + return -EFAULT; + + if (put_user(len, optlen)) + return -EFAULT; + + return 0; +} static const struct proto_ops unix_stream_ops = { .family = PF_UNIX, @@ -685,8 +819,8 @@ static const struct proto_ops unix_stream_ops = { .ioctl = unix_ioctl, .listen = unix_listen, .shutdown = unix_shutdown, - .setsockopt = sock_no_setsockopt, - .getsockopt = sock_no_getsockopt, + .setsockopt = unix_setsockopt, + .getsockopt = unix_getsockopt, .sendmsg = unix_stream_sendmsg, .recvmsg = unix_stream_recvmsg, .mmap = sock_no_mmap, @@ -708,8 +842,8 @@ static const struct proto_ops unix_dgram_ops = { .ioctl = unix_ioctl, .listen = sock_no_listen, .shutdown = unix_shutdown, - .setsockopt = sock_no_setsockopt, - .getsockopt = sock_no_getsockopt, + .setsockopt = unix_setsockopt, + .getsockopt = unix_getsockopt, .sendmsg = unix_dgram_sendmsg, .recvmsg = unix_dgram_recvmsg, .mmap = sock_no_mmap, @@ -730,8 +864,8 @@ static const struct proto_ops unix_seqpacket_ops = { .ioctl = unix_ioctl, .listen = unix_listen, .shutdown = unix_shutdown, - .setsockopt = sock_no_setsockopt, - .getsockopt = sock_no_getsockopt, + .setsockopt = unix_setsockopt, + .getsockopt = unix_getsockopt, .sendmsg = unix_seqpacket_sendmsg, .recvmsg = unix_seqpacket_recvmsg, .mmap = sock_no_mmap, @@ -985,6 +1119,64 @@ static int unix_mknod(const char *sun_path, umode_t mode, struct path *res) return err; } +static struct unix_persist *unix_get_persist(struct net *net, + struct sockaddr_un *sunname, int len, + int type, unsigned int hash) +{ + struct inode *inode; + struct unix_persist *entry, *res = NULL; + struct path path; + int err = 0; + + printk("unix_get_persist: enter\n"); + + if (!sunname->sun_path[0]) { + printk("unix_get_persist 1\n"); + return NULL; + } + + err = kern_path(sunname->sun_path, LOOKUP_FOLLOW, &path); + if (err) { + printk("unix_get_persist 2: %s %d\n", sunname->sun_path, err); + return NULL; + } + inode = d_backing_inode(path.dentry); + err = inode_permission(inode, MAY_WRITE); + if (err) { + printk("unix_get_persist 3\n"); + goto out; + } + + if (!S_ISSOCK(inode->i_mode)) { + printk("unix_get_persist 4\n"); + spin_lock(&unix_persist_lock); + list_for_each_entry(entry, &unix_persist_head, link) { + struct dentry *dentry = entry->path.dentry; + + printk("unix_get_persist: %p %p\n", d_backing_inode(dentry), inode); + } + spin_unlock(&unix_persist_lock); + goto out; + } + + spin_lock(&unix_persist_lock); + list_for_each_entry(entry, &unix_persist_head, link) { + struct dentry *dentry = entry->path.dentry; + + printk("unix_get_delayed_close: %p %p\n", d_backing_inode(dentry), inode); + if (dentry && d_backing_inode(dentry) == inode) { + list_del_init(&entry->link); + res = entry; + break; + } + } + spin_unlock(&unix_persist_lock); + +out: + path_put(&path); + return res; +} + static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { struct sock *sk = sock->sk; @@ -997,6 +1189,7 @@ static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) struct unix_address *addr; struct hlist_head *list; struct path path = { }; + err = -EINVAL; if (sunaddr->sun_family != AF_UNIX) @@ -1013,13 +1206,38 @@ static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) addr_len = err; if (sun_path[0]) { - umode_t mode = S_IFSOCK | - (SOCK_INODE(sock)->i_mode & ~current_umask()); - err = unix_mknod(sun_path, mode, &path); - if (err) { - if (err == -EEXIST) - err = -EADDRINUSE; - goto out; + /* first check if we can bind to existing socket */ + struct unix_persist *persist = unix_get_persist(net, + sunaddr, addr_len, + sock->type, hash); + if (persist) { + struct sk_buff *skb = NULL; + + cancel_delayed_work(&persist->dw); + unix_state_lock(sk); + if (skb_queue_len(&sk->sk_receive_queue) + skb_queue_len(&persist->receive_queue) > sk->sk_max_ack_backlog) { + unix_state_unlock(sk); + skb_queue_purge(&persist->receive_queue); + kfree(persist); + goto out; + } + while ((skb = skb_dequeue(&persist->receive_queue)) != NULL) { + skb_queue_tail(&sk->sk_receive_queue, skb); + } + unix_state_unlock(sk); + path = persist->path; + kfree(persist); + /* FIXME: test if queue is not empty */ + sk->sk_data_ready(sk); + } else { + umode_t mode = S_IFSOCK | + (SOCK_INODE(sock)->i_mode & ~current_umask()); + err = unix_mknod(sun_path, mode, &path); + if (err) { + if (err == -EEXIST) + err = -EADDRINUSE; + goto out; + } } }