aboutsummaryrefslogtreecommitdiffstats
path: root/net/core
diff options
context:
space:
mode:
Diffstat (limited to 'net/core')
-rw-r--r--net/core/dev.c231
-rw-r--r--net/core/dev_addr_lists.c12
-rw-r--r--net/core/drop_monitor.c12
-rw-r--r--net/core/dst.c57
-rw-r--r--net/core/ethtool.c196
-rw-r--r--net/core/fib_rules.c4
-rw-r--r--net/core/filter.c69
-rw-r--r--net/core/gen_estimator.c9
-rw-r--r--net/core/net-sysfs.c60
-rw-r--r--net/core/net_namespace.c22
-rw-r--r--net/core/netpoll.c28
-rw-r--r--net/core/pktgen.c222
-rw-r--r--net/core/rtnetlink.c20
-rw-r--r--net/core/skbuff.c4
-rw-r--r--net/core/sysctl_net_core.c9
-rw-r--r--net/core/utils.c24
16 files changed, 481 insertions, 498 deletions
diff --git a/net/core/dev.c b/net/core/dev.c
index 856b6ee..c7e305d 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -948,7 +948,7 @@ int dev_alloc_name(struct net_device *dev, const char *name)
}
EXPORT_SYMBOL(dev_alloc_name);
-static int dev_get_valid_name(struct net_device *dev, const char *name, bool fmt)
+static int dev_get_valid_name(struct net_device *dev, const char *name)
{
struct net *net;
@@ -958,7 +958,7 @@ static int dev_get_valid_name(struct net_device *dev, const char *name, bool fmt
if (!dev_valid_name(name))
return -EINVAL;
- if (fmt && strchr(name, '%'))
+ if (strchr(name, '%'))
return dev_alloc_name(dev, name);
else if (__dev_get_by_name(net, name))
return -EEXIST;
@@ -995,7 +995,7 @@ int dev_change_name(struct net_device *dev, const char *newname)
memcpy(oldname, dev->name, IFNAMSIZ);
- err = dev_get_valid_name(dev, newname, 1);
+ err = dev_get_valid_name(dev, newname);
if (err < 0)
return err;
@@ -1007,7 +1007,7 @@ rollback:
}
write_lock_bh(&dev_base_lock);
- hlist_del(&dev->name_hlist);
+ hlist_del_rcu(&dev->name_hlist);
write_unlock_bh(&dev_base_lock);
synchronize_rcu();
@@ -1284,11 +1284,13 @@ static int dev_close_many(struct list_head *head)
*/
int dev_close(struct net_device *dev)
{
- LIST_HEAD(single);
+ if (dev->flags & IFF_UP) {
+ LIST_HEAD(single);
- list_add(&dev->unreg_list, &single);
- dev_close_many(&single);
- list_del(&single);
+ list_add(&dev->unreg_list, &single);
+ dev_close_many(&single);
+ list_del(&single);
+ }
return 0;
}
EXPORT_SYMBOL(dev_close);
@@ -1306,6 +1308,13 @@ void dev_disable_lro(struct net_device *dev)
{
u32 flags;
+ /*
+ * If we're trying to disable lro on a vlan device
+ * use the underlying physical device instead
+ */
+ if (is_vlan_dev(dev))
+ dev = vlan_dev_real_dev(dev);
+
if (dev->ethtool_ops && dev->ethtool_ops->get_flags)
flags = dev->ethtool_ops->get_flags(dev);
else
@@ -1315,7 +1324,8 @@ void dev_disable_lro(struct net_device *dev)
return;
__ethtool_set_flags(dev, flags & ~ETH_FLAG_LRO);
- WARN_ON(dev->features & NETIF_F_LRO);
+ if (unlikely(dev->features & NETIF_F_LRO))
+ netdev_WARN(dev, "failed to disable LRO!\n");
}
EXPORT_SYMBOL(dev_disable_lro);
@@ -2502,8 +2512,8 @@ static inline void ____napi_schedule(struct softnet_data *sd,
__u32 __skb_get_rxhash(struct sk_buff *skb)
{
int nhoff, hash = 0, poff;
- struct ipv6hdr *ip6;
- struct iphdr *ip;
+ const struct ipv6hdr *ip6;
+ const struct iphdr *ip;
u8 ip_proto;
u32 addr1, addr2, ihl;
union {
@@ -2518,7 +2528,7 @@ __u32 __skb_get_rxhash(struct sk_buff *skb)
if (!pskb_may_pull(skb, sizeof(*ip) + nhoff))
goto done;
- ip = (struct iphdr *) (skb->data + nhoff);
+ ip = (const struct iphdr *) (skb->data + nhoff);
if (ip->frag_off & htons(IP_MF | IP_OFFSET))
ip_proto = 0;
else
@@ -2531,7 +2541,7 @@ __u32 __skb_get_rxhash(struct sk_buff *skb)
if (!pskb_may_pull(skb, sizeof(*ip6) + nhoff))
goto done;
- ip6 = (struct ipv6hdr *) (skb->data + nhoff);
+ ip6 = (const struct ipv6hdr *) (skb->data + nhoff);
ip_proto = ip6->nexthdr;
addr1 = (__force u32) ip6->saddr.s6_addr32[3];
addr2 = (__force u32) ip6->daddr.s6_addr32[3];
@@ -3076,25 +3086,6 @@ void netdev_rx_handler_unregister(struct net_device *dev)
}
EXPORT_SYMBOL_GPL(netdev_rx_handler_unregister);
-static void vlan_on_bond_hook(struct sk_buff *skb)
-{
- /*
- * Make sure ARP frames received on VLAN interfaces stacked on
- * bonding interfaces still make their way to any base bonding
- * device that may have registered for a specific ptype.
- */
- if (skb->dev->priv_flags & IFF_802_1Q_VLAN &&
- vlan_dev_real_dev(skb->dev)->priv_flags & IFF_BONDING &&
- skb->protocol == htons(ETH_P_ARP)) {
- struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
-
- if (!skb2)
- return;
- skb2->dev = vlan_dev_real_dev(skb->dev);
- netif_rx(skb2);
- }
-}
-
static int __netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
@@ -3130,6 +3121,12 @@ another_round:
__this_cpu_inc(softnet_data.processed);
+ if (skb->protocol == cpu_to_be16(ETH_P_8021Q)) {
+ skb = vlan_untag(skb);
+ if (unlikely(!skb))
+ goto out;
+ }
+
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
@@ -3177,15 +3174,13 @@ ncls:
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
- if (vlan_hwaccel_do_receive(&skb)) {
+ if (vlan_do_receive(&skb)) {
ret = __netif_receive_skb(skb);
goto out;
} else if (unlikely(!skb))
goto out;
}
- vlan_on_bond_hook(skb);
-
/* deliver only exact match when indicated */
null_or_dev = deliver_exact ? skb->dev : NULL;
@@ -4306,10 +4301,8 @@ int netdev_set_master(struct net_device *slave, struct net_device *master)
slave->master = master;
- if (old) {
- synchronize_net();
+ if (old)
dev_put(old);
- }
return 0;
}
EXPORT_SYMBOL(netdev_set_master);
@@ -4510,6 +4503,30 @@ void dev_set_rx_mode(struct net_device *dev)
}
/**
+ * dev_ethtool_get_settings - call device's ethtool_ops::get_settings()
+ * @dev: device
+ * @cmd: memory area for ethtool_ops::get_settings() result
+ *
+ * The cmd arg is initialized properly (cleared and
+ * ethtool_cmd::cmd field set to ETHTOOL_GSET).
+ *
+ * Return device's ethtool_ops::get_settings() result value or
+ * -EOPNOTSUPP when device doesn't expose
+ * ethtool_ops::get_settings() operation.
+ */
+int dev_ethtool_get_settings(struct net_device *dev,
+ struct ethtool_cmd *cmd)
+{
+ if (!dev->ethtool_ops || !dev->ethtool_ops->get_settings)
+ return -EOPNOTSUPP;
+
+ memset(cmd, 0, sizeof(struct ethtool_cmd));
+ cmd->cmd = ETHTOOL_GSET;
+ return dev->ethtool_ops->get_settings(dev, cmd);
+}
+EXPORT_SYMBOL(dev_ethtool_get_settings);
+
+/**
* dev_get_flags - get flags reported to userspace
* @dev: device
*
@@ -5114,7 +5131,7 @@ static void rollback_registered_many(struct list_head *head)
list_del(&dev->unreg_list);
continue;
}
-
+ dev->dismantle = true;
BUG_ON(dev->reg_state != NETREG_REGISTERED);
}
@@ -5184,27 +5201,27 @@ u32 netdev_fix_features(struct net_device *dev, u32 features)
/* Fix illegal checksum combinations */
if ((features & NETIF_F_HW_CSUM) &&
(features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
- netdev_info(dev, "mixed HW and IP checksum settings.\n");
+ netdev_warn(dev, "mixed HW and IP checksum settings.\n");
features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);
}
if ((features & NETIF_F_NO_CSUM) &&
(features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
- netdev_info(dev, "mixed no checksumming and other settings.\n");
+ netdev_warn(dev, "mixed no checksumming and other settings.\n");
features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);
}
/* Fix illegal SG+CSUM combinations. */
if ((features & NETIF_F_SG) &&
!(features & NETIF_F_ALL_CSUM)) {
- netdev_info(dev,
- "Dropping NETIF_F_SG since no checksum feature.\n");
+ netdev_dbg(dev,
+ "Dropping NETIF_F_SG since no checksum feature.\n");
features &= ~NETIF_F_SG;
}
/* TSO requires that SG is present as well. */
if ((features & NETIF_F_ALL_TSO) && !(features & NETIF_F_SG)) {
- netdev_info(dev, "Dropping TSO features since no SG feature.\n");
+ netdev_dbg(dev, "Dropping TSO features since no SG feature.\n");
features &= ~NETIF_F_ALL_TSO;
}
@@ -5214,7 +5231,7 @@ u32 netdev_fix_features(struct net_device *dev, u32 features)
/* Software GSO depends on SG. */
if ((features & NETIF_F_GSO) && !(features & NETIF_F_SG)) {
- netdev_info(dev, "Dropping NETIF_F_GSO since no SG feature.\n");
+ netdev_dbg(dev, "Dropping NETIF_F_GSO since no SG feature.\n");
features &= ~NETIF_F_GSO;
}
@@ -5224,13 +5241,13 @@ u32 netdev_fix_features(struct net_device *dev, u32 features)
if (!((features & NETIF_F_GEN_CSUM) ||
(features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))
== (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
- netdev_info(dev,
+ netdev_dbg(dev,
"Dropping NETIF_F_UFO since no checksum offload features.\n");
features &= ~NETIF_F_UFO;
}
if (!(features & NETIF_F_SG)) {
- netdev_info(dev,
+ netdev_dbg(dev,
"Dropping NETIF_F_UFO since no NETIF_F_SG feature.\n");
features &= ~NETIF_F_UFO;
}
@@ -5240,11 +5257,13 @@ u32 netdev_fix_features(struct net_device *dev, u32 features)
}
EXPORT_SYMBOL(netdev_fix_features);
-void netdev_update_features(struct net_device *dev)
+int __netdev_update_features(struct net_device *dev)
{
u32 features;
int err = 0;
+ ASSERT_RTNL();
+
features = netdev_get_wanted_features(dev);
if (dev->netdev_ops->ndo_fix_features)
@@ -5254,24 +5273,60 @@ void netdev_update_features(struct net_device *dev)
features = netdev_fix_features(dev, features);
if (dev->features == features)
- return;
+ return 0;
- netdev_info(dev, "Features changed: 0x%08x -> 0x%08x\n",
+ netdev_dbg(dev, "Features changed: 0x%08x -> 0x%08x\n",
dev->features, features);
if (dev->netdev_ops->ndo_set_features)
err = dev->netdev_ops->ndo_set_features(dev, features);
- if (!err)
- dev->features = features;
- else if (err < 0)
+ if (unlikely(err < 0)) {
netdev_err(dev,
"set_features() failed (%d); wanted 0x%08x, left 0x%08x\n",
err, features, dev->features);
+ return -1;
+ }
+
+ if (!err)
+ dev->features = features;
+
+ return 1;
+}
+
+/**
+ * netdev_update_features - recalculate device features
+ * @dev: the device to check
+ *
+ * Recalculate dev->features set and send notifications if it
+ * has changed. Should be called after driver or hardware dependent
+ * conditions might have changed that influence the features.
+ */
+void netdev_update_features(struct net_device *dev)
+{
+ if (__netdev_update_features(dev))
+ netdev_features_change(dev);
}
EXPORT_SYMBOL(netdev_update_features);
/**
+ * netdev_change_features - recalculate device features
+ * @dev: the device to check
+ *
+ * Recalculate dev->features set and send notifications even
+ * if they have not changed. Should be called instead of
+ * netdev_update_features() if also dev->vlan_features might
+ * have changed to allow the changes to be propagated to stacked
+ * VLAN devices.
+ */
+void netdev_change_features(struct net_device *dev)
+{
+ __netdev_update_features(dev);
+ netdev_features_change(dev);
+}
+EXPORT_SYMBOL(netdev_change_features);
+
+/**
* netif_stacked_transfer_operstate - transfer operstate
* @rootdev: the root or lower level device to transfer state from
* @dev: the device to transfer operstate to
@@ -5387,6 +5442,10 @@ int register_netdevice(struct net_device *dev)
dev->iflink = -1;
+ ret = dev_get_valid_name(dev, dev->name);
+ if (ret < 0)
+ goto out;
+
/* Init, if this function is available */
if (dev->netdev_ops->ndo_init) {
ret = dev->netdev_ops->ndo_init(dev);
@@ -5397,10 +5456,6 @@ int register_netdevice(struct net_device *dev)
}
}
- ret = dev_get_valid_name(dev, dev->name, 0);
- if (ret)
- goto err_uninit;
-
dev->ifindex = dev_new_index(net);
if (dev->iflink == -1)
dev->iflink = dev->ifindex;
@@ -5412,10 +5467,12 @@ int register_netdevice(struct net_device *dev)
dev->features |= NETIF_F_SOFT_FEATURES;
dev->wanted_features = dev->features & dev->hw_features;
- /* Avoid warning from netdev_fix_features() for GSO without SG */
- if (!(dev->wanted_features & NETIF_F_SG)) {
- dev->wanted_features &= ~NETIF_F_GSO;
- dev->features &= ~NETIF_F_GSO;
+ /* Turn on no cache copy if HW is doing checksum */
+ dev->hw_features |= NETIF_F_NOCACHE_COPY;
+ if ((dev->features & NETIF_F_ALL_CSUM) &&
+ !(dev->features & NETIF_F_NO_CSUM)) {
+ dev->wanted_features |= NETIF_F_NOCACHE_COPY;
+ dev->features |= NETIF_F_NOCACHE_COPY;
}
/* Enable GRO and NETIF_F_HIGHDMA for vlans by default,
@@ -5434,7 +5491,7 @@ int register_netdevice(struct net_device *dev)
goto err_uninit;
dev->reg_state = NETREG_REGISTERED;
- netdev_update_features(dev);
+ __netdev_update_features(dev);
/*
* Default initial state at registry is that the
@@ -5531,19 +5588,7 @@ int register_netdev(struct net_device *dev)
int err;
rtnl_lock();
-
- /*
- * If the name is a format string the caller wants us to do a
- * name allocation.
- */
- if (strchr(dev->name, '%')) {
- err = dev_alloc_name(dev, dev->name);
- if (err < 0)
- goto out;
- }
-
err = register_netdevice(dev);
-out:
rtnl_unlock();
return err;
}
@@ -5916,7 +5961,10 @@ EXPORT_SYMBOL(free_netdev);
void synchronize_net(void)
{
might_sleep();
- synchronize_rcu();
+ if (rtnl_is_locked())
+ synchronize_rcu_expedited();
+ else
+ synchronize_rcu();
}
EXPORT_SYMBOL(synchronize_net);
@@ -6025,7 +6073,7 @@ int dev_change_net_namespace(struct net_device *dev, struct net *net, const char
/* We get here if we can't use the current device name */
if (!pat)
goto out;
- if (dev_get_valid_name(dev, pat, 1))
+ if (dev_get_valid_name(dev, pat) < 0)
goto out;
}
@@ -6157,29 +6205,20 @@ static int dev_cpu_callback(struct notifier_block *nfb,
*/
u32 netdev_increment_features(u32 all, u32 one, u32 mask)
{
- /* If device needs checksumming, downgrade to it. */
- if (all & NETIF_F_NO_CSUM && !(one & NETIF_F_NO_CSUM))
- all ^= NETIF_F_NO_CSUM | (one & NETIF_F_ALL_CSUM);
- else if (mask & NETIF_F_ALL_CSUM) {
- /* If one device supports v4/v6 checksumming, set for all. */
- if (one & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM) &&
- !(all & NETIF_F_GEN_CSUM)) {
- all &= ~NETIF_F_ALL_CSUM;
- all |= one & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM);
- }
+ if (mask & NETIF_F_GEN_CSUM)
+ mask |= NETIF_F_ALL_CSUM;
+ mask |= NETIF_F_VLAN_CHALLENGED;
- /* If one device supports hw checksumming, set for all. */
- if (one & NETIF_F_GEN_CSUM && !(all & NETIF_F_GEN_CSUM)) {
- all &= ~NETIF_F_ALL_CSUM;
- all |= NETIF_F_HW_CSUM;
- }
- }
+ all |= one & (NETIF_F_ONE_FOR_ALL|NETIF_F_ALL_CSUM) & mask;
+ all &= one | ~NETIF_F_ALL_FOR_ALL;
- one |= NETIF_F_ALL_CSUM;
+ /* If device needs checksumming, downgrade to it. */
+ if (all & (NETIF_F_ALL_CSUM & ~NETIF_F_NO_CSUM))
+ all &= ~NETIF_F_NO_CSUM;
- one |= all & NETIF_F_ONE_FOR_ALL;
- all &= one | NETIF_F_LLTX | NETIF_F_GSO | NETIF_F_UFO;
- all |= one & mask & NETIF_F_ONE_FOR_ALL;
+ /* If one device supports hw checksumming, set for all. */
+ if (all & NETIF_F_GEN_CSUM)
+ all &= ~(NETIF_F_ALL_CSUM & ~NETIF_F_GEN_CSUM);
return all;
}
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index 7b39f3e..e2e6693 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -68,14 +68,6 @@ static int __hw_addr_add(struct netdev_hw_addr_list *list, unsigned char *addr,
return __hw_addr_add_ex(list, addr, addr_len, addr_type, false);
}
-static void ha_rcu_free(struct rcu_head *head)
-{
- struct netdev_hw_addr *ha;
-
- ha = container_of(head, struct netdev_hw_addr, rcu_head);
- kfree(ha);
-}
-
static int __hw_addr_del_ex(struct netdev_hw_addr_list *list,
unsigned char *addr, int addr_len,
unsigned char addr_type, bool global)
@@ -94,7 +86,7 @@ static int __hw_addr_del_ex(struct netdev_hw_addr_list *list,
if (--ha->refcount)
return 0;
list_del_rcu(&ha->list);
- call_rcu(&ha->rcu_head, ha_rcu_free);
+ kfree_rcu(ha, rcu_head);
list->count--;
return 0;
}
@@ -197,7 +189,7 @@ void __hw_addr_flush(struct netdev_hw_addr_list *list)
list_for_each_entry_safe(ha, tmp, &list->list, list) {
list_del_rcu(&ha->list);
- call_rcu(&ha->rcu_head, ha_rcu_free);
+ kfree_rcu(ha, rcu_head);
}
list->count = 0;
}
diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c
index 706502f..7f36b38 100644
--- a/net/core/drop_monitor.c
+++ b/net/core/drop_monitor.c
@@ -207,14 +207,6 @@ static void trace_napi_poll_hit(void *ignore, struct napi_struct *napi)
rcu_read_unlock();
}
-
-static void free_dm_hw_stat(struct rcu_head *head)
-{
- struct dm_hw_stat_delta *n;
- n = container_of(head, struct dm_hw_stat_delta, rcu);
- kfree(n);
-}
-
static int set_all_monitor_traces(int state)
{
int rc = 0;
@@ -245,7 +237,7 @@ static int set_all_monitor_traces(int state)
list_for_each_entry_safe(new_stat, temp, &hw_stats_list, list) {
if (new_stat->dev == NULL) {
list_del_rcu(&new_stat->list);
- call_rcu(&new_stat->rcu, free_dm_hw_stat);
+ kfree_rcu(new_stat, rcu);
}
}
break;
@@ -314,7 +306,7 @@ static int dropmon_net_event(struct notifier_block *ev_block,
new_stat->dev = NULL;
if (trace_state == TRACE_OFF) {
list_del_rcu(&new_stat->list);
- call_rcu(&new_stat->rcu, free_dm_hw_stat);
+ kfree_rcu(new_stat, rcu);
break;
}
}
diff --git a/net/core/dst.c b/net/core/dst.c
index 91104d3..9ccca03 100644
--- a/net/core/dst.c
+++ b/net/core/dst.c
@@ -19,6 +19,7 @@
#include <linux/types.h>
#include <net/net_namespace.h>
#include <linux/sched.h>
+#include <linux/prefetch.h>
#include <net/dst.h>
@@ -33,9 +34,6 @@
* 3) This list is guarded by a mutex,
* so that the gc_task and dst_dev_event() can be synchronized.
*/
-#if RT_CACHE_DEBUG >= 2
-static atomic_t dst_total = ATOMIC_INIT(0);
-#endif
/*
* We want to keep lock & list close together
@@ -69,10 +67,6 @@ static void dst_gc_task(struct work_struct *work)
unsigned long expires = ~0L;
struct dst_entry *dst, *next, head;
struct dst_entry *last = &head;
-#if RT_CACHE_DEBUG >= 2
- ktime_t time_start = ktime_get();
- struct timespec elapsed;
-#endif
mutex_lock(&dst_gc_mutex);
next = dst_busy_list;
@@ -146,15 +140,6 @@ loop:
spin_unlock_bh(&dst_garbage.lock);
mutex_unlock(&dst_gc_mutex);
-#if RT_CACHE_DEBUG >= 2
- elapsed = ktime_to_timespec(ktime_sub(ktime_get(), time_start));
- printk(KERN_DEBUG "dst_total: %d delayed: %d work_perf: %d"
- " expires: %lu elapsed: %lu us\n",
- atomic_read(&dst_total), delayed, work_performed,
- expires,
- elapsed.tv_sec * USEC_PER_SEC +
- elapsed.tv_nsec / NSEC_PER_USEC);
-#endif
}
int dst_discard(struct sk_buff *skb)
@@ -166,7 +151,8 @@ EXPORT_SYMBOL(dst_discard);
const u32 dst_default_metrics[RTAX_MAX];
-void *dst_alloc(struct dst_ops *ops, int initial_ref)
+void *dst_alloc(struct dst_ops *ops, struct net_device *dev,
+ int initial_ref, int initial_obsolete, int flags)
{
struct dst_entry *dst;
@@ -174,18 +160,36 @@ void *dst_alloc(struct dst_ops *ops, int initial_ref)
if (ops->gc(ops))
return NULL;
}
- dst = kmem_cache_zalloc(ops->kmem_cachep, GFP_ATOMIC);
+ dst = kmem_cache_alloc(ops->kmem_cachep, GFP_ATOMIC);
if (!dst)
return NULL;
- atomic_set(&dst->__refcnt, initial_ref);
+ dst->child = NULL;
+ dst->dev = dev;
+ if (dev)
+ dev_hold(dev);
dst->ops = ops;
- dst->lastuse = jiffies;
- dst->path = dst;
- dst->input = dst->output = dst_discard;
dst_init_metrics(dst, dst_default_metrics, true);
-#if RT_CACHE_DEBUG >= 2
- atomic_inc(&dst_total);
+ dst->expires = 0UL;
+ dst->path = dst;
+ dst->neighbour = NULL;
+ dst->hh = NULL;
+#ifdef CONFIG_XFRM
+ dst->xfrm = NULL;
+#endif
+ dst->input = dst_discard;
+ dst->output = dst_discard;
+ dst->error = 0;
+ dst->obsolete = initial_obsolete;
+ dst->header_len = 0;
+ dst->trailer_len = 0;
+#ifdef CONFIG_IP_ROUTE_CLASSID
+ dst->tclassid = 0;
#endif
+ atomic_set(&dst->__refcnt, initial_ref);
+ dst->__use = 0;
+ dst->lastuse = jiffies;
+ dst->flags = flags;
+ dst->next = NULL;
dst_entries_add(ops, 1);
return dst;
}
@@ -245,9 +249,6 @@ again:
dst->ops->destroy(dst);
if (dst->dev)
dev_put(dst->dev);
-#if RT_CACHE_DEBUG >= 2
- atomic_dec(&dst_total);
-#endif
kmem_cache_free(dst->ops->kmem_cachep, dst);
dst = child;
@@ -314,7 +315,7 @@ void __dst_destroy_metrics_generic(struct dst_entry *dst, unsigned long old)
{
unsigned long prev, new;
- new = (unsigned long) dst_default_metrics;
+ new = ((unsigned long) dst_default_metrics) | DST_METRICS_READ_ONLY;
prev = cmpxchg(&dst->_metrics, old, new);
if (prev == old)
kfree(__DST_METRICS_PTR(old));
diff --git a/net/core/ethtool.c b/net/core/ethtool.c
index 74ead9e..84e7304 100644
--- a/net/core/ethtool.c
+++ b/net/core/ethtool.c
@@ -21,6 +21,8 @@
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
+#include <linux/rtnetlink.h>
+#include <linux/sched.h>
/*
* Some useful ethtool_ops methods that're device independent.
@@ -317,7 +319,7 @@ static int ethtool_set_features(struct net_device *dev, void __user *useraddr)
dev->wanted_features &= ~features[0].valid;
dev->wanted_features |= features[0].valid & features[0].requested;
- netdev_update_features(dev);
+ __netdev_update_features(dev);
if ((dev->wanted_features ^ dev->features) & features[0].valid)
ret |= ETHTOOL_F_WISH;
@@ -330,7 +332,7 @@ static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GS
/* NETIF_F_IP_CSUM */ "tx-checksum-ipv4",
/* NETIF_F_NO_CSUM */ "tx-checksum-unneeded",
/* NETIF_F_HW_CSUM */ "tx-checksum-ip-generic",
- /* NETIF_F_IPV6_CSUM */ "tx_checksum-ipv6",
+ /* NETIF_F_IPV6_CSUM */ "tx-checksum-ipv6",
/* NETIF_F_HIGHDMA */ "highdma",
/* NETIF_F_FRAGLIST */ "tx-scatter-gather-fraglist",
/* NETIF_F_HW_VLAN_TX */ "tx-vlan-hw-insert",
@@ -359,8 +361,8 @@ static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GS
/* NETIF_F_NTUPLE */ "rx-ntuple-filter",
/* NETIF_F_RXHASH */ "rx-hashing",
/* NETIF_F_RXCSUM */ "rx-checksum",
- "",
- "",
+ /* NETIF_F_NOCACHE_COPY */ "tx-nocache-copy",
+ /* NETIF_F_LOOPBACK */ "loopback",
};
static int __ethtool_get_sset_count(struct net_device *dev, int sset)
@@ -499,7 +501,7 @@ static int ethtool_set_one_feature(struct net_device *dev,
else
dev->wanted_features &= ~mask;
- netdev_update_features(dev);
+ __netdev_update_features(dev);
return 0;
}
@@ -544,14 +546,14 @@ int __ethtool_set_flags(struct net_device *dev, u32 data)
}
/* allow changing only bits set in hw_features */
- changed = (data ^ dev->wanted_features) & flags_dup_features;
+ changed = (data ^ dev->features) & flags_dup_features;
if (changed & ~dev->hw_features)
return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP;
dev->wanted_features =
- (dev->wanted_features & ~changed) | data;
+ (dev->wanted_features & ~changed) | (data & dev->hw_features);
- netdev_update_features(dev);
+ __netdev_update_features(dev);
return 0;
}
@@ -908,6 +910,9 @@ static noinline_for_stack int ethtool_set_rx_ntuple(struct net_device *dev,
struct ethtool_rx_ntuple_flow_spec_container *fsc = NULL;
int ret;
+ if (!ops->set_rx_ntuple)
+ return -EOPNOTSUPP;
+
if (!(dev->features & NETIF_F_NTUPLE))
return -EINVAL;
@@ -1441,6 +1446,35 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
return dev->ethtool_ops->set_ringparam(dev, &ringparam);
}
+static noinline_for_stack int ethtool_get_channels(struct net_device *dev,
+ void __user *useraddr)
+{
+ struct ethtool_channels channels = { .cmd = ETHTOOL_GCHANNELS };
+
+ if (!dev->ethtool_ops->get_channels)
+ return -EOPNOTSUPP;
+
+ dev->ethtool_ops->get_channels(dev, &channels);
+
+ if (copy_to_user(useraddr, &channels, sizeof(channels)))
+ return -EFAULT;
+ return 0;
+}
+
+static noinline_for_stack int ethtool_set_channels(struct net_device *dev,
+ void __user *useraddr)
+{
+ struct ethtool_channels channels;
+
+ if (!dev->ethtool_ops->set_channels)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&channels, useraddr, sizeof(channels)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_channels(dev, &channels);
+}
+
static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
{
struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM };
@@ -1618,14 +1652,60 @@ out:
static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
{
struct ethtool_value id;
+ static bool busy;
+ int rc;
- if (!dev->ethtool_ops->phys_id)
+ if (!dev->ethtool_ops->set_phys_id)
return -EOPNOTSUPP;
+ if (busy)
+ return -EBUSY;
+
if (copy_from_user(&id, useraddr, sizeof(id)))
return -EFAULT;
- return dev->ethtool_ops->phys_id(dev, id.data);
+ rc = dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_ACTIVE);
+ if (rc < 0)
+ return rc;
+
+ /* Drop the RTNL lock while waiting, but prevent reentry or
+ * removal of the device.
+ */
+ busy = true;
+ dev_hold(dev);
+ rtnl_unlock();
+
+ if (rc == 0) {
+ /* Driver will handle this itself */
+ schedule_timeout_interruptible(
+ id.data ? (id.data * HZ) : MAX_SCHEDULE_TIMEOUT);
+ } else {
+ /* Driver expects to be called at twice the frequency in rc */
+ int n = rc * 2, i, interval = HZ / n;
+
+ /* Count down seconds */
+ do {
+ /* Count down iterations per second */
+ i = n;
+ do {
+ rtnl_lock();
+ rc = dev->ethtool_ops->set_phys_id(dev,
+ (i & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON);
+ rtnl_unlock();
+ if (rc)
+ break;
+ schedule_timeout_interruptible(interval);
+ } while (!signal_pending(current) && --i != 0);
+ } while (!signal_pending(current) &&
+ (id.data == 0 || --id.data != 0));
+ }
+
+ rtnl_lock();
+ dev_put(dev);
+ busy = false;
+
+ (void)dev->ethtool_ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE);
+ return rc;
}
static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
@@ -1743,6 +1823,87 @@ static noinline_for_stack int ethtool_flash_device(struct net_device *dev,
return dev->ethtool_ops->flash_device(dev, &efl);
}
+static int ethtool_set_dump(struct net_device *dev,
+ void __user *useraddr)
+{
+ struct ethtool_dump dump;
+
+ if (!dev->ethtool_ops->set_dump)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&dump, useraddr, sizeof(dump)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_dump(dev, &dump);
+}
+
+static int ethtool_get_dump_flag(struct net_device *dev,
+ void __user *useraddr)
+{
+ int ret;
+ struct ethtool_dump dump;
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+
+ if (!dev->ethtool_ops->get_dump_flag)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&dump, useraddr, sizeof(dump)))
+ return -EFAULT;
+
+ ret = ops->get_dump_flag(dev, &dump);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(useraddr, &dump, sizeof(dump)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_get_dump_data(struct net_device *dev,
+ void __user *useraddr)
+{
+ int ret;
+ __u32 len;
+ struct ethtool_dump dump, tmp;
+ const struct ethtool_ops *ops = dev->ethtool_ops;
+ void *data = NULL;
+
+ if (!dev->ethtool_ops->get_dump_data ||
+ !dev->ethtool_ops->get_dump_flag)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&dump, useraddr, sizeof(dump)))
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.cmd = ETHTOOL_GET_DUMP_FLAG;
+ ret = ops->get_dump_flag(dev, &tmp);
+ if (ret)
+ return ret;
+
+ len = (tmp.len > dump.len) ? dump.len : tmp.len;
+ if (!len)
+ return -EFAULT;
+
+ data = vzalloc(tmp.len);
+ if (!data)
+ return -ENOMEM;
+ ret = ops->get_dump_data(dev, &dump, data);
+ if (ret)
+ goto out;
+
+ if (copy_to_user(useraddr, &dump, sizeof(dump))) {
+ ret = -EFAULT;
+ goto out;
+ }
+ useraddr += offsetof(struct ethtool_dump, data);
+ if (copy_to_user(useraddr, data, len))
+ ret = -EFAULT;
+out:
+ vfree(data);
+ return ret;
+}
+
/* The main entry point in this file. Called from net/core/dev.c */
int dev_ethtool(struct net *net, struct ifreq *ifr)
@@ -1953,6 +2114,21 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
case ETHTOOL_SGRO:
rc = ethtool_set_one_feature(dev, useraddr, ethcmd);
break;
+ case ETHTOOL_GCHANNELS:
+ rc = ethtool_get_channels(dev, useraddr);
+ break;
+ case ETHTOOL_SCHANNELS:
+ rc = ethtool_set_channels(dev, useraddr);
+ break;
+ case ETHTOOL_SET_DUMP:
+ rc = ethtool_set_dump(dev, useraddr);
+ break;
+ case ETHTOOL_GET_DUMP_FLAG:
+ rc = ethtool_get_dump_flag(dev, useraddr);
+ break;
+ case ETHTOOL_GET_DUMP_DATA:
+ rc = ethtool_get_dump_data(dev, useraddr);
+ break;
default:
rc = -EOPNOTSUPP;
}
diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c
index 8248ebb..008dc70 100644
--- a/net/core/fib_rules.c
+++ b/net/core/fib_rules.c
@@ -590,7 +590,8 @@ static int dump_rules(struct sk_buff *skb, struct netlink_callback *cb,
int idx = 0;
struct fib_rule *rule;
- list_for_each_entry(rule, &ops->rules_list, list) {
+ rcu_read_lock();
+ list_for_each_entry_rcu(rule, &ops->rules_list, list) {
if (idx < cb->args[1])
goto skip;
@@ -601,6 +602,7 @@ static int dump_rules(struct sk_buff *skb, struct netlink_callback *cb,
skip:
idx++;
}
+ rcu_read_unlock();
cb->args[1] = idx;
rules_ops_put(ops);
diff --git a/net/core/filter.c b/net/core/filter.c
index afb8afb..0e3622f 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -39,65 +39,6 @@
#include <linux/filter.h>
#include <linux/reciprocal_div.h>
-enum {
- BPF_S_RET_K = 1,
- BPF_S_RET_A,
- BPF_S_ALU_ADD_K,
- BPF_S_ALU_ADD_X,
- BPF_S_ALU_SUB_K,
- BPF_S_ALU_SUB_X,
- BPF_S_ALU_MUL_K,
- BPF_S_ALU_MUL_X,
- BPF_S_ALU_DIV_X,
- BPF_S_ALU_AND_K,
- BPF_S_ALU_AND_X,
- BPF_S_ALU_OR_K,
- BPF_S_ALU_OR_X,
- BPF_S_ALU_LSH_K,
- BPF_S_ALU_LSH_X,
- BPF_S_ALU_RSH_K,
- BPF_S_ALU_RSH_X,
- BPF_S_ALU_NEG,
- BPF_S_LD_W_ABS,
- BPF_S_LD_H_ABS,
- BPF_S_LD_B_ABS,
- BPF_S_LD_W_LEN,
- BPF_S_LD_W_IND,
- BPF_S_LD_H_IND,
- BPF_S_LD_B_IND,
- BPF_S_LD_IMM,
- BPF_S_LDX_W_LEN,
- BPF_S_LDX_B_MSH,
- BPF_S_LDX_IMM,
- BPF_S_MISC_TAX,
- BPF_S_MISC_TXA,
- BPF_S_ALU_DIV_K,
- BPF_S_LD_MEM,
- BPF_S_LDX_MEM,
- BPF_S_ST,
- BPF_S_STX,
- BPF_S_JMP_JA,
- BPF_S_JMP_JEQ_K,
- BPF_S_JMP_JEQ_X,
- BPF_S_JMP_JGE_K,
- BPF_S_JMP_JGE_X,
- BPF_S_JMP_JGT_K,
- BPF_S_JMP_JGT_X,
- BPF_S_JMP_JSET_K,
- BPF_S_JMP_JSET_X,
- /* Ancillary data */
- BPF_S_ANC_PROTOCOL,
- BPF_S_ANC_PKTTYPE,
- BPF_S_ANC_IFINDEX,
- BPF_S_ANC_NLATTR,
- BPF_S_ANC_NLATTR_NEST,
- BPF_S_ANC_MARK,
- BPF_S_ANC_QUEUE,
- BPF_S_ANC_HATYPE,
- BPF_S_ANC_RXHASH,
- BPF_S_ANC_CPU,
-};
-
/* No hurry in this branch */
static void *__load_pointer(const struct sk_buff *skb, int k, unsigned int size)
{
@@ -145,7 +86,7 @@ int sk_filter(struct sock *sk, struct sk_buff *skb)
rcu_read_lock();
filter = rcu_dereference(sk->sk_filter);
if (filter) {
- unsigned int pkt_len = sk_run_filter(skb, filter->insns);
+ unsigned int pkt_len = SK_RUN_FILTER(filter, skb);
err = pkt_len ? pskb_trim(skb, pkt_len) : -EPERM;
}
@@ -409,7 +350,9 @@ load_b:
continue;
}
default:
- WARN_ON(1);
+ WARN_RATELIMIT(1, "Unknown code:%u jt:%u tf:%u k:%u\n",
+ fentry->code, fentry->jt,
+ fentry->jf, fentry->k);
return 0;
}
}
@@ -638,6 +581,7 @@ void sk_filter_release_rcu(struct rcu_head *rcu)
{
struct sk_filter *fp = container_of(rcu, struct sk_filter, rcu);
+ bpf_jit_free(fp);
kfree(fp);
}
EXPORT_SYMBOL(sk_filter_release_rcu);
@@ -672,6 +616,7 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
atomic_set(&fp->refcnt, 1);
fp->len = fprog->len;
+ fp->bpf_func = sk_run_filter;
err = sk_chk_filter(fp->insns, fp->len);
if (err) {
@@ -679,6 +624,8 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
return err;
}
+ bpf_jit_compile(fp);
+
old_fp = rcu_dereference_protected(sk->sk_filter,
sock_owned_by_user(sk));
rcu_assign_pointer(sk->sk_filter, fp);
diff --git a/net/core/gen_estimator.c b/net/core/gen_estimator.c
index 7c23733..43b03dd 100644
--- a/net/core/gen_estimator.c
+++ b/net/core/gen_estimator.c
@@ -249,13 +249,6 @@ int gen_new_estimator(struct gnet_stats_basic_packed *bstats,
}
EXPORT_SYMBOL(gen_new_estimator);
-static void __gen_kill_estimator(struct rcu_head *head)
-{
- struct gen_estimator *e = container_of(head,
- struct gen_estimator, e_rcu);
- kfree(e);
-}
-
/**
* gen_kill_estimator - remove a rate estimator
* @bstats: basic statistics
@@ -279,7 +272,7 @@ void gen_kill_estimator(struct gnet_stats_basic_packed *bstats,
write_unlock(&est_lock);
list_del_rcu(&e->list);
- call_rcu(&e->e_rcu, __gen_kill_estimator);
+ kfree_rcu(e, e_rcu);
}
spin_unlock_bh(&est_tree_lock);
}
diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c
index 5ceb257..11b98bc 100644
--- a/net/core/net-sysfs.c
+++ b/net/core/net-sysfs.c
@@ -28,6 +28,7 @@
static const char fmt_hex[] = "%#x\n";
static const char fmt_long_hex[] = "%#lx\n";
static const char fmt_dec[] = "%d\n";
+static const char fmt_udec[] = "%u\n";
static const char fmt_ulong[] = "%lu\n";
static const char fmt_u64[] = "%llu\n";
@@ -145,13 +146,10 @@ static ssize_t show_speed(struct device *dev,
if (!rtnl_trylock())
return restart_syscall();
- if (netif_running(netdev) &&
- netdev->ethtool_ops &&
- netdev->ethtool_ops->get_settings) {
- struct ethtool_cmd cmd = { ETHTOOL_GSET };
-
- if (!netdev->ethtool_ops->get_settings(netdev, &cmd))
- ret = sprintf(buf, fmt_dec, ethtool_cmd_speed(&cmd));
+ if (netif_running(netdev)) {
+ struct ethtool_cmd cmd;
+ if (!dev_ethtool_get_settings(netdev, &cmd))
+ ret = sprintf(buf, fmt_udec, ethtool_cmd_speed(&cmd));
}
rtnl_unlock();
return ret;
@@ -166,13 +164,11 @@ static ssize_t show_duplex(struct device *dev,
if (!rtnl_trylock())
return restart_syscall();
- if (netif_running(netdev) &&
- netdev->ethtool_ops &&
- netdev->ethtool_ops->get_settings) {
- struct ethtool_cmd cmd = { ETHTOOL_GSET };
-
- if (!netdev->ethtool_ops->get_settings(netdev, &cmd))
- ret = sprintf(buf, "%s\n", cmd.duplex ? "full" : "half");
+ if (netif_running(netdev)) {
+ struct ethtool_cmd cmd;
+ if (!dev_ethtool_get_settings(netdev, &cmd))
+ ret = sprintf(buf, "%s\n",
+ cmd.duplex ? "full" : "half");
}
rtnl_unlock();
return ret;
@@ -565,13 +561,6 @@ static ssize_t show_rps_map(struct netdev_rx_queue *queue,
return len;
}
-static void rps_map_release(struct rcu_head *rcu)
-{
- struct rps_map *map = container_of(rcu, struct rps_map, rcu);
-
- kfree(map);
-}
-
static ssize_t store_rps_map(struct netdev_rx_queue *queue,
struct rx_queue_attribute *attribute,
const char *buf, size_t len)
@@ -619,7 +608,7 @@ static ssize_t store_rps_map(struct netdev_rx_queue *queue,
spin_unlock(&rps_map_lock);
if (old_map)
- call_rcu(&old_map->rcu, rps_map_release);
+ kfree_rcu(old_map, rcu);
free_cpumask_var(mask);
return len;
@@ -728,7 +717,7 @@ static void rx_queue_release(struct kobject *kobj)
map = rcu_dereference_raw(queue->rps_map);
if (map) {
RCU_INIT_POINTER(queue->rps_map, NULL);
- call_rcu(&map->rcu, rps_map_release);
+ kfree_rcu(map, rcu);
}
flow_table = rcu_dereference_raw(queue->rps_flow_table);
@@ -898,21 +887,6 @@ static ssize_t show_xps_map(struct netdev_queue *queue,
return len;
}
-static void xps_map_release(struct rcu_head *rcu)
-{
- struct xps_map *map = container_of(rcu, struct xps_map, rcu);
-
- kfree(map);
-}
-
-static void xps_dev_maps_release(struct rcu_head *rcu)
-{
- struct xps_dev_maps *dev_maps =
- container_of(rcu, struct xps_dev_maps, rcu);
-
- kfree(dev_maps);
-}
-
static DEFINE_MUTEX(xps_map_mutex);
#define xmap_dereference(P) \
rcu_dereference_protected((P), lockdep_is_held(&xps_map_mutex))
@@ -968,7 +942,7 @@ static ssize_t store_xps_map(struct netdev_queue *queue,
} else
pos = map_len = alloc_len = 0;
- need_set = cpu_isset(cpu, *mask) && cpu_online(cpu);
+ need_set = cpumask_test_cpu(cpu, mask) && cpu_online(cpu);
#ifdef CONFIG_NUMA
if (need_set) {
if (numa_node == -2)
@@ -1009,7 +983,7 @@ static ssize_t store_xps_map(struct netdev_queue *queue,
map = dev_maps ?
xmap_dereference(dev_maps->cpu_map[cpu]) : NULL;
if (map && xmap_dereference(new_dev_maps->cpu_map[cpu]) != map)
- call_rcu(&map->rcu, xps_map_release);
+ kfree_rcu(map, rcu);
if (new_dev_maps->cpu_map[cpu])
nonempty = 1;
}
@@ -1022,7 +996,7 @@ static ssize_t store_xps_map(struct netdev_queue *queue,
}
if (dev_maps)
- call_rcu(&dev_maps->rcu, xps_dev_maps_release);
+ kfree_rcu(dev_maps, rcu);
netdev_queue_numa_node_write(queue, (numa_node >= 0) ? numa_node :
NUMA_NO_NODE);
@@ -1084,7 +1058,7 @@ static void netdev_queue_release(struct kobject *kobj)
else {
RCU_INIT_POINTER(dev_maps->cpu_map[i],
NULL);
- call_rcu(&map->rcu, xps_map_release);
+ kfree_rcu(map, rcu);
map = NULL;
}
}
@@ -1094,7 +1068,7 @@ static void netdev_queue_release(struct kobject *kobj)
if (!nonempty) {
RCU_INIT_POINTER(dev->xps_maps, NULL);
- call_rcu(&dev_maps->rcu, xps_dev_maps_release);
+ kfree_rcu(dev_maps, rcu);
}
}
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index d4cf178..6c6b86d 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -29,14 +29,6 @@ EXPORT_SYMBOL(init_net);
#define INITIAL_NET_GEN_PTRS 13 /* +1 for len +2 for rcu_head */
-static void net_generic_release(struct rcu_head *rcu)
-{
- struct net_generic *ng;
-
- ng = container_of(rcu, struct net_generic, rcu);
- kfree(ng);
-}
-
static int net_assign_generic(struct net *net, int id, void *data)
{
struct net_generic *ng, *old_ng;
@@ -70,7 +62,7 @@ static int net_assign_generic(struct net *net, int id, void *data)
memcpy(&ng->ptr, &old_ng->ptr, old_ng->len * sizeof(void*));
rcu_assign_pointer(net->gen, ng);
- call_rcu(&old_ng->rcu, net_generic_release);
+ kfree_rcu(old_ng, rcu);
assign:
ng->ptr[id - 1] = data;
return 0;
@@ -218,11 +210,14 @@ static void net_free(struct net *net)
kmem_cache_free(net_cachep, net);
}
-static struct net *net_create(void)
+struct net *copy_net_ns(unsigned long flags, struct net *old_net)
{
struct net *net;
int rv;
+ if (!(flags & CLONE_NEWNET))
+ return get_net(old_net);
+
net = net_alloc();
if (!net)
return ERR_PTR(-ENOMEM);
@@ -241,13 +236,6 @@ static struct net *net_create(void)
return net;
}
-struct net *copy_net_ns(unsigned long flags, struct net *old_net)
-{
- if (!(flags & CLONE_NEWNET))
- return get_net(old_net);
- return net_create();
-}
-
static DEFINE_SPINLOCK(cleanup_list_lock);
static LIST_HEAD(cleanup_list); /* Must hold cleanup_list_lock to touch */
diff --git a/net/core/netpoll.c b/net/core/netpoll.c
index 06be243..2d7d6d4 100644
--- a/net/core/netpoll.c
+++ b/net/core/netpoll.c
@@ -539,7 +539,7 @@ int __netpoll_rx(struct sk_buff *skb)
{
int proto, len, ulen;
int hits = 0;
- struct iphdr *iph;
+ const struct iphdr *iph;
struct udphdr *uh;
struct netpoll_info *npinfo = skb->dev->npinfo;
struct netpoll *np, *tmp;
@@ -698,32 +698,8 @@ int netpoll_parse_options(struct netpoll *np, char *opt)
if (*cur != 0) {
/* MAC address */
- if ((delim = strchr(cur, ':')) == NULL)
- goto parse_failed;
- *delim = 0;
- np->remote_mac[0] = simple_strtol(cur, NULL, 16);
- cur = delim + 1;
- if ((delim = strchr(cur, ':')) == NULL)
- goto parse_failed;
- *delim = 0;
- np->remote_mac[1] = simple_strtol(cur, NULL, 16);
- cur = delim + 1;
- if ((delim = strchr(cur, ':')) == NULL)
+ if (!mac_pton(cur, np->remote_mac))
goto parse_failed;
- *delim = 0;
- np->remote_mac[2] = simple_strtol(cur, NULL, 16);
- cur = delim + 1;
- if ((delim = strchr(cur, ':')) == NULL)
- goto parse_failed;
- *delim = 0;
- np->remote_mac[3] = simple_strtol(cur, NULL, 16);
- cur = delim + 1;
- if ((delim = strchr(cur, ':')) == NULL)
- goto parse_failed;
- *delim = 0;
- np->remote_mac[4] = simple_strtol(cur, NULL, 16);
- cur = delim + 1;
- np->remote_mac[5] = simple_strtol(cur, NULL, 16);
}
netpoll_print_options(np);
diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index aeeece7..f76079c 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -156,6 +156,7 @@
#include <linux/wait.h>
#include <linux/etherdevice.h>
#include <linux/kthread.h>
+#include <linux/prefetch.h>
#include <net/net_namespace.h>
#include <net/checksum.h>
#include <net/ipv6.h>
@@ -449,7 +450,6 @@ static void pktgen_stop(struct pktgen_thread *t);
static void pktgen_clear_counters(struct pktgen_dev *pkt_dev);
static unsigned int scan_ip6(const char *s, char ip[16]);
-static unsigned int fmt_ip6(char *s, const char ip[16]);
/* Module parameters, defaults. */
static int pg_count_d __read_mostly = 1000;
@@ -556,21 +556,13 @@ static int pktgen_if_show(struct seq_file *seq, void *v)
pkt_dev->skb_priority);
if (pkt_dev->flags & F_IPV6) {
- char b1[128], b2[128], b3[128];
- fmt_ip6(b1, pkt_dev->in6_saddr.s6_addr);
- fmt_ip6(b2, pkt_dev->min_in6_saddr.s6_addr);
- fmt_ip6(b3, pkt_dev->max_in6_saddr.s6_addr);
seq_printf(seq,
- " saddr: %s min_saddr: %s max_saddr: %s\n", b1,
- b2, b3);
-
- fmt_ip6(b1, pkt_dev->in6_daddr.s6_addr);
- fmt_ip6(b2, pkt_dev->min_in6_daddr.s6_addr);
- fmt_ip6(b3, pkt_dev->max_in6_daddr.s6_addr);
- seq_printf(seq,
- " daddr: %s min_daddr: %s max_daddr: %s\n", b1,
- b2, b3);
-
+ " saddr: %pI6c min_saddr: %pI6c max_saddr: %pI6c\n"
+ " daddr: %pI6c min_daddr: %pI6c max_daddr: %pI6c\n",
+ &pkt_dev->in6_saddr,
+ &pkt_dev->min_in6_saddr, &pkt_dev->max_in6_saddr,
+ &pkt_dev->in6_daddr,
+ &pkt_dev->min_in6_daddr, &pkt_dev->max_in6_daddr);
} else {
seq_printf(seq,
" dst_min: %s dst_max: %s\n",
@@ -706,10 +698,9 @@ static int pktgen_if_show(struct seq_file *seq, void *v)
pkt_dev->cur_src_mac_offset);
if (pkt_dev->flags & F_IPV6) {
- char b1[128], b2[128];
- fmt_ip6(b1, pkt_dev->cur_in6_daddr.s6_addr);
- fmt_ip6(b2, pkt_dev->cur_in6_saddr.s6_addr);
- seq_printf(seq, " cur_saddr: %s cur_daddr: %s\n", b2, b1);
+ seq_printf(seq, " cur_saddr: %pI6c cur_daddr: %pI6c\n",
+ &pkt_dev->cur_in6_saddr,
+ &pkt_dev->cur_in6_daddr);
} else
seq_printf(seq, " cur_saddr: 0x%x cur_daddr: 0x%x\n",
pkt_dev->cur_saddr, pkt_dev->cur_daddr);
@@ -1309,7 +1300,7 @@ static ssize_t pktgen_if_write(struct file *file,
buf[len] = 0;
scan_ip6(buf, pkt_dev->in6_daddr.s6_addr);
- fmt_ip6(buf, pkt_dev->in6_daddr.s6_addr);
+ snprintf(buf, sizeof(buf), "%pI6c", &pkt_dev->in6_daddr);
ipv6_addr_copy(&pkt_dev->cur_in6_daddr, &pkt_dev->in6_daddr);
@@ -1332,7 +1323,7 @@ static ssize_t pktgen_if_write(struct file *file,
buf[len] = 0;
scan_ip6(buf, pkt_dev->min_in6_daddr.s6_addr);
- fmt_ip6(buf, pkt_dev->min_in6_daddr.s6_addr);
+ snprintf(buf, sizeof(buf), "%pI6c", &pkt_dev->min_in6_daddr);
ipv6_addr_copy(&pkt_dev->cur_in6_daddr,
&pkt_dev->min_in6_daddr);
@@ -1355,7 +1346,7 @@ static ssize_t pktgen_if_write(struct file *file,
buf[len] = 0;
scan_ip6(buf, pkt_dev->max_in6_daddr.s6_addr);
- fmt_ip6(buf, pkt_dev->max_in6_daddr.s6_addr);
+ snprintf(buf, sizeof(buf), "%pI6c", &pkt_dev->max_in6_daddr);
if (debug)
printk(KERN_DEBUG "pktgen: dst6_max set to: %s\n", buf);
@@ -1376,7 +1367,7 @@ static ssize_t pktgen_if_write(struct file *file,
buf[len] = 0;
scan_ip6(buf, pkt_dev->in6_saddr.s6_addr);
- fmt_ip6(buf, pkt_dev->in6_saddr.s6_addr);
+ snprintf(buf, sizeof(buf), "%pI6c", &pkt_dev->in6_saddr);
ipv6_addr_copy(&pkt_dev->cur_in6_saddr, &pkt_dev->in6_saddr);
@@ -1430,11 +1421,6 @@ static ssize_t pktgen_if_write(struct file *file,
return count;
}
if (!strcmp(name, "dst_mac")) {
- char *v = valstr;
- unsigned char old_dmac[ETH_ALEN];
- unsigned char *m = pkt_dev->dst_mac;
- memcpy(old_dmac, pkt_dev->dst_mac, ETH_ALEN);
-
len = strn_len(&user_buffer[i], sizeof(valstr) - 1);
if (len < 0)
return len;
@@ -1442,35 +1428,16 @@ static ssize_t pktgen_if_write(struct file *file,
memset(valstr, 0, sizeof(valstr));
if (copy_from_user(valstr, &user_buffer[i], len))
return -EFAULT;
- i += len;
-
- for (*m = 0; *v && m < pkt_dev->dst_mac + 6; v++) {
- int value;
-
- value = hex_to_bin(*v);
- if (value >= 0)
- *m = *m * 16 + value;
-
- if (*v == ':') {
- m++;
- *m = 0;
- }
- }
+ if (!mac_pton(valstr, pkt_dev->dst_mac))
+ return -EINVAL;
/* Set up Dest MAC */
- if (compare_ether_addr(old_dmac, pkt_dev->dst_mac))
- memcpy(&(pkt_dev->hh[0]), pkt_dev->dst_mac, ETH_ALEN);
+ memcpy(&pkt_dev->hh[0], pkt_dev->dst_mac, ETH_ALEN);
- sprintf(pg_result, "OK: dstmac");
+ sprintf(pg_result, "OK: dstmac %pM", pkt_dev->dst_mac);
return count;
}
if (!strcmp(name, "src_mac")) {
- char *v = valstr;
- unsigned char old_smac[ETH_ALEN];
- unsigned char *m = pkt_dev->src_mac;
-
- memcpy(old_smac, pkt_dev->src_mac, ETH_ALEN);
-
len = strn_len(&user_buffer[i], sizeof(valstr) - 1);
if (len < 0)
return len;
@@ -1478,26 +1445,13 @@ static ssize_t pktgen_if_write(struct file *file,
memset(valstr, 0, sizeof(valstr));
if (copy_from_user(valstr, &user_buffer[i], len))
return -EFAULT;
- i += len;
-
- for (*m = 0; *v && m < pkt_dev->src_mac + 6; v++) {
- int value;
-
- value = hex_to_bin(*v);
- if (value >= 0)
- *m = *m * 16 + value;
-
- if (*v == ':') {
- m++;
- *m = 0;
- }
- }
+ if (!mac_pton(valstr, pkt_dev->src_mac))
+ return -EINVAL;
/* Set up Src MAC */
- if (compare_ether_addr(old_smac, pkt_dev->src_mac))
- memcpy(&(pkt_dev->hh[6]), pkt_dev->src_mac, ETH_ALEN);
+ memcpy(&pkt_dev->hh[6], pkt_dev->src_mac, ETH_ALEN);
- sprintf(pg_result, "OK: srcmac");
+ sprintf(pg_result, "OK: srcmac %pM", pkt_dev->src_mac);
return count;
}
@@ -2514,7 +2468,6 @@ static int pktgen_output_ipsec(struct sk_buff *skb, struct pktgen_dev *pkt_dev)
{
struct xfrm_state *x = pkt_dev->flows[pkt_dev->curfl].x;
int err = 0;
- struct iphdr *iph;
if (!x)
return 0;
@@ -2524,7 +2477,6 @@ static int pktgen_output_ipsec(struct sk_buff *skb, struct pktgen_dev *pkt_dev)
return 0;
spin_lock(&x->lock);
- iph = ip_hdr(skb);
err = x->outer_mode->output(x, skb);
if (err)
@@ -2624,6 +2576,7 @@ static void pktgen_finalize_skb(struct pktgen_dev *pkt_dev, struct sk_buff *skb,
} else {
int frags = pkt_dev->nfrags;
int i, len;
+ int frag_len;
if (frags > MAX_SKB_FRAGS)
@@ -2635,6 +2588,8 @@ static void pktgen_finalize_skb(struct pktgen_dev *pkt_dev, struct sk_buff *skb,
}
i = 0;
+ frag_len = (datalen/frags) < PAGE_SIZE ?
+ (datalen/frags) : PAGE_SIZE;
while (datalen > 0) {
if (unlikely(!pkt_dev->page)) {
int node = numa_node_id();
@@ -2648,38 +2603,18 @@ static void pktgen_finalize_skb(struct pktgen_dev *pkt_dev, struct sk_buff *skb,
skb_shinfo(skb)->frags[i].page = pkt_dev->page;
get_page(pkt_dev->page);
skb_shinfo(skb)->frags[i].page_offset = 0;
- skb_shinfo(skb)->frags[i].size =
- (datalen < PAGE_SIZE ? datalen : PAGE_SIZE);
+ /*last fragment, fill rest of data*/
+ if (i == (frags - 1))
+ skb_shinfo(skb)->frags[i].size =
+ (datalen < PAGE_SIZE ? datalen : PAGE_SIZE);
+ else
+ skb_shinfo(skb)->frags[i].size = frag_len;
datalen -= skb_shinfo(skb)->frags[i].size;
skb->len += skb_shinfo(skb)->frags[i].size;
skb->data_len += skb_shinfo(skb)->frags[i].size;
i++;
skb_shinfo(skb)->nr_frags = i;
}
-
- while (i < frags) {
- int rem;
-
- if (i == 0)
- break;
-
- rem = skb_shinfo(skb)->frags[i - 1].size / 2;
- if (rem == 0)
- break;
-
- skb_shinfo(skb)->frags[i - 1].size -= rem;
-
- skb_shinfo(skb)->frags[i] =
- skb_shinfo(skb)->frags[i - 1];
- get_page(skb_shinfo(skb)->frags[i].page);
- skb_shinfo(skb)->frags[i].page =
- skb_shinfo(skb)->frags[i - 1].page;
- skb_shinfo(skb)->frags[i].page_offset +=
- skb_shinfo(skb)->frags[i - 1].size;
- skb_shinfo(skb)->frags[i].size = rem;
- i++;
- skb_shinfo(skb)->nr_frags = i;
- }
}
/* Stamp the time, and sequence number,
@@ -2917,79 +2852,6 @@ static unsigned int scan_ip6(const char *s, char ip[16])
return len;
}
-static char tohex(char hexdigit)
-{
- return hexdigit > 9 ? hexdigit + 'a' - 10 : hexdigit + '0';
-}
-
-static int fmt_xlong(char *s, unsigned int i)
-{
- char *bak = s;
- *s = tohex((i >> 12) & 0xf);
- if (s != bak || *s != '0')
- ++s;
- *s = tohex((i >> 8) & 0xf);
- if (s != bak || *s != '0')
- ++s;
- *s = tohex((i >> 4) & 0xf);
- if (s != bak || *s != '0')
- ++s;
- *s = tohex(i & 0xf);
- return s - bak + 1;
-}
-
-static unsigned int fmt_ip6(char *s, const char ip[16])
-{
- unsigned int len;
- unsigned int i;
- unsigned int temp;
- unsigned int compressing;
- int j;
-
- len = 0;
- compressing = 0;
- for (j = 0; j < 16; j += 2) {
-
-#ifdef V4MAPPEDPREFIX
- if (j == 12 && !memcmp(ip, V4mappedprefix, 12)) {
- inet_ntoa_r(*(struct in_addr *)(ip + 12), s);
- temp = strlen(s);
- return len + temp;
- }
-#endif
- temp = ((unsigned long)(unsigned char)ip[j] << 8) +
- (unsigned long)(unsigned char)ip[j + 1];
- if (temp == 0) {
- if (!compressing) {
- compressing = 1;
- if (j == 0) {
- *s++ = ':';
- ++len;
- }
- }
- } else {
- if (compressing) {
- compressing = 0;
- *s++ = ':';
- ++len;
- }
- i = fmt_xlong(s, temp);
- len += i;
- s += i;
- if (j < 14) {
- *s++ = ':';
- ++len;
- }
- }
- }
- if (compressing) {
- *s++ = ':';
- ++len;
- }
- *s = 0;
- return len;
-}
-
static struct sk_buff *fill_packet_ipv6(struct net_device *odev,
struct pktgen_dev *pkt_dev)
{
@@ -3682,13 +3544,12 @@ static int pktgen_add_device(struct pktgen_thread *t, const char *ifname)
return -ENOMEM;
strcpy(pkt_dev->odevname, ifname);
- pkt_dev->flows = vmalloc_node(MAX_CFLOWS * sizeof(struct flow_state),
+ pkt_dev->flows = vzalloc_node(MAX_CFLOWS * sizeof(struct flow_state),
node);
if (pkt_dev->flows == NULL) {
kfree(pkt_dev);
return -ENOMEM;
}
- memset(pkt_dev->flows, 0, MAX_CFLOWS * sizeof(struct flow_state));
pkt_dev->removal_mark = 0;
pkt_dev->min_pkt_size = ETH_ZLEN;
@@ -3846,6 +3707,7 @@ static int __init pg_init(void)
{
int cpu;
struct proc_dir_entry *pe;
+ int ret = 0;
pr_info("%s", version);
@@ -3856,11 +3718,10 @@ static int __init pg_init(void)
pe = proc_create(PGCTRL, 0600, pg_proc_dir, &pktgen_fops);
if (pe == NULL) {
pr_err("ERROR: cannot create %s procfs entry\n", PGCTRL);
- proc_net_remove(&init_net, PG_PROC_DIR);
- return -EINVAL;
+ ret = -EINVAL;
+ goto remove_dir;
}
- /* Register us to receive netdevice events */
register_netdevice_notifier(&pktgen_notifier_block);
for_each_online_cpu(cpu) {
@@ -3874,13 +3735,18 @@ static int __init pg_init(void)
if (list_empty(&pktgen_threads)) {
pr_err("ERROR: Initialization failed for all threads\n");
- unregister_netdevice_notifier(&pktgen_notifier_block);
- remove_proc_entry(PGCTRL, pg_proc_dir);
- proc_net_remove(&init_net, PG_PROC_DIR);
- return -ENODEV;
+ ret = -ENODEV;
+ goto unregister;
}
return 0;
+
+ unregister:
+ unregister_netdevice_notifier(&pktgen_notifier_block);
+ remove_proc_entry(PGCTRL, pg_proc_dir);
+ remove_dir:
+ proc_net_remove(&init_net, PG_PROC_DIR);
+ return ret;
}
static void __exit pg_cleanup(void)
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index dca9602..abd936d 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -850,6 +850,7 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
struct nlattr *attr, *af_spec;
struct rtnl_af_ops *af_ops;
+ ASSERT_RTNL();
nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ifm), flags);
if (nlh == NULL)
return -EMSGSIZE;
@@ -1007,10 +1008,11 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
s_h = cb->args[0];
s_idx = cb->args[1];
+ rcu_read_lock();
for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
idx = 0;
head = &net->dev_index_head[h];
- hlist_for_each_entry(dev, node, head, index_hlist) {
+ hlist_for_each_entry_rcu(dev, node, head, index_hlist) {
if (idx < s_idx)
goto cont;
if (rtnl_fill_ifinfo(skb, dev, RTM_NEWLINK,
@@ -1023,6 +1025,7 @@ cont:
}
}
out:
+ rcu_read_unlock();
cb->args[1] = idx;
cb->args[0] = h;
@@ -1502,6 +1505,7 @@ static int rtnl_dellink(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
char ifname[IFNAMSIZ];
struct nlattr *tb[IFLA_MAX+1];
int err;
+ LIST_HEAD(list_kill);
err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFLA_MAX, ifla_policy);
if (err < 0)
@@ -1525,7 +1529,9 @@ static int rtnl_dellink(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
if (!ops)
return -EOPNOTSUPP;
- ops->dellink(dev, NULL);
+ ops->dellink(dev, &list_kill);
+ unregister_netdevice_many(&list_kill);
+ list_del(&list_kill);
return 0;
}
@@ -1573,12 +1579,6 @@ struct net_device *rtnl_create_link(struct net *src_net, struct net *net,
dev->rtnl_link_state = RTNL_LINK_INITIALIZING;
dev->real_num_tx_queues = real_num_queues;
- if (strchr(dev->name, '%')) {
- err = dev_alloc_name(dev, dev->name);
- if (err < 0)
- goto err_free;
- }
-
if (tb[IFLA_MTU])
dev->mtu = nla_get_u32(tb[IFLA_MTU]);
if (tb[IFLA_ADDRESS])
@@ -1598,8 +1598,6 @@ struct net_device *rtnl_create_link(struct net *src_net, struct net *net,
return dev;
-err_free:
- free_netdev(dev);
err:
return ERR_PTR(err);
}
@@ -1966,6 +1964,8 @@ static int rtnetlink_event(struct notifier_block *this, unsigned long event, voi
case NETDEV_GOING_DOWN:
case NETDEV_UNREGISTER:
case NETDEV_UNREGISTER_BATCH:
+ case NETDEV_RELEASE:
+ case NETDEV_JOIN:
break;
default:
rtmsg_ifinfo(RTM_NEWLINK, dev, 0);
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 7ebeed0..46cbd28 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -57,6 +57,7 @@
#include <linux/init.h>
#include <linux/scatterlist.h>
#include <linux/errqueue.h>
+#include <linux/prefetch.h>
#include <net/protocol.h>
#include <net/dst.h>
@@ -2993,6 +2994,9 @@ int sock_queue_err_skb(struct sock *sk, struct sk_buff *skb)
skb->destructor = sock_rmem_free;
atomic_add(skb->truesize, &sk->sk_rmem_alloc);
+ /* before exiting rcu section, make sure dst is refcounted */
+ skb_dst_force(skb);
+
skb_queue_tail(&sk->sk_error_queue, skb);
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_data_ready(sk, skb->len);
diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index 385b609..a829e3f 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -122,6 +122,15 @@ static struct ctl_table net_core_table[] = {
.mode = 0644,
.proc_handler = proc_dointvec
},
+#ifdef CONFIG_BPF_JIT
+ {
+ .procname = "bpf_jit_enable",
+ .data = &bpf_jit_enable,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec
+ },
+#endif
{
.procname = "netdev_tstamp_prequeue",
.data = &netdev_tstamp_prequeue,
diff --git a/net/core/utils.c b/net/core/utils.c
index 5fea0ab..2012bc7 100644
--- a/net/core/utils.c
+++ b/net/core/utils.c
@@ -296,3 +296,27 @@ void inet_proto_csum_replace4(__sum16 *sum, struct sk_buff *skb,
csum_unfold(*sum)));
}
EXPORT_SYMBOL(inet_proto_csum_replace4);
+
+int mac_pton(const char *s, u8 *mac)
+{
+ int i;
+
+ /* XX:XX:XX:XX:XX:XX */
+ if (strlen(s) < 3 * ETH_ALEN - 1)
+ return 0;
+
+ /* Don't dirty result unless string is valid MAC. */
+ for (i = 0; i < ETH_ALEN; i++) {
+ if (!strchr("0123456789abcdefABCDEF", s[i * 3]))
+ return 0;
+ if (!strchr("0123456789abcdefABCDEF", s[i * 3 + 1]))
+ return 0;
+ if (i != ETH_ALEN - 1 && s[i * 3 + 2] != ':')
+ return 0;
+ }
+ for (i = 0; i < ETH_ALEN; i++) {
+ mac[i] = (hex_to_bin(s[i * 3]) << 4) | hex_to_bin(s[i * 3 + 1]);
+ }
+ return 1;
+}
+EXPORT_SYMBOL(mac_pton);