]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - net/netlink/af_netlink.c
netlink: allow to listen "all" netns
[mirror_ubuntu-artful-kernel.git] / net / netlink / af_netlink.c
index bf7f56d7a9aa1406ad7c6f7a093ee03adcc4787b..a5fff75accf8babec5f526f93111c223eb4a7d19 100644 (file)
@@ -83,6 +83,7 @@ struct listeners {
 #define NETLINK_F_RECV_PKTINFO         0x2
 #define NETLINK_F_BROADCAST_SEND_ERROR 0x4
 #define NETLINK_F_RECV_NO_ENOBUFS      0x8
+#define NETLINK_F_LISTEN_ALL_NSID      0x10
 
 static inline int netlink_is_kernel(struct sock *sk)
 {
@@ -1932,8 +1933,17 @@ static void do_one_broadcast(struct sock *sk,
            !test_bit(p->group - 1, nlk->groups))
                return;
 
-       if (!net_eq(sock_net(sk), p->net))
-               return;
+       if (!net_eq(sock_net(sk), p->net)) {
+               if (!(nlk->flags & NETLINK_F_LISTEN_ALL_NSID))
+                       return;
+
+               if (!peernet_has_id(sock_net(sk), p->net))
+                       return;
+
+               if (!file_ns_capable(sk->sk_socket->file, p->net->user_ns,
+                                    CAP_NET_BROADCAST))
+                       return;
+       }
 
        if (p->failure) {
                netlink_overrun(sk);
@@ -1959,13 +1969,22 @@ static void do_one_broadcast(struct sock *sk,
                p->failure = 1;
                if (nlk->flags & NETLINK_F_BROADCAST_SEND_ERROR)
                        p->delivery_failure = 1;
-       } else if (p->tx_filter && p->tx_filter(sk, p->skb2, p->tx_data)) {
+               goto out;
+       }
+       if (p->tx_filter && p->tx_filter(sk, p->skb2, p->tx_data)) {
                kfree_skb(p->skb2);
                p->skb2 = NULL;
-       } else if (sk_filter(sk, p->skb2)) {
+               goto out;
+       }
+       if (sk_filter(sk, p->skb2)) {
                kfree_skb(p->skb2);
                p->skb2 = NULL;
-       } else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) {
+               goto out;
+       }
+       NETLINK_CB(p->skb2).nsid = peernet2id(sock_net(sk), p->net);
+       NETLINK_CB(p->skb2).nsid_is_set = true;
+       val = netlink_broadcast_deliver(sk, p->skb2);
+       if (val < 0) {
                netlink_overrun(sk);
                if (nlk->flags & NETLINK_F_BROADCAST_SEND_ERROR)
                        p->delivery_failure = 1;
@@ -1974,6 +1993,7 @@ static void do_one_broadcast(struct sock *sk,
                p->delivered = 1;
                p->skb2 = NULL;
        }
+out:
        sock_put(sk);
 }
 
@@ -2202,6 +2222,16 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname,
                break;
        }
 #endif /* CONFIG_NETLINK_MMAP */
+       case NETLINK_LISTEN_ALL_NSID:
+               if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_BROADCAST))
+                       return -EPERM;
+
+               if (val)
+                       nlk->flags |= NETLINK_F_LISTEN_ALL_NSID;
+               else
+                       nlk->flags &= ~NETLINK_F_LISTEN_ALL_NSID;
+               err = 0;
+               break;
        default:
                err = -ENOPROTOOPT;
        }
@@ -2268,6 +2298,16 @@ static void netlink_cmsg_recv_pktinfo(struct msghdr *msg, struct sk_buff *skb)
        put_cmsg(msg, SOL_NETLINK, NETLINK_PKTINFO, sizeof(info), &info);
 }
 
+static void netlink_cmsg_listen_all_nsid(struct sock *sk, struct msghdr *msg,
+                                        struct sk_buff *skb)
+{
+       if (!NETLINK_CB(skb).nsid_is_set)
+               return;
+
+       put_cmsg(msg, SOL_NETLINK, NETLINK_LISTEN_ALL_NSID, sizeof(int),
+                &NETLINK_CB(skb).nsid);
+}
+
 static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 {
        struct sock *sk = sock->sk;
@@ -2421,6 +2461,8 @@ static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
 
        if (nlk->flags & NETLINK_F_RECV_PKTINFO)
                netlink_cmsg_recv_pktinfo(msg, skb);
+       if (nlk->flags & NETLINK_F_LISTEN_ALL_NSID)
+               netlink_cmsg_listen_all_nsid(sk, msg, skb);
 
        memset(&scm, 0, sizeof(scm));
        scm.creds = *NETLINK_CREDS(skb);