|
@@ -29,9 +29,16 @@
|
|
|
#include <linux/if_link.h>
|
|
|
#include <linux/if_macvlan.h>
|
|
|
#include <net/rtnetlink.h>
|
|
|
+#include <net/xfrm.h>
|
|
|
|
|
|
#define MACVLAN_HASH_SIZE (1 << BITS_PER_BYTE)
|
|
|
|
|
|
+enum macvlan_mode {
|
|
|
+ MACVLAN_MODE_PRIVATE = 1,
|
|
|
+ MACVLAN_MODE_VEPA = 2,
|
|
|
+ MACVLAN_MODE_BRIDGE = 4,
|
|
|
+};
|
|
|
+
|
|
|
struct macvlan_port {
|
|
|
struct net_device *dev;
|
|
|
struct hlist_head vlan_hash[MACVLAN_HASH_SIZE];
|
|
@@ -59,6 +66,7 @@ struct macvlan_dev {
|
|
|
struct macvlan_port *port;
|
|
|
struct net_device *lowerdev;
|
|
|
struct macvlan_rx_stats *rx_stats;
|
|
|
+ enum macvlan_mode mode;
|
|
|
};
|
|
|
|
|
|
|
|
@@ -134,11 +142,14 @@ static inline void macvlan_count_rx(const struct macvlan_dev *vlan,
|
|
|
}
|
|
|
|
|
|
static int macvlan_broadcast_one(struct sk_buff *skb, struct net_device *dev,
|
|
|
- const struct ethhdr *eth)
|
|
|
+ const struct ethhdr *eth, bool local)
|
|
|
{
|
|
|
if (!skb)
|
|
|
return NET_RX_DROP;
|
|
|
|
|
|
+ if (local)
|
|
|
+ return dev_forward_skb(dev, skb);
|
|
|
+
|
|
|
skb->dev = dev;
|
|
|
if (!compare_ether_addr_64bits(eth->h_dest,
|
|
|
dev->broadcast))
|
|
@@ -150,7 +161,9 @@ static int macvlan_broadcast_one(struct sk_buff *skb, struct net_device *dev,
|
|
|
}
|
|
|
|
|
|
static void macvlan_broadcast(struct sk_buff *skb,
|
|
|
- const struct macvlan_port *port)
|
|
|
+ const struct macvlan_port *port,
|
|
|
+ struct net_device *src,
|
|
|
+ enum macvlan_mode mode)
|
|
|
{
|
|
|
const struct ethhdr *eth = eth_hdr(skb);
|
|
|
const struct macvlan_dev *vlan;
|
|
@@ -164,8 +177,12 @@ static void macvlan_broadcast(struct sk_buff *skb,
|
|
|
|
|
|
for (i = 0; i < MACVLAN_HASH_SIZE; i++) {
|
|
|
hlist_for_each_entry_rcu(vlan, n, &port->vlan_hash[i], hlist) {
|
|
|
+ if (vlan->dev == src || !(vlan->mode & mode))
|
|
|
+ continue;
|
|
|
+
|
|
|
nskb = skb_clone(skb, GFP_ATOMIC);
|
|
|
- err = macvlan_broadcast_one(nskb, vlan->dev, eth);
|
|
|
+ err = macvlan_broadcast_one(nskb, vlan->dev, eth,
|
|
|
+ mode == MACVLAN_MODE_BRIDGE);
|
|
|
macvlan_count_rx(vlan, skb->len + ETH_HLEN,
|
|
|
err == NET_RX_SUCCESS, 1);
|
|
|
}
|
|
@@ -178,6 +195,7 @@ static struct sk_buff *macvlan_handle_frame(struct sk_buff *skb)
|
|
|
const struct ethhdr *eth = eth_hdr(skb);
|
|
|
const struct macvlan_port *port;
|
|
|
const struct macvlan_dev *vlan;
|
|
|
+ const struct macvlan_dev *src;
|
|
|
struct net_device *dev;
|
|
|
unsigned int len;
|
|
|
|
|
@@ -186,7 +204,25 @@ static struct sk_buff *macvlan_handle_frame(struct sk_buff *skb)
|
|
|
return skb;
|
|
|
|
|
|
if (is_multicast_ether_addr(eth->h_dest)) {
|
|
|
- macvlan_broadcast(skb, port);
|
|
|
+ src = macvlan_hash_lookup(port, eth->h_source);
|
|
|
+ if (!src)
|
|
|
+ /* frame comes from an external address */
|
|
|
+ macvlan_broadcast(skb, port, NULL,
|
|
|
+ MACVLAN_MODE_PRIVATE |
|
|
|
+ MACVLAN_MODE_VEPA |
|
|
|
+ MACVLAN_MODE_BRIDGE);
|
|
|
+ else if (src->mode == MACVLAN_MODE_VEPA)
|
|
|
+ /* flood to everyone except source */
|
|
|
+ macvlan_broadcast(skb, port, src->dev,
|
|
|
+ MACVLAN_MODE_VEPA |
|
|
|
+ MACVLAN_MODE_BRIDGE);
|
|
|
+ else if (src->mode == MACVLAN_MODE_BRIDGE)
|
|
|
+ /*
|
|
|
+ * flood only to VEPA ports, bridge ports
|
|
|
+ * already saw the frame on the way out.
|
|
|
+ */
|
|
|
+ macvlan_broadcast(skb, port, src->dev,
|
|
|
+ MACVLAN_MODE_VEPA);
|
|
|
return skb;
|
|
|
}
|
|
|
|
|
@@ -212,18 +248,46 @@ static struct sk_buff *macvlan_handle_frame(struct sk_buff *skb)
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
+static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
|
+{
|
|
|
+ const struct macvlan_dev *vlan = netdev_priv(dev);
|
|
|
+ const struct macvlan_port *port = vlan->port;
|
|
|
+ const struct macvlan_dev *dest;
|
|
|
+
|
|
|
+ if (vlan->mode == MACVLAN_MODE_BRIDGE) {
|
|
|
+ const struct ethhdr *eth = (void *)skb->data;
|
|
|
+
|
|
|
+ /* send to other bridge ports directly */
|
|
|
+ if (is_multicast_ether_addr(eth->h_dest)) {
|
|
|
+ macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
|
|
|
+ goto xmit_world;
|
|
|
+ }
|
|
|
+
|
|
|
+ dest = macvlan_hash_lookup(port, eth->h_dest);
|
|
|
+ if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {
|
|
|
+ unsigned int length = skb->len + ETH_HLEN;
|
|
|
+ int ret = dev_forward_skb(dest->dev, skb);
|
|
|
+ macvlan_count_rx(dest, length,
|
|
|
+ ret == NET_RX_SUCCESS, 0);
|
|
|
+
|
|
|
+ return NET_XMIT_SUCCESS;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+xmit_world:
|
|
|
+ skb->dev = vlan->lowerdev;
|
|
|
+ return dev_queue_xmit(skb);
|
|
|
+}
|
|
|
+
|
|
|
static netdev_tx_t macvlan_start_xmit(struct sk_buff *skb,
|
|
|
struct net_device *dev)
|
|
|
{
|
|
|
int i = skb_get_queue_mapping(skb);
|
|
|
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
|
|
|
- const struct macvlan_dev *vlan = netdev_priv(dev);
|
|
|
unsigned int len = skb->len;
|
|
|
int ret;
|
|
|
|
|
|
- skb->dev = vlan->lowerdev;
|
|
|
- ret = dev_queue_xmit(skb);
|
|
|
-
|
|
|
+ ret = macvlan_queue_xmit(skb, dev);
|
|
|
if (likely(ret == NET_XMIT_SUCCESS)) {
|
|
|
txq->tx_packets++;
|
|
|
txq->tx_bytes += len;
|