packet: add PACKET_FANOUT_FLAG_UNIQUEID to assign new fanout group id.
authorMike Maloney <maloney@google.com>
Fri, 21 Apr 2017 14:56:11 +0000 (10:56 -0400)
committerDavid S. Miller <davem@davemloft.net>
Mon, 24 Apr 2017 16:46:00 +0000 (12:46 -0400)
Fanout uses a per net global namespace. A process that intends to create
a new fanout group can accidentally join an existing group. It is not
possible to detect this.

Add socket option PACKET_FANOUT_FLAG_UNIQUEID.  When specified the
supplied fanout group id must be set to 0, and the kernel chooses an id
that is not already in use.  This is an ephemeral flag so that
other sockets can be added to this group using setsockopt, but NOT
specifying this flag.  The current getsockopt(..., PACKET_FANOUT, ...)
can be used to retrieve the new group id.

We assume that there are not a lot of fanout groups and that this is not
a high frequency call.

The method assigns ids starting at zero and increases until it finds an
unused id.  It keeps track of the last assigned id, and uses it as a
starting point to find new ids.

Signed-off-by: Mike Maloney <maloney@google.com>
Acked-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/if_packet.h
net/packet/af_packet.c

index 9e7edfd8141e5dea69129807f46b89b2b637ead9..4df96a7dd4fae591d834735046911337221c60d3 100644 (file)
@@ -66,6 +66,7 @@ struct sockaddr_ll {
 #define PACKET_FANOUT_CBPF             6
 #define PACKET_FANOUT_EBPF             7
 #define PACKET_FANOUT_FLAG_ROLLOVER    0x1000
+#define PACKET_FANOUT_FLAG_UNIQUEID    0x2000
 #define PACKET_FANOUT_FLAG_DEFRAG      0x8000
 
 struct tpacket_stats {
index 8489beff5c25c971067f38833ed4a790a98dd86e..94052f42058b5dd911df87b3102bf3ec36a73653 100644 (file)
@@ -1496,6 +1496,7 @@ static int packet_rcv_fanout(struct sk_buff *skb, struct net_device *dev,
 DEFINE_MUTEX(fanout_mutex);
 EXPORT_SYMBOL_GPL(fanout_mutex);
 static LIST_HEAD(fanout_list);
+static u16 fanout_next_id;
 
 static void __fanout_link(struct sock *sk, struct packet_sock *po)
 {
@@ -1629,6 +1630,36 @@ static void fanout_release_data(struct packet_fanout *f)
        };
 }
 
+static bool __fanout_id_is_free(struct sock *sk, u16 candidate_id)
+{
+       struct packet_fanout *f;
+
+       list_for_each_entry(f, &fanout_list, list) {
+               if (f->id == candidate_id &&
+                   read_pnet(&f->net) == sock_net(sk)) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+static bool fanout_find_new_id(struct sock *sk, u16 *new_id)
+{
+       u16 id = fanout_next_id;
+
+       do {
+               if (__fanout_id_is_free(sk, id)) {
+                       *new_id = id;
+                       fanout_next_id = id + 1;
+                       return true;
+               }
+
+               id++;
+       } while (id != fanout_next_id);
+
+       return false;
+}
+
 static int fanout_add(struct sock *sk, u16 id, u16 type_flags)
 {
        struct packet_rollover *rollover = NULL;
@@ -1676,6 +1707,19 @@ static int fanout_add(struct sock *sk, u16 id, u16 type_flags)
                po->rollover = rollover;
        }
 
+       if (type_flags & PACKET_FANOUT_FLAG_UNIQUEID) {
+               if (id != 0) {
+                       err = -EINVAL;
+                       goto out;
+               }
+               if (!fanout_find_new_id(sk, &id)) {
+                       err = -ENOMEM;
+                       goto out;
+               }
+               /* ephemeral flag for the first socket in the group: drop it */
+               flags &= ~(PACKET_FANOUT_FLAG_UNIQUEID >> 8);
+       }
+
        match = NULL;
        list_for_each_entry(f, &fanout_list, list) {
                if (f->id == id &&