diff options
Diffstat (limited to 'drivers/net/macvlan.c')
-rw-r--r-- | drivers/net/macvlan.c | 280 |
1 files changed, 220 insertions, 60 deletions
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index 3aabfd9..21a9c9a 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -29,6 +29,7 @@ #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) @@ -38,12 +39,28 @@ struct macvlan_port { struct list_head vlans; }; +/** + * struct macvlan_rx_stats - MACVLAN percpu rx stats + * @rx_packets: number of received packets + * @rx_bytes: number of received bytes + * @multicast: number of received multicast packets + * @rx_errors: number of errors + */ +struct macvlan_rx_stats { + unsigned long rx_packets; + unsigned long rx_bytes; + unsigned long multicast; + unsigned long rx_errors; +}; + struct macvlan_dev { struct net_device *dev; struct list_head list; struct hlist_node hlist; struct macvlan_port *port; struct net_device *lowerdev; + struct macvlan_rx_stats *rx_stats; + enum macvlan_mode mode; }; @@ -101,41 +118,67 @@ static int macvlan_addr_busy(const struct macvlan_port *port, return 0; } +static inline void macvlan_count_rx(const struct macvlan_dev *vlan, + unsigned int len, bool success, + bool multicast) +{ + struct macvlan_rx_stats *rx_stats; + + rx_stats = per_cpu_ptr(vlan->rx_stats, smp_processor_id()); + if (likely(success)) { + rx_stats->rx_packets++;; + rx_stats->rx_bytes += len; + if (multicast) + rx_stats->multicast++; + } else { + rx_stats->rx_errors++; + } +} + +static int macvlan_broadcast_one(struct sk_buff *skb, struct net_device *dev, + 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)) + skb->pkt_type = PACKET_BROADCAST; + else + skb->pkt_type = PACKET_MULTICAST; + + return netif_rx(skb); +} + 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; struct hlist_node *n; - struct net_device *dev; struct sk_buff *nskb; unsigned int i; + int err; if (skb->protocol == htons(ETH_P_PAUSE)) return; for (i = 0; i < MACVLAN_HASH_SIZE; i++) { hlist_for_each_entry_rcu(vlan, n, &port->vlan_hash[i], hlist) { - dev = vlan->dev; - - nskb = skb_clone(skb, GFP_ATOMIC); - if (nskb == NULL) { - dev->stats.rx_errors++; - dev->stats.rx_dropped++; + if (vlan->dev == src || !(vlan->mode & mode)) continue; - } - - dev->stats.rx_bytes += skb->len + ETH_HLEN; - dev->stats.rx_packets++; - dev->stats.multicast++; - - nskb->dev = dev; - if (!compare_ether_addr_64bits(eth->h_dest, dev->broadcast)) - nskb->pkt_type = PACKET_BROADCAST; - else - nskb->pkt_type = PACKET_MULTICAST; - netif_rx(nskb); + nskb = skb_clone(skb, GFP_ATOMIC); + 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); } } } @@ -146,14 +189,34 @@ 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; port = rcu_dereference(skb->dev->macvlan_port); if (port == NULL) 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; } @@ -166,16 +229,11 @@ static struct sk_buff *macvlan_handle_frame(struct sk_buff *skb) kfree_skb(skb); return NULL; } - + len = skb->len + ETH_HLEN; skb = skb_share_check(skb, GFP_ATOMIC); - if (skb == NULL) { - dev->stats.rx_errors++; - dev->stats.rx_dropped++; + macvlan_count_rx(vlan, len, skb != NULL, 0); + if (!skb) return NULL; - } - - dev->stats.rx_bytes += skb->len + ETH_HLEN; - dev->stats.rx_packets++; skb->dev = dev; skb->pkt_type = PACKET_HOST; @@ -184,25 +242,53 @@ 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; } else txq->tx_dropped++; - return NETDEV_TX_OK; + return ret; } static int macvlan_hard_header(struct sk_buff *skb, struct net_device *dev, @@ -360,14 +446,53 @@ static int macvlan_init(struct net_device *dev) dev->state = (dev->state & ~MACVLAN_STATE_MASK) | (lowerdev->state & MACVLAN_STATE_MASK); dev->features = lowerdev->features & MACVLAN_FEATURES; + dev->gso_max_size = lowerdev->gso_max_size; dev->iflink = lowerdev->ifindex; dev->hard_header_len = lowerdev->hard_header_len; macvlan_set_lockdep_class(dev); + vlan->rx_stats = alloc_percpu(struct macvlan_rx_stats); + if (!vlan->rx_stats) + return -ENOMEM; + return 0; } +static void macvlan_uninit(struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + + free_percpu(vlan->rx_stats); +} + +static struct net_device_stats *macvlan_dev_get_stats(struct net_device *dev) +{ + struct net_device_stats *stats = &dev->stats; + struct macvlan_dev *vlan = netdev_priv(dev); + + dev_txq_stats_fold(dev, stats); + + if (vlan->rx_stats) { + struct macvlan_rx_stats *p, rx = {0}; + int i; + + for_each_possible_cpu(i) { + p = per_cpu_ptr(vlan->rx_stats, i); + rx.rx_packets += p->rx_packets; + rx.rx_bytes += p->rx_bytes; + rx.rx_errors += p->rx_errors; + rx.multicast += p->multicast; + } + stats->rx_packets = rx.rx_packets; + stats->rx_bytes = rx.rx_bytes; + stats->rx_errors = rx.rx_errors; + stats->rx_dropped = rx.rx_errors; + stats->multicast = rx.multicast; + } + return stats; +} + static void macvlan_ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *drvinfo) { @@ -404,6 +529,7 @@ static const struct ethtool_ops macvlan_ethtool_ops = { static const struct net_device_ops macvlan_netdev_ops = { .ndo_init = macvlan_init, + .ndo_uninit = macvlan_uninit, .ndo_open = macvlan_open, .ndo_stop = macvlan_stop, .ndo_start_xmit = macvlan_start_xmit, @@ -411,6 +537,7 @@ static const struct net_device_ops macvlan_netdev_ops = { .ndo_change_rx_flags = macvlan_change_rx_flags, .ndo_set_mac_address = macvlan_set_mac_address, .ndo_set_multicast_list = macvlan_set_multicast_list, + .ndo_get_stats = macvlan_dev_get_stats, .ndo_validate_addr = eth_validate_addr, }; @@ -455,25 +582,6 @@ static void macvlan_port_destroy(struct net_device *dev) kfree(port); } -static void macvlan_transfer_operstate(struct net_device *dev) -{ - struct macvlan_dev *vlan = netdev_priv(dev); - const struct net_device *lowerdev = vlan->lowerdev; - - if (lowerdev->operstate == IF_OPER_DORMANT) - netif_dormant_on(dev); - else - netif_dormant_off(dev); - - if (netif_carrier_ok(lowerdev)) { - if (!netif_carrier_ok(dev)) - netif_carrier_on(dev); - } else { - if (netif_carrier_ok(dev)) - netif_carrier_off(dev); - } -} - static int macvlan_validate(struct nlattr *tb[], struct nlattr *data[]) { if (tb[IFLA_ADDRESS]) { @@ -482,6 +590,17 @@ static int macvlan_validate(struct nlattr *tb[], struct nlattr *data[]) if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) return -EADDRNOTAVAIL; } + + if (data && data[IFLA_MACVLAN_MODE]) { + switch (nla_get_u32(data[IFLA_MACVLAN_MODE])) { + case MACVLAN_MODE_PRIVATE: + case MACVLAN_MODE_VEPA: + case MACVLAN_MODE_BRIDGE: + break; + default: + return -EINVAL; + } + } return 0; } @@ -504,7 +623,7 @@ static int macvlan_get_tx_queues(struct net *net, return 0; } -static int macvlan_newlink(struct net_device *dev, +static int macvlan_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[], struct nlattr *data[]) { struct macvlan_dev *vlan = netdev_priv(dev); @@ -515,7 +634,7 @@ static int macvlan_newlink(struct net_device *dev, if (!tb[IFLA_LINK]) return -EINVAL; - lowerdev = __dev_get_by_index(dev_net(dev), nla_get_u32(tb[IFLA_LINK])); + lowerdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK])); if (lowerdev == NULL) return -ENODEV; @@ -546,27 +665,61 @@ static int macvlan_newlink(struct net_device *dev, vlan->dev = dev; vlan->port = port; + vlan->mode = MACVLAN_MODE_VEPA; + if (data && data[IFLA_MACVLAN_MODE]) + vlan->mode = nla_get_u32(data[IFLA_MACVLAN_MODE]); + err = register_netdevice(dev); if (err < 0) return err; list_add_tail(&vlan->list, &port->vlans); - macvlan_transfer_operstate(dev); + netif_stacked_transfer_operstate(lowerdev, dev); return 0; } -static void macvlan_dellink(struct net_device *dev) +static void macvlan_dellink(struct net_device *dev, struct list_head *head) { struct macvlan_dev *vlan = netdev_priv(dev); struct macvlan_port *port = vlan->port; list_del(&vlan->list); - unregister_netdevice(dev); + unregister_netdevice_queue(dev, head); if (list_empty(&port->vlans)) macvlan_port_destroy(port->dev); } +static int macvlan_changelink(struct net_device *dev, + struct nlattr *tb[], struct nlattr *data[]) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + if (data && data[IFLA_MACVLAN_MODE]) + vlan->mode = nla_get_u32(data[IFLA_MACVLAN_MODE]); + return 0; +} + +static size_t macvlan_get_size(const struct net_device *dev) +{ + return nla_total_size(4); +} + +static int macvlan_fill_info(struct sk_buff *skb, + const struct net_device *dev) +{ + struct macvlan_dev *vlan = netdev_priv(dev); + + NLA_PUT_U32(skb, IFLA_MACVLAN_MODE, vlan->mode); + return 0; + +nla_put_failure: + return -EMSGSIZE; +} + +static const struct nla_policy macvlan_policy[IFLA_MACVLAN_MAX + 1] = { + [IFLA_MACVLAN_MODE] = { .type = NLA_U32 }, +}; + static struct rtnl_link_ops macvlan_link_ops __read_mostly = { .kind = "macvlan", .priv_size = sizeof(struct macvlan_dev), @@ -575,6 +728,11 @@ static struct rtnl_link_ops macvlan_link_ops __read_mostly = { .validate = macvlan_validate, .newlink = macvlan_newlink, .dellink = macvlan_dellink, + .maxtype = IFLA_MACVLAN_MAX, + .policy = macvlan_policy, + .changelink = macvlan_changelink, + .get_size = macvlan_get_size, + .fill_info = macvlan_fill_info, }; static int macvlan_device_event(struct notifier_block *unused, @@ -591,17 +749,19 @@ static int macvlan_device_event(struct notifier_block *unused, switch (event) { case NETDEV_CHANGE: list_for_each_entry(vlan, &port->vlans, list) - macvlan_transfer_operstate(vlan->dev); + netif_stacked_transfer_operstate(vlan->lowerdev, + vlan->dev); break; case NETDEV_FEAT_CHANGE: list_for_each_entry(vlan, &port->vlans, list) { vlan->dev->features = dev->features & MACVLAN_FEATURES; + vlan->dev->gso_max_size = dev->gso_max_size; netdev_features_change(vlan->dev); } break; case NETDEV_UNREGISTER: list_for_each_entry_safe(vlan, next, &port->vlans, list) - macvlan_dellink(vlan->dev); + macvlan_dellink(vlan->dev, NULL); break; } return NOTIFY_DONE; |