netlink: allow to listen "all" netns
authorNicolas Dichtel <nicolas.dichtel@6wind.com>
Thu, 7 May 2015 09:02:53 +0000 (11:02 +0200)
committerDavid S. Miller <davem@davemloft.net>
Sun, 10 May 2015 02:15:31 +0000 (22:15 -0400)
More accurately, listen all netns that have a nsid assigned into the netns
where the netlink socket is opened.
For this purpose, a netlink socket option is added:
NETLINK_LISTEN_ALL_NSID. When this option is set on a netlink socket, this
socket will receive netlink notifications from all netns that have a nsid
assigned into the netns where the socket has been opened. The nsid is sent
to userland via an anscillary data.

With this patch, a daemon needs only one socket to listen many netns. This
is useful when the number of netns is high.

Because 0 is a valid value for a nsid, the field nsid_is_set indicates if
the field nsid is valid or not. skb->cb is initialized to 0 on skb
allocation, thus we are sure that we will never send a nsid 0 by error to
the userland.

Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
Acked-by: Thomas Graf <tgraf@suug.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/netlink.h
include/net/net_namespace.h
include/uapi/linux/netlink.h
net/core/net_namespace.c
net/netlink/af_netlink.c

index 6835c1279df77328894ef9bc1396d786e7f29dec..9120edb650a068df60b0a2c390ae431ab78671cb 100644 (file)
@@ -28,6 +28,8 @@ struct netlink_skb_parms {
        __u32                   dst_group;
        __u32                   flags;
        struct sock             *sk;
+       bool                    nsid_is_set;
+       int                     nsid;
 };
 
 #define NETLINK_CB(skb)                (*(struct netlink_skb_parms*)&((skb)->cb))
index 6d1e2eae32fbbc4f5ea2fff3a7128e8291011f19..3f850acc844e7ca99143afc144e6017bc88fce28 100644 (file)
@@ -272,6 +272,8 @@ static inline struct net *read_pnet(const possible_net_t *pnet)
 #endif
 
 int peernet2id_alloc(struct net *net, struct net *peer);
+int peernet2id(struct net *net, struct net *peer);
+bool peernet_has_id(struct net *net, struct net *peer);
 struct net *get_net_ns_by_id(struct net *net, int id);
 
 struct pernet_operations {
index 1a85940f8ab733e85f50ade2ee25f8a527316a89..3e34b7d702f8a4884ee15afeca35c651861d40bc 100644 (file)
@@ -108,6 +108,7 @@ struct nlmsgerr {
 #define NETLINK_NO_ENOBUFS     5
 #define NETLINK_RX_RING                6
 #define NETLINK_TX_RING                7
+#define NETLINK_LISTEN_ALL_NSID        8
 
 struct nl_pktinfo {
        __u32   group;
index ae5008b097de2cf777269a60c47a7b12e4ed883d..a665bf490c8808c43184effa2dba18112155295a 100644 (file)
@@ -229,7 +229,7 @@ int peernet2id_alloc(struct net *net, struct net *peer)
 EXPORT_SYMBOL(peernet2id_alloc);
 
 /* This function returns, if assigned, the id of a peer netns. */
-static int peernet2id(struct net *net, struct net *peer)
+int peernet2id(struct net *net, struct net *peer)
 {
        unsigned long flags;
        int id;
@@ -240,6 +240,14 @@ static int peernet2id(struct net *net, struct net *peer)
        return id;
 }
 
+/* This function returns true is the peer netns has an id assigned into the
+ * current netns.
+ */
+bool peernet_has_id(struct net *net, struct net *peer)
+{
+       return peernet2id(net, peer) >= 0;
+}
+
 struct net *get_net_ns_by_id(struct net *net, int id)
 {
        unsigned long flags;
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);