netns xfrm: fix "ip xfrm state|policy count" misreport
[safe/jmp/linux-2.6] / drivers / net / veth.c
index ca1c689..3a15de5 100644 (file)
@@ -8,36 +8,36 @@
  *
  */
 
-#include <linux/list.h>
 #include <linux/netdevice.h>
 #include <linux/ethtool.h>
 #include <linux/etherdevice.h>
 
 #include <net/dst.h>
 #include <net/xfrm.h>
-#include <net/veth.h>
+#include <linux/veth.h>
 
 #define DRV_NAME       "veth"
 #define DRV_VERSION    "1.0"
 
+#define MIN_MTU 68             /* Min L3 MTU */
+#define MAX_MTU 65535          /* Max L3 MTU (arbitrary) */
+#define MTU_PAD (ETH_HLEN + 4)  /* Max difference between L2 and L3 size MTU */
+
 struct veth_net_stats {
        unsigned long   rx_packets;
        unsigned long   tx_packets;
        unsigned long   rx_bytes;
        unsigned long   tx_bytes;
        unsigned long   tx_dropped;
+       unsigned long   rx_dropped;
 };
 
 struct veth_priv {
        struct net_device *peer;
-       struct net_device *dev;
-       struct list_head list;
        struct veth_net_stats *stats;
        unsigned ip_summed;
 };
 
-static LIST_HEAD(veth_list);
-
 /*
  * ethtool interface
  */
@@ -79,9 +79,14 @@ static void veth_get_strings(struct net_device *dev, u32 stringset, u8 *buf)
        }
 }
 
-static int veth_get_stats_count(struct net_device *dev)
+static int veth_get_sset_count(struct net_device *dev, int sset)
 {
-       return ARRAY_SIZE(ethtool_stats_keys);
+       switch (sset) {
+       case ETH_SS_STATS:
+               return ARRAY_SIZE(ethtool_stats_keys);
+       default:
+               return -EOPNOTSUPP;
+       }
 }
 
 static void veth_get_ethtool_stats(struct net_device *dev,
@@ -124,7 +129,7 @@ static int veth_set_tx_csum(struct net_device *dev, u32 data)
        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,
@@ -135,7 +140,7 @@ static struct ethtool_ops veth_ethtool_ops = {
        .get_sg                 = ethtool_op_get_sg,
        .set_sg                 = ethtool_op_set_sg,
        .get_strings            = veth_get_strings,
-       .get_stats_count        = veth_get_stats_count,
+       .get_sset_count         = veth_get_sset_count,
        .get_ethtool_stats      = veth_get_ethtool_stats,
 };
 
@@ -143,52 +148,47 @@ static struct ethtool_ops veth_ethtool_ops = {
  * 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);
+       struct veth_net_stats *stats, *rcv_stats;
+       int length;
 
        priv = netdev_priv(dev);
        rcv = priv->peer;
        rcv_priv = netdev_priv(rcv);
 
-       cpu = smp_processor_id();
-       stats = per_cpu_ptr(priv->stats, cpu);
+       stats = this_cpu_ptr(priv->stats);
+       rcv_stats = this_cpu_ptr(rcv_priv->stats);
 
        if (!(rcv->flags & IFF_UP))
-               goto outf;
+               goto tx_drop;
 
-       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;
+       length = skb->len + ETH_HLEN;
+       if (dev_forward_skb(rcv, skb) != NET_RX_SUCCESS)
+               goto rx_drop;
 
        stats->tx_bytes += length;
        stats->tx_packets++;
 
-       stats = per_cpu_ptr(rcv_priv->stats, cpu);
-       stats->rx_bytes += length;
-       stats->rx_packets++;
+       rcv_stats->rx_bytes += length;
+       rcv_stats->rx_packets++;
 
-       netif_rx(skb);
-       return 0;
+       return NETDEV_TX_OK;
 
-outf:
+tx_drop:
        kfree_skb(skb);
        stats->tx_dropped++;
-       return 0;
+       return NETDEV_TX_OK;
+
+rx_drop:
+       kfree_skb(skb);
+       rcv_stats->rx_dropped++;
+       return NETDEV_TX_OK;
 }
 
 /*
@@ -198,30 +198,29 @@ outf:
 static struct net_device_stats *veth_get_stats(struct net_device *dev)
 {
        struct veth_priv *priv;
-       struct net_device_stats *dev_stats;
        int cpu;
-       struct veth_net_stats *stats;
+       struct veth_net_stats *stats, total = {0};
 
        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) {
+       for_each_possible_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;
+               total.rx_packets += stats->rx_packets;
+               total.tx_packets += stats->tx_packets;
+               total.rx_bytes   += stats->rx_bytes;
+               total.tx_bytes   += stats->tx_bytes;
+               total.tx_dropped += stats->tx_dropped;
+               total.rx_dropped += stats->rx_dropped;
        }
-
-       return dev_stats;
+       dev->stats.rx_packets = total.rx_packets;
+       dev->stats.tx_packets = total.tx_packets;
+       dev->stats.rx_bytes   = total.rx_bytes;
+       dev->stats.tx_bytes   = total.tx_bytes;
+       dev->stats.tx_dropped = total.tx_dropped;
+       dev->stats.rx_dropped = total.rx_dropped;
+
+       return &dev->stats;
 }
 
 static int veth_open(struct net_device *dev)
@@ -241,13 +240,24 @@ static int veth_open(struct net_device *dev)
 
 static int veth_close(struct net_device *dev)
 {
-       struct veth_priv *priv;
+       struct veth_priv *priv = netdev_priv(dev);
+
+       netif_carrier_off(dev);
+       netif_carrier_off(priv->peer);
 
-       if (netif_carrier_ok(dev)) {
-               priv = netdev_priv(dev);
-               netif_carrier_off(dev);
-               netif_carrier_off(priv->peer);
-       }
+       return 0;
+}
+
+static int is_valid_veth_mtu(int new_mtu)
+{
+       return (new_mtu >= MIN_MTU && new_mtu <= MAX_MTU);
+}
+
+static int veth_change_mtu(struct net_device *dev, int new_mtu)
+{
+       if (!is_valid_veth_mtu(new_mtu))
+               return -EINVAL;
+       dev->mtu = new_mtu;
        return 0;
 }
 
@@ -274,17 +284,23 @@ static void veth_dev_free(struct net_device *dev)
        free_netdev(dev);
 }
 
+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_stats       = veth_get_stats,
+       .ndo_set_mac_address = eth_mac_addr,
+};
+
 static void veth_setup(struct net_device *dev)
 {
        ether_setup(dev);
 
-       dev->hard_start_xmit = veth_xmit;
-       dev->get_stats = veth_get_stats;
-       dev->open = veth_open;
-       dev->stop = veth_close;
+       dev->netdev_ops = &veth_netdev_ops;
        dev->ethtool_ops = &veth_ethtool_ops;
        dev->features |= NETIF_F_LLTX;
-       dev->init = veth_dev_init;
        dev->destructor = veth_dev_free;
 }
 
@@ -300,12 +316,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;
@@ -313,6 +333,7 @@ static int veth_newlink(struct net_device *dev,
        struct veth_priv *priv;
        char ifname[IFNAMSIZ];
        struct nlattr *peer_tb[IFLA_MAX + 1], **tbp;
+       struct net *net;
 
        /*
         * create and register peer first
@@ -345,14 +366,22 @@ static int veth_newlink(struct net_device *dev,
        else
                snprintf(ifname, IFNAMSIZ, DRV_NAME "%%d");
 
-       peer = rtnl_create_link(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(src_net, 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);
 
        err = register_netdevice(peer);
+       put_net(net);
+       net = NULL;
        if (err < 0)
                goto err_register_peer;
 
@@ -390,14 +419,10 @@ static int veth_newlink(struct net_device *dev,
         */
 
        priv = netdev_priv(dev);
-       priv->dev = dev;
        priv->peer = peer;
-       list_add(&priv->list, &veth_list);
 
        priv = netdev_priv(peer);
-       priv->dev = peer;
        priv->peer = dev;
-       INIT_LIST_HEAD(&priv->list);
        return 0;
 
 err_register_dev:
@@ -411,7 +436,7 @@ 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;
@@ -419,15 +444,8 @@ static void veth_dellink(struct net_device *dev)
        priv = netdev_priv(dev);
        peer = priv->peer;
 
-       if (!list_empty(&priv->list))
-               list_del(&priv->list);
-
-       priv = netdev_priv(peer);
-       if (!list_empty(&priv->list))
-               list_del(&priv->list);
-
-       unregister_netdevice(dev);
-       unregister_netdevice(peer);
+       unregister_netdevice_queue(dev, head);
+       unregister_netdevice_queue(peer, head);
 }
 
 static const struct nla_policy veth_policy[VETH_INFO_MAX + 1];
@@ -454,19 +472,7 @@ static __init int veth_init(void)
 
 static __exit void veth_exit(void)
 {
-       struct veth_priv *priv, *next;
-
-       rtnl_lock();
-       /*
-        * cannot trust __rtnl_link_unregister() to unregister all
-        * devices, as each ->dellink call will remove two devices
-        * from the list at once.
-        */
-       list_for_each_entry_safe(priv, next, &veth_list, list)
-               veth_dellink(priv->dev);
-
-       __rtnl_link_unregister(&veth_link_ops);
-       rtnl_unlock();
+       rtnl_link_unregister(&veth_link_ops);
 }
 
 module_init(veth_init);