diff options
Diffstat (limited to 'drivers/net/veth.c')
| -rw-r--r-- | drivers/net/veth.c | 432 |
1 files changed, 207 insertions, 225 deletions
diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 31cd817f33f..b4a10bcb66a 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -8,36 +8,35 @@ * */ -#include <linux/list.h> #include <linux/netdevice.h> +#include <linux/slab.h> #include <linux/ethtool.h> #include <linux/etherdevice.h> +#include <linux/u64_stats_sync.h> +#include <net/rtnetlink.h> #include <net/dst.h> #include <net/xfrm.h> #include <linux/veth.h> +#include <linux/module.h> #define DRV_NAME "veth" #define DRV_VERSION "1.0" -struct veth_net_stats { - unsigned long rx_packets; - unsigned long tx_packets; - unsigned long rx_bytes; - unsigned long tx_bytes; - unsigned long tx_dropped; +#define MIN_MTU 68 /* Min L3 MTU */ +#define MAX_MTU 65535 /* Max L3 MTU (arbitrary) */ + +struct pcpu_vstats { + u64 packets; + u64 bytes; + struct u64_stats_sync syncp; }; struct veth_priv { - struct net_device *peer; - struct net_device *dev; - struct list_head list; - struct veth_net_stats *stats; - unsigned ip_summed; + struct net_device __rcu *peer; + atomic64_t dropped; }; -static LIST_HEAD(veth_list); - /* * ethtool interface */ @@ -52,7 +51,7 @@ static int veth_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) { cmd->supported = 0; cmd->advertising = 0; - cmd->speed = SPEED_10000; + ethtool_cmd_speed_set(cmd, SPEED_10000); cmd->duplex = DUPLEX_FULL; cmd->port = PORT_TP; cmd->phy_address = 0; @@ -65,9 +64,8 @@ static int veth_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) static void veth_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { - strcpy(info->driver, DRV_NAME); - strcpy(info->version, DRV_VERSION); - strcpy(info->fw_version, "N/A"); + strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); + strlcpy(info->version, DRV_VERSION, sizeof(info->version)); } static void veth_get_strings(struct net_device *dev, u32 stringset, u8 *buf) @@ -92,231 +90,203 @@ static int veth_get_sset_count(struct net_device *dev, int sset) static void veth_get_ethtool_stats(struct net_device *dev, struct ethtool_stats *stats, u64 *data) { - struct veth_priv *priv; - - priv = netdev_priv(dev); - data[0] = priv->peer->ifindex; -} - -static u32 veth_get_rx_csum(struct net_device *dev) -{ - struct veth_priv *priv; - - priv = netdev_priv(dev); - return priv->ip_summed == CHECKSUM_UNNECESSARY; -} - -static int veth_set_rx_csum(struct net_device *dev, u32 data) -{ - struct veth_priv *priv; - - priv = netdev_priv(dev); - priv->ip_summed = data ? CHECKSUM_UNNECESSARY : CHECKSUM_NONE; - return 0; -} + struct veth_priv *priv = netdev_priv(dev); + struct net_device *peer = rtnl_dereference(priv->peer); -static u32 veth_get_tx_csum(struct net_device *dev) -{ - return (dev->features & NETIF_F_NO_CSUM) != 0; + data[0] = peer ? peer->ifindex : 0; } -static int veth_set_tx_csum(struct net_device *dev, u32 data) -{ - if (data) - dev->features |= NETIF_F_NO_CSUM; - else - dev->features &= ~NETIF_F_NO_CSUM; - return 0; -} - -static struct ethtool_ops veth_ethtool_ops = { +static const struct ethtool_ops veth_ethtool_ops = { .get_settings = veth_get_settings, .get_drvinfo = veth_get_drvinfo, .get_link = ethtool_op_get_link, - .get_rx_csum = veth_get_rx_csum, - .set_rx_csum = veth_set_rx_csum, - .get_tx_csum = veth_get_tx_csum, - .set_tx_csum = veth_set_tx_csum, - .get_sg = ethtool_op_get_sg, - .set_sg = ethtool_op_set_sg, .get_strings = veth_get_strings, .get_sset_count = veth_get_sset_count, .get_ethtool_stats = veth_get_ethtool_stats, }; -/* - * xmit - */ - -static int veth_xmit(struct sk_buff *skb, struct net_device *dev) +static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev) { - struct net_device *rcv = NULL; - struct veth_priv *priv, *rcv_priv; - struct veth_net_stats *stats; - int length, cpu; - - skb_orphan(skb); - - priv = netdev_priv(dev); - rcv = priv->peer; - rcv_priv = netdev_priv(rcv); - - cpu = smp_processor_id(); - stats = per_cpu_ptr(priv->stats, cpu); - - if (!(rcv->flags & IFF_UP)) - goto outf; - - skb->pkt_type = PACKET_HOST; - skb->protocol = eth_type_trans(skb, rcv); - if (dev->features & NETIF_F_NO_CSUM) - skb->ip_summed = rcv_priv->ip_summed; - - dst_release(skb->dst); - skb->dst = NULL; - skb->mark = 0; - secpath_reset(skb); - nf_reset(skb); - - length = skb->len; - - stats->tx_bytes += length; - stats->tx_packets++; - - stats = per_cpu_ptr(rcv_priv->stats, cpu); - stats->rx_bytes += length; - stats->rx_packets++; + struct veth_priv *priv = netdev_priv(dev); + struct net_device *rcv; + int length = skb->len; + + rcu_read_lock(); + rcv = rcu_dereference(priv->peer); + if (unlikely(!rcv)) { + kfree_skb(skb); + goto drop; + } + /* don't change ip_summed == CHECKSUM_PARTIAL, as that + * will cause bad checksum on forwarded packets + */ + if (skb->ip_summed == CHECKSUM_NONE && + rcv->features & NETIF_F_RXCSUM) + skb->ip_summed = CHECKSUM_UNNECESSARY; - netif_rx(skb); - return 0; + if (likely(dev_forward_skb(rcv, skb) == NET_RX_SUCCESS)) { + struct pcpu_vstats *stats = this_cpu_ptr(dev->vstats); -outf: - kfree_skb(skb); - stats->tx_dropped++; - return 0; + u64_stats_update_begin(&stats->syncp); + stats->bytes += length; + stats->packets++; + u64_stats_update_end(&stats->syncp); + } else { +drop: + atomic64_inc(&priv->dropped); + } + rcu_read_unlock(); + return NETDEV_TX_OK; } /* * general routines */ -static struct net_device_stats *veth_get_stats(struct net_device *dev) +static u64 veth_stats_one(struct pcpu_vstats *result, struct net_device *dev) { - struct veth_priv *priv; - struct net_device_stats *dev_stats; + struct veth_priv *priv = netdev_priv(dev); int cpu; - struct veth_net_stats *stats; - priv = netdev_priv(dev); - dev_stats = &dev->stats; - - dev_stats->rx_packets = 0; - dev_stats->tx_packets = 0; - dev_stats->rx_bytes = 0; - dev_stats->tx_bytes = 0; - dev_stats->tx_dropped = 0; - - for_each_online_cpu(cpu) { - stats = per_cpu_ptr(priv->stats, cpu); - - dev_stats->rx_packets += stats->rx_packets; - dev_stats->tx_packets += stats->tx_packets; - dev_stats->rx_bytes += stats->rx_bytes; - dev_stats->tx_bytes += stats->tx_bytes; - dev_stats->tx_dropped += stats->tx_dropped; + result->packets = 0; + result->bytes = 0; + for_each_possible_cpu(cpu) { + struct pcpu_vstats *stats = per_cpu_ptr(dev->vstats, cpu); + u64 packets, bytes; + unsigned int start; + + do { + start = u64_stats_fetch_begin_irq(&stats->syncp); + packets = stats->packets; + bytes = stats->bytes; + } while (u64_stats_fetch_retry_irq(&stats->syncp, start)); + result->packets += packets; + result->bytes += bytes; + } + return atomic64_read(&priv->dropped); +} + +static struct rtnl_link_stats64 *veth_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *tot) +{ + struct veth_priv *priv = netdev_priv(dev); + struct net_device *peer; + struct pcpu_vstats one; + + tot->tx_dropped = veth_stats_one(&one, dev); + tot->tx_bytes = one.bytes; + tot->tx_packets = one.packets; + + rcu_read_lock(); + peer = rcu_dereference(priv->peer); + if (peer) { + tot->rx_dropped = veth_stats_one(&one, peer); + tot->rx_bytes = one.bytes; + tot->rx_packets = one.packets; } + rcu_read_unlock(); - return dev_stats; + return tot; +} + +/* fake multicast ability */ +static void veth_set_multicast_list(struct net_device *dev) +{ } static int veth_open(struct net_device *dev) { - struct veth_priv *priv; + struct veth_priv *priv = netdev_priv(dev); + struct net_device *peer = rtnl_dereference(priv->peer); - priv = netdev_priv(dev); - if (priv->peer == NULL) + if (!peer) return -ENOTCONN; - if (priv->peer->flags & IFF_UP) { + if (peer->flags & IFF_UP) { netif_carrier_on(dev); - netif_carrier_on(priv->peer); + netif_carrier_on(peer); } return 0; } -static int veth_dev_init(struct net_device *dev) +static int veth_close(struct net_device *dev) { - struct veth_net_stats *stats; - struct veth_priv *priv; + struct veth_priv *priv = netdev_priv(dev); + struct net_device *peer = rtnl_dereference(priv->peer); - stats = alloc_percpu(struct veth_net_stats); - if (stats == NULL) - return -ENOMEM; + netif_carrier_off(dev); + if (peer) + netif_carrier_off(peer); - priv = netdev_priv(dev); - priv->stats = stats; return 0; } -static void veth_dev_free(struct net_device *dev) +static int is_valid_veth_mtu(int new_mtu) { - struct veth_priv *priv; - - priv = netdev_priv(dev); - free_percpu(priv->stats); - free_netdev(dev); + return new_mtu >= MIN_MTU && new_mtu <= MAX_MTU; } -static void veth_setup(struct net_device *dev) +static int veth_change_mtu(struct net_device *dev, int new_mtu) { - ether_setup(dev); + if (!is_valid_veth_mtu(new_mtu)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} - dev->hard_start_xmit = veth_xmit; - dev->get_stats = veth_get_stats; - dev->open = veth_open; - dev->ethtool_ops = &veth_ethtool_ops; - dev->features |= NETIF_F_LLTX; - dev->init = veth_dev_init; - dev->destructor = veth_dev_free; +static int veth_dev_init(struct net_device *dev) +{ + dev->vstats = netdev_alloc_pcpu_stats(struct pcpu_vstats); + if (!dev->vstats) + return -ENOMEM; + return 0; } -static void veth_change_state(struct net_device *dev) +static void veth_dev_free(struct net_device *dev) { - struct net_device *peer; - struct veth_priv *priv; + free_percpu(dev->vstats); + free_netdev(dev); +} - priv = netdev_priv(dev); - peer = priv->peer; +static const struct net_device_ops veth_netdev_ops = { + .ndo_init = veth_dev_init, + .ndo_open = veth_open, + .ndo_stop = veth_close, + .ndo_start_xmit = veth_xmit, + .ndo_change_mtu = veth_change_mtu, + .ndo_get_stats64 = veth_get_stats64, + .ndo_set_rx_mode = veth_set_multicast_list, + .ndo_set_mac_address = eth_mac_addr, +}; - if (netif_carrier_ok(peer)) { - if (!netif_carrier_ok(dev)) - netif_carrier_on(dev); - } else { - if (netif_carrier_ok(dev)) - netif_carrier_off(dev); - } -} +#define VETH_FEATURES (NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_ALL_TSO | \ + NETIF_F_HW_CSUM | NETIF_F_RXCSUM | NETIF_F_HIGHDMA | \ + NETIF_F_GSO_GRE | NETIF_F_GSO_UDP_TUNNEL | \ + NETIF_F_GSO_IPIP | NETIF_F_GSO_SIT | NETIF_F_UFO | \ + NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX | \ + NETIF_F_HW_VLAN_STAG_TX | NETIF_F_HW_VLAN_STAG_RX ) -static int veth_device_event(struct notifier_block *unused, - unsigned long event, void *ptr) +static void veth_setup(struct net_device *dev) { - struct net_device *dev = ptr; + ether_setup(dev); - if (dev->open != veth_open) - goto out; + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; - switch (event) { - case NETDEV_CHANGE: - veth_change_state(dev); - break; - } -out: - return NOTIFY_DONE; -} + dev->netdev_ops = &veth_netdev_ops; + dev->ethtool_ops = &veth_ethtool_ops; + dev->features |= NETIF_F_LLTX; + dev->features |= VETH_FEATURES; + dev->vlan_features = dev->features & + ~(NETIF_F_HW_VLAN_CTAG_TX | + NETIF_F_HW_VLAN_STAG_TX | + NETIF_F_HW_VLAN_CTAG_RX | + NETIF_F_HW_VLAN_STAG_RX); + dev->destructor = veth_dev_free; -static struct notifier_block veth_notifier_block __read_mostly = { - .notifier_call = veth_device_event, -}; + dev->hw_features = VETH_FEATURES; + dev->hw_enc_features = VETH_FEATURES; +} /* * netlink interface @@ -330,12 +300,16 @@ static int veth_validate(struct nlattr *tb[], struct nlattr *data[]) if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) return -EADDRNOTAVAIL; } + if (tb[IFLA_MTU]) { + if (!is_valid_veth_mtu(nla_get_u32(tb[IFLA_MTU]))) + return -EINVAL; + } return 0; } static struct rtnl_link_ops veth_link_ops; -static int veth_newlink(struct net_device *dev, +static int veth_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[], struct nlattr *data[]) { int err; @@ -343,22 +317,20 @@ static int veth_newlink(struct net_device *dev, struct veth_priv *priv; char ifname[IFNAMSIZ]; struct nlattr *peer_tb[IFLA_MAX + 1], **tbp; + struct ifinfomsg *ifmp; + struct net *net; /* * create and register peer first - * - * struct ifinfomsg is at the head of VETH_INFO_PEER, but we - * skip it since no info from it is useful yet */ - if (data != NULL && data[VETH_INFO_PEER] != NULL) { struct nlattr *nla_peer; nla_peer = data[VETH_INFO_PEER]; - err = nla_parse(peer_tb, IFLA_MAX, - nla_data(nla_peer) + sizeof(struct ifinfomsg), - nla_len(nla_peer) - sizeof(struct ifinfomsg), - ifla_policy); + ifmp = nla_data(nla_peer); + err = rtnl_nla_parse_ifla(peer_tb, + nla_data(nla_peer) + sizeof(struct ifinfomsg), + nla_len(nla_peer) - sizeof(struct ifinfomsg)); if (err < 0) return err; @@ -367,27 +339,44 @@ static int veth_newlink(struct net_device *dev, return err; tbp = peer_tb; - } else + } else { + ifmp = NULL; tbp = tb; + } if (tbp[IFLA_IFNAME]) nla_strlcpy(ifname, tbp[IFLA_IFNAME], IFNAMSIZ); else snprintf(ifname, IFNAMSIZ, DRV_NAME "%%d"); - peer = rtnl_create_link(dev_net(dev), ifname, &veth_link_ops, tbp); - if (IS_ERR(peer)) + net = rtnl_link_get_net(src_net, tbp); + if (IS_ERR(net)) + return PTR_ERR(net); + + peer = rtnl_create_link(net, ifname, &veth_link_ops, tbp); + if (IS_ERR(peer)) { + put_net(net); return PTR_ERR(peer); + } if (tbp[IFLA_ADDRESS] == NULL) - random_ether_addr(peer->dev_addr); + eth_hw_addr_random(peer); + + if (ifmp && (dev->ifindex != 0)) + peer->ifindex = ifmp->ifi_index; err = register_netdevice(peer); + put_net(net); + net = NULL; if (err < 0) goto err_register_peer; netif_carrier_off(peer); + err = rtnl_configure_link(peer, ifmp); + if (err < 0) + goto err_configure_peer; + /* * register dev last * @@ -396,19 +385,13 @@ static int veth_newlink(struct net_device *dev, */ if (tb[IFLA_ADDRESS] == NULL) - random_ether_addr(dev->dev_addr); + eth_hw_addr_random(dev); if (tb[IFLA_IFNAME]) nla_strlcpy(dev->name, tb[IFLA_IFNAME], IFNAMSIZ); else snprintf(dev->name, IFNAMSIZ, DRV_NAME "%%d"); - if (strchr(dev->name, '%')) { - err = dev_alloc_name(dev, dev->name); - if (err < 0) - goto err_alloc_name; - } - err = register_netdevice(dev); if (err < 0) goto err_register_dev; @@ -420,19 +403,15 @@ static int veth_newlink(struct net_device *dev, */ priv = netdev_priv(dev); - priv->dev = dev; - priv->peer = peer; - list_add(&priv->list, &veth_list); + rcu_assign_pointer(priv->peer, peer); priv = netdev_priv(peer); - priv->dev = peer; - priv->peer = dev; - INIT_LIST_HEAD(&priv->list); + rcu_assign_pointer(priv->peer, dev); return 0; err_register_dev: /* nothing to do */ -err_alloc_name: +err_configure_peer: unregister_netdevice(peer); return err; @@ -441,26 +420,31 @@ err_register_peer: return err; } -static void veth_dellink(struct net_device *dev) +static void veth_dellink(struct net_device *dev, struct list_head *head) { struct veth_priv *priv; struct net_device *peer; priv = netdev_priv(dev); - peer = priv->peer; - - if (!list_empty(&priv->list)) - list_del(&priv->list); + peer = rtnl_dereference(priv->peer); - priv = netdev_priv(peer); - if (!list_empty(&priv->list)) - list_del(&priv->list); + /* Note : dellink() is called from default_device_exit_batch(), + * before a rcu_synchronize() point. The devices are guaranteed + * not being freed before one RCU grace period. + */ + RCU_INIT_POINTER(priv->peer, NULL); + unregister_netdevice_queue(dev, head); - unregister_netdevice(dev); - unregister_netdevice(peer); + if (peer) { + priv = netdev_priv(peer); + RCU_INIT_POINTER(priv->peer, NULL); + unregister_netdevice_queue(peer, head); + } } -static const struct nla_policy veth_policy[VETH_INFO_MAX + 1]; +static const struct nla_policy veth_policy[VETH_INFO_MAX + 1] = { + [VETH_INFO_PEER] = { .len = sizeof(struct ifinfomsg) }, +}; static struct rtnl_link_ops veth_link_ops = { .kind = DRV_NAME, @@ -479,14 +463,12 @@ static struct rtnl_link_ops veth_link_ops = { static __init int veth_init(void) { - register_netdevice_notifier(&veth_notifier_block); return rtnl_link_register(&veth_link_ops); } static __exit void veth_exit(void) { rtnl_link_unregister(&veth_link_ops); - unregister_netdevice_notifier(&veth_notifier_block); } module_init(veth_init); |
