The ovpn DCO driver currently drops all multicast/broadcast packets
because it does not set IFF_MULTICAST and IFF_BROADCAST on the
netdevice and always performs a unicast peer lookup in ovpn_net_xmit().
This prevents multicast routing daemons such as smcroute from using an
ovpn interface as a multicast VIF and makes it impossible to
forward multicastand broadcast traffic to VPN clients.

Add the minimal infrastructure needed to get multicast/broadcast working:

- Set IFF_MULTICAST and IFF_BROADCAST in ovpn_setup().

- Detect multicast and broadcast destinations in
  ovpn_peer_list_get_by_dst() and create a list with the target peers.

- Introduce ovpn_skb_list_clone() to clone GSO segment lists and
  replicate the packet to every connected peer in
  ovpn_net_xmit().

For now multicast traffic is flooded to all peers. A future
enhancement will replace the flood with a subscription table driven by
IGMP snooping.

Signed-off-by: Marco Baffo <[email protected]>
---
 drivers/net/ovpn/io.c   | 57 ++++++++++++++++++++++++++-----
 drivers/net/ovpn/main.c |  2 +-
 drivers/net/ovpn/peer.c | 76 ++++++++++++++++++++++++++++++++---------
 drivers/net/ovpn/peer.h |  5 +--
 4 files changed, 112 insertions(+), 28 deletions(-)

diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 22c555dd962e..acf0907dd445 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -351,15 +351,39 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct 
sk_buff *skb,
        ovpn_peer_put(peer);
 }
 
+static struct sk_buff *ovpn_skb_list_clone(struct sk_buff *skb)
+{
+       struct sk_buff *copy, *curr, *next, *head = NULL, *prev = NULL;
+
+       skb_list_walk_safe(skb, curr, next) {
+               copy = skb_clone(curr, GFP_ATOMIC);
+               if (unlikely(!copy)) {
+                       kfree_skb_list(head);
+                       return NULL;
+               }
+
+               if (unlikely(!head))
+                       head = copy;
+               else
+                       prev->next = copy;
+
+               prev = copy;
+       }
+
+       return head;
+}
+
 /* Send user data to the network
  */
 netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
 {
        struct ovpn_priv *ovpn = netdev_priv(dev);
-       struct sk_buff *segments, *curr, *next;
+       struct sk_buff *segments, *curr, *next, *to_send;
        struct sk_buff_head skb_list;
-       unsigned int tx_bytes = 0;
+       struct llist_head mcast_list;
+       struct llist_node *node, *n;
        struct ovpn_peer *peer;
+       unsigned int tx_bytes = 0;
        __be16 proto;
        int ret;
 
@@ -372,8 +396,9 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct 
net_device *dev)
                goto drop_no_peer;
 
        /* retrieve peer serving the destination IP of this packet */
-       peer = ovpn_peer_get_by_dst(ovpn, skb);
-       if (unlikely(!peer)) {
+       init_llist_head(&mcast_list);
+       ovpn_peer_list_get_by_dst(ovpn, skb, &mcast_list);
+       if (unlikely(llist_empty(&mcast_list))) {
                switch (skb->protocol) {
                case htons(ETH_P_IP):
                        net_dbg_ratelimited("%s: no peer to send data to 
dst=%pI4\n",
@@ -427,18 +452,34 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct 
net_device *dev)
         * incremented the counter for each failure in the loop
         */
        if (unlikely(skb_queue_empty(&skb_list))) {
-               ovpn_peer_put(peer);
+               llist_for_each_safe(node, n, mcast_list.first) {
+                       peer = llist_entry(node, struct ovpn_peer, mcast_entry);
+                       ovpn_peer_put(peer);
+               }
                return NETDEV_TX_OK;
        }
        skb_list.prev->next = NULL;
 
-       ovpn_peer_stats_increment_tx(&peer->vpn_stats, tx_bytes);
-       ovpn_send(ovpn, skb_list.next, peer);
+       llist_for_each_safe(node, n, mcast_list.first) {
+               peer = llist_entry(node, struct ovpn_peer, mcast_entry);
+
+               to_send = n ? ovpn_skb_list_clone(skb_list.next) : 
skb_list.next;
+               if (likely(to_send)) {
+                       ovpn_peer_stats_increment_tx(&peer->vpn_stats, 
tx_bytes);
+                       ovpn_send(ovpn, to_send, peer);
+               } else {
+                       dev_dstats_tx_dropped(ovpn->dev);
+                       ovpn_peer_put(peer);
+               }
+       }
 
        return NETDEV_TX_OK;
 
 drop:
-       ovpn_peer_put(peer);
+       llist_for_each_safe(node, n, mcast_list.first) {
+               peer = llist_entry(node, struct ovpn_peer, mcast_entry);
+               ovpn_peer_put(peer);
+       }
 drop_no_peer:
        dev_dstats_tx_dropped(ovpn->dev);
        skb_tx_error(skb);
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 2e0420febda0..ee9cb61a090f 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -155,7 +155,7 @@ static void ovpn_setup(struct net_device *dev)
        dev->max_mtu = IP_MAX_MTU - OVPN_HEAD_ROOM;
 
        dev->type = ARPHRD_NONE;
-       dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+       dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | 
IFF_BROADCAST;
        dev->priv_flags |= IFF_NO_QUEUE;
        /* when routing packets to a LAN behind a client, we rely on the
         * route entry that originally brought the packet into ovpn, so
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index c02dfab51a6e..06d47c468956 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -718,23 +718,49 @@ static void ovpn_peer_remove(struct ovpn_peer *peer,
        llist_add(&peer->release_entry, release_list);
 }
 
+static void ovpn_peer_list_get_all(struct ovpn_priv *ovpn,
+                                  struct llist_head *list)
+{
+       struct ovpn_peer *peer;
+       int bkt;
+
+       rcu_read_lock();
+       hash_for_each_rcu(ovpn->peers->by_id, bkt, peer, hash_entry_id) {
+               if (ovpn_peer_hold(peer))
+                       llist_add(&peer->mcast_entry, list);
+       }
+       rcu_read_unlock();
+}
+
+/**
+ * TO DO: At the moment the list contain all the peers,
+ * after IGMP snooping is implemented we want to select only the peers
+ * subscribed to a specific multicast group.
+ */
+static void ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn,
+                                             struct llist_head *list)
+{
+       ovpn_peer_list_get_all(ovpn, list);
+}
+
 /**
- * ovpn_peer_get_by_dst - Lookup peer to send skb to
+ * ovpn_peer_list_get_by_dst - Lookup peers to send skb to
  * @ovpn: the private data representing the current VPN session
  * @skb: the skb to extract the destination address from
+ * @list: the head of the list to fill with the target peers
  *
- * This function takes a tunnel packet and looks up the peer to send it to
- * after encapsulation. The skb is expected to be the in-tunnel packet, without
- * any OpenVPN related header.
+ * This function takes a tunnel packet and looks up the peers to send it to
+ * after encapsulation and add them to `list'. The skb is expected to be the
+ * in-tunnel packet, without any OpenVPN related header.
  *
  * Assume that the IP header is accessible in the skb data.
  *
- * Return: the peer if found or NULL otherwise.
  */
-struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn,
-                                      struct sk_buff *skb)
+void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb,
+                              struct llist_head *list)
 {
        struct ovpn_peer *peer = NULL;
+       unsigned int addr_type;
        struct in6_addr addr6;
        __be32 addr4;
 
@@ -744,29 +770,45 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv 
*ovpn,
        if (ovpn->mode == OVPN_MODE_P2P) {
                rcu_read_lock();
                peer = rcu_dereference(ovpn->peer);
-               if (unlikely(peer && !ovpn_peer_hold(peer)))
-                       peer = NULL;
+               if (likely(peer && ovpn_peer_hold(peer)))
+                       llist_add(&peer->mcast_entry, list);
                rcu_read_unlock();
-               return peer;
+               return;
        }
 
-       rcu_read_lock();
        switch (skb->protocol) {
        case htons(ETH_P_IP):
                addr4 = ovpn_nexthop_from_skb4(skb);
+               rcu_read_lock();
                peer = ovpn_peer_get_by_vpn_addr4(ovpn, addr4);
-               break;
+
+               if (peer)
+                       break;
+
+               rcu_read_unlock();
+               addr_type = inet_dev_addr_type(dev_net(ovpn->dev), ovpn->dev, 
addr4);
+               if (addr_type == RTN_MULTICAST)
+                       ovpn_peer_list_get_by_mcast_group(ovpn, list);
+               else if (addr_type == RTN_BROADCAST)
+                       ovpn_peer_list_get_all(ovpn, list);
+               return;
        case htons(ETH_P_IPV6):
                addr6 = ovpn_nexthop_from_skb6(skb);
+               rcu_read_lock();
                peer = ovpn_peer_get_by_vpn_addr6(ovpn, &addr6);
-               break;
+
+               if (peer)
+                       break;
+
+               rcu_read_unlock();
+               if (ipv6_addr_is_multicast(&addr6))
+                       ovpn_peer_list_get_by_mcast_group(ovpn, list);
+               return;
        }
 
-       if (unlikely(peer && !ovpn_peer_hold(peer)))
-               peer = NULL;
+       if (likely(peer && ovpn_peer_hold(peer)))
+               llist_add(&peer->mcast_entry, list);
        rcu_read_unlock();
-
-       return peer;
 }
 
 /**
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 328401570cba..08a471deb187 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -113,6 +113,7 @@ struct ovpn_peer {
        struct kref refcount;
        struct rcu_head rcu;
        struct llist_node release_entry;
+       struct llist_node mcast_entry;
        struct work_struct keepalive_work;
 };
 
@@ -148,8 +149,8 @@ void ovpn_peers_free(struct ovpn_priv *ovpn, struct sock 
*sock,
 struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn,
                                               struct sk_buff *skb);
 struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id);
-struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn,
-                                      struct sk_buff *skb);
+void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb,
+                              struct llist_head *list);
 void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer);
 bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb,
                            struct ovpn_peer *peer);
-- 
2.43.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to