Netdev List
 help / color / mirror / Atom feed
* [GIT PULL nf] IPVS Fixes for v3.19
From: Simon Horman @ 2015-01-30  1:22 UTC (permalink / raw)
  To: Pablo Neira Ayuso
  Cc: lvs-devel, netdev, netfilter-devel, Wensong Zhang,
	Julian Anastasov, Simon Horman

Hi Pablo,

please consider this fix for v3.19.

It resolves a crash in xfrm reported by Florian Wiessner.

I believe this problem manifests since 0a5ebb8000c5 ("ipv4: Pass explicit
daddr arg to ip_send_reply().") which was included in v2.6.39.

Julian reports that the patch has been tested on net tree (Dec 7), 3.14.25,
3.12.33, 3.10.61. 3.4.104 needs a modified fix that resolves rejects. It
applies with little fuzz on 3.2.64. Please let us know if we should post
separate 3.2 and 3.4 patches.


The following changes since commit e8781f70a5b210a1b08cff8ce05895ebcec18d83:

  netfilter: nf_tables: disable preemption when restoring chain counters (2015-01-26 11:50:02 +0100)

are available in the git repository at:

  https://git.kernel.org/pub/scm/linux/kernel/git/horms/ipvs.git tags/ipvs-fixes-for-v3.19

for you to fetch changes up to 579eb62ac35845686a7c4286c0a820b4eb1f96aa:

  ipvs: rerouting to local clients is not needed anymore (2015-01-30 10:05:55 +0900)

----------------------------------------------------------------
Julian Anastasov (1):
      ipvs: rerouting to local clients is not needed anymore

 net/netfilter/ipvs/ip_vs_core.c | 33 ++++++++++++++++++++++-----------
 1 file changed, 22 insertions(+), 11 deletions(-)

^ permalink raw reply

* [PATCH 6/6] rhashtable: Avoid bucket cross reference after removal
From: Thomas Graf @ 2015-01-30  0:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ying.xue
In-Reply-To: <cover.1422576760.git.tgraf@suug.ch>

During a resize, when two buckets in the larger table map to
a single bucket in the smaller table and the new table has already
been (partially) linked to the old table. Removal of an element
may result the bucket in the larger table to point to entries
which all hash to a different value than the bucket index. Thus
causing two buckets to point to the same sub chain after unzipping.
This is not illegal *during* the resize phase but after it has
completed.

Keep the old table around until all of the unzipping is done to
allow the removal code to only search for matching hashed entries
during this special period.

Reported-by: Ying Xue <ying.xue@windriver.com>
Fixes: 97defe1ecf86 ("rhashtable: Per bucket locks & deferred expansion/shrinking")
Signed-off-by: Thomas Graf <tgraf@suug.ch>
---
 lib/rhashtable.c | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index f21026a..74b9284 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -414,12 +414,6 @@ int rhashtable_expand(struct rhashtable *ht)
 		unlock_buckets(new_tbl, old_tbl, new_hash);
 	}
 
-	/* Publish the new table pointer. Lookups may now traverse
-	 * the new table, but they will not benefit from any
-	 * additional efficiency until later steps unzip the buckets.
-	 */
-	rcu_assign_pointer(ht->tbl, new_tbl);
-
 	/* Unzip interleaved hash chains */
 	while (!complete && !ht->being_destroyed) {
 		/* Wait for readers. All new readers will see the new
@@ -445,6 +439,7 @@ int rhashtable_expand(struct rhashtable *ht)
 	}
 
 	synchronize_rcu();
+	rcu_assign_pointer(ht->tbl, new_tbl);
 
 	bucket_table_free(old_tbl);
 	return 0;
@@ -621,7 +616,7 @@ bool rhashtable_remove(struct rhashtable *ht, struct rhash_head *obj)
 {
 	struct bucket_table *tbl, *new_tbl, *old_tbl;
 	struct rhash_head __rcu **pprev;
-	struct rhash_head *he;
+	struct rhash_head *he, *he2;
 	unsigned int hash, new_hash;
 	bool ret = false;
 
@@ -641,8 +636,21 @@ restart:
 		}
 
 		ASSERT_BUCKET_LOCK(ht, tbl, hash);
-		rcu_assign_pointer(*pprev, obj->next);
 
+		if (unlikely(new_tbl != tbl)) {
+			rht_for_each_continue(he2, he->next, tbl, hash) {
+				if (head_hashfn(ht, tbl, he2) == hash) {
+					rcu_assign_pointer(*pprev, he2);
+					goto found;
+				}
+			}
+
+			INIT_RHT_NULLS_HEAD(*pprev, ht, hash);
+		} else {
+			rcu_assign_pointer(*pprev, obj->next);
+		}
+
+found:
 		ret = true;
 		break;
 	}
-- 
1.9.3

^ permalink raw reply related

* [PATCH 5/6] rhashtable: Add more lock verification
From: Thomas Graf @ 2015-01-30  0:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ying.xue
In-Reply-To: <cover.1422576760.git.tgraf@suug.ch>

Catch hash miscalculations which result in hard to track down race
conditions.

Signed-off-by: Thomas Graf <tgraf@suug.ch>
---
 lib/rhashtable.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index fa11a2e..f21026a 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -347,9 +347,11 @@ static bool hashtable_chain_unzip(struct rhashtable *ht,
 	return !rht_is_a_nulls(p);
 }
 
-static void link_old_to_new(struct bucket_table *new_tbl,
+static void link_old_to_new(struct rhashtable *ht, struct bucket_table *new_tbl,
 			    unsigned int new_hash, struct rhash_head *entry)
 {
+	ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash);
+
 	rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), entry);
 }
 
@@ -405,7 +407,7 @@ int rhashtable_expand(struct rhashtable *ht)
 		lock_buckets(new_tbl, old_tbl, new_hash);
 		rht_for_each(he, old_tbl, old_hash) {
 			if (head_hashfn(ht, new_tbl, he) == new_hash) {
-				link_old_to_new(new_tbl, new_hash, he);
+				link_old_to_new(ht, new_tbl, new_hash, he);
 				break;
 			}
 		}
@@ -494,6 +496,7 @@ int rhashtable_shrink(struct rhashtable *ht)
 
 		rcu_assign_pointer(*bucket_tail(new_tbl, new_hash),
 				   tbl->buckets[new_hash]);
+		ASSERT_BUCKET_LOCK(ht, tbl, new_hash + new_tbl->size);
 		rcu_assign_pointer(*bucket_tail(new_tbl, new_hash),
 				   tbl->buckets[new_hash + new_tbl->size]);
 
@@ -551,6 +554,8 @@ static void __rhashtable_insert(struct rhashtable *ht, struct rhash_head *obj,
 	struct rhash_head *head = rht_dereference_bucket(tbl->buckets[hash],
 							 tbl, hash);
 
+	ASSERT_BUCKET_LOCK(ht, tbl, hash);
+
 	if (rht_is_a_nulls(head))
 		INIT_RHT_NULLS_HEAD(obj->next, ht, hash);
 	else
@@ -635,6 +640,7 @@ restart:
 			continue;
 		}
 
+		ASSERT_BUCKET_LOCK(ht, tbl, hash);
 		rcu_assign_pointer(*pprev, obj->next);
 
 		ret = true;
-- 
1.9.3

^ permalink raw reply related

* [PATCH 4/6] rhashtable: Dump bucket tables on locking violation under PROVE_LOCKING
From: Thomas Graf @ 2015-01-30  0:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ying.xue
In-Reply-To: <cover.1422576760.git.tgraf@suug.ch>

This simplifies debugging of locking violations if compiled with
CONFIG_PROVE_LOCKING.

Signed-off-by: Thomas Graf <tgraf@suug.ch>
---
 lib/rhashtable.c | 99 ++++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 75 insertions(+), 24 deletions(-)

diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index 85ec36b..fa11a2e 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -48,26 +48,6 @@ static spinlock_t *bucket_lock(const struct bucket_table *tbl, u32 hash)
 	return &tbl->locks[hash & tbl->locks_mask];
 }
 
-#define ASSERT_RHT_MUTEX(HT) BUG_ON(!lockdep_rht_mutex_is_held(HT))
-#define ASSERT_BUCKET_LOCK(TBL, HASH) \
-	BUG_ON(!lockdep_rht_bucket_is_held(TBL, HASH))
-
-#ifdef CONFIG_PROVE_LOCKING
-int lockdep_rht_mutex_is_held(struct rhashtable *ht)
-{
-	return (debug_locks) ? lockdep_is_held(&ht->mutex) : 1;
-}
-EXPORT_SYMBOL_GPL(lockdep_rht_mutex_is_held);
-
-int lockdep_rht_bucket_is_held(const struct bucket_table *tbl, u32 hash)
-{
-	spinlock_t *lock = bucket_lock(tbl, hash);
-
-	return (debug_locks) ? lockdep_is_held(lock) : 1;
-}
-EXPORT_SYMBOL_GPL(lockdep_rht_bucket_is_held);
-#endif
-
 static void *rht_obj(const struct rhashtable *ht, const struct rhash_head *he)
 {
 	return (void *) he - ht->p.head_offset;
@@ -103,6 +83,77 @@ static u32 head_hashfn(const struct rhashtable *ht,
 	return rht_bucket_index(tbl, obj_raw_hashfn(ht, rht_obj(ht, he)));
 }
 
+#ifdef CONFIG_PROVE_LOCKING
+static void debug_dump_buckets(const struct rhashtable *ht,
+			       const struct bucket_table *tbl)
+{
+	struct rhash_head *he;
+	unsigned int i, hash;
+
+	for (i = 0; i < tbl->size; i++) {
+		pr_warn(" [Bucket %d] ", i);
+		rht_for_each_rcu(he, tbl, i) {
+			hash = head_hashfn(ht, tbl, he);
+			pr_cont("[hash = %#x, lock = %p] ",
+				hash, bucket_lock(tbl, hash));
+		}
+		pr_cont("\n");
+	}
+
+}
+
+static void debug_dump_table(struct rhashtable *ht,
+			     const struct bucket_table *tbl,
+			     unsigned int hash)
+{
+	struct bucket_table *old_tbl, *future_tbl;
+
+	pr_emerg("BUG: lock for hash %#x in table %p not held\n",
+		 hash, tbl);
+
+	rcu_read_lock();
+	future_tbl = rht_dereference_rcu(ht->future_tbl, ht);
+	old_tbl = rht_dereference_rcu(ht->tbl, ht);
+	if (future_tbl != old_tbl) {
+		pr_warn("Future table %p (size: %zd)\n",
+			future_tbl, future_tbl->size);
+		debug_dump_buckets(ht, future_tbl);
+	}
+
+	pr_warn("Table %p (size: %zd)\n", old_tbl, old_tbl->size);
+	debug_dump_buckets(ht, old_tbl);
+
+	rcu_read_unlock();
+}
+
+#define ASSERT_RHT_MUTEX(HT) BUG_ON(!lockdep_rht_mutex_is_held(HT))
+#define ASSERT_BUCKET_LOCK(HT, TBL, HASH)				\
+	do {								\
+		if (unlikely(!lockdep_rht_bucket_is_held(TBL, HASH))) {	\
+			debug_dump_table(HT, TBL, HASH);		\
+			BUG();						\
+		}							\
+	} while (0)
+
+int lockdep_rht_mutex_is_held(struct rhashtable *ht)
+{
+	return (debug_locks) ? lockdep_is_held(&ht->mutex) : 1;
+}
+EXPORT_SYMBOL_GPL(lockdep_rht_mutex_is_held);
+
+int lockdep_rht_bucket_is_held(const struct bucket_table *tbl, u32 hash)
+{
+	spinlock_t *lock = bucket_lock(tbl, hash);
+
+	return (debug_locks) ? lockdep_is_held(lock) : 1;
+}
+EXPORT_SYMBOL_GPL(lockdep_rht_bucket_is_held);
+#else
+#define ASSERT_RHT_MUTEX(HT)
+#define ASSERT_BUCKET_LOCK(HT, TBL, HASH)
+#endif
+
+
 static struct rhash_head __rcu **bucket_tail(struct bucket_table *tbl, u32 n)
 {
 	struct rhash_head __rcu **pprev;
@@ -234,7 +285,7 @@ static void unlock_buckets(struct bucket_table *new_tbl,
  *
  * Returns true if no more work needs to be performed on the bucket.
  */
-static bool hashtable_chain_unzip(const struct rhashtable *ht,
+static bool hashtable_chain_unzip(struct rhashtable *ht,
 				  const struct bucket_table *new_tbl,
 				  struct bucket_table *old_tbl,
 				  size_t old_hash)
@@ -242,7 +293,7 @@ static bool hashtable_chain_unzip(const struct rhashtable *ht,
 	struct rhash_head *he, *p, *next;
 	unsigned int new_hash, new_hash2;
 
-	ASSERT_BUCKET_LOCK(old_tbl, old_hash);
+	ASSERT_BUCKET_LOCK(ht, old_tbl, old_hash);
 
 	/* Old bucket empty, no work needed. */
 	p = rht_dereference_bucket(old_tbl->buckets[old_hash], old_tbl,
@@ -251,7 +302,7 @@ static bool hashtable_chain_unzip(const struct rhashtable *ht,
 		return false;
 
 	new_hash = head_hashfn(ht, new_tbl, p);
-	ASSERT_BUCKET_LOCK(new_tbl, new_hash);
+	ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash);
 
 	/* Advance the old bucket pointer one or more times until it
 	 * reaches a node that doesn't hash to the same bucket as the
@@ -264,7 +315,7 @@ static bool hashtable_chain_unzip(const struct rhashtable *ht,
 		 * bucket lock. This is ensured because the bucket lock
 		 * hash map ignores the highest bit.
 		 */
-		ASSERT_BUCKET_LOCK(new_tbl, new_hash2);
+		ASSERT_BUCKET_LOCK(ht, new_tbl, new_hash2);
 
 		if (new_hash != new_hash2)
 			break;
-- 
1.9.3

^ permalink raw reply related

* [PATCH 3/6] rhashtable: Wait for RCU readers after final unzip work
From: Thomas Graf @ 2015-01-30  0:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ying.xue
In-Reply-To: <cover.1422576760.git.tgraf@suug.ch>

We need to wait for all RCU readers to complete after the last bit of
unzipping has been completed. Otherwise the old table is freed up
prematurely.

Fixes: 7e1e77636e36 ("lib: Resizable, Scalable, Concurrent Hash Table")
Signed-off-by: Thomas Graf <tgraf@suug.ch>
---
 lib/rhashtable.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index 17eeabc..85ec36b 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -391,6 +391,8 @@ int rhashtable_expand(struct rhashtable *ht)
 		}
 	}
 
+	synchronize_rcu();
+
 	bucket_table_free(old_tbl);
 	return 0;
 }
-- 
1.9.3

^ permalink raw reply related

* [PATCH 2/6] rhashtable: Use a single bucket lock for sibling buckets
From: Thomas Graf @ 2015-01-30  0:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ying.xue
In-Reply-To: <cover.1422576760.git.tgraf@suug.ch>

rhashtable currently allows to use a bucket lock per bucket. This
requires multiple levels of complicated nested locking because when
resizing, a single bucket of the smaller table will map to two
buckets in the larger table. So far rhashtable has explicitly locked
both buckets in the larger table.

By excluding the highest bit of the hash from the bucket lock map and
thus only allowing locks to buckets in a ratio of 1:2, the locking
can be simplified a lot without losing the benefits of multiple locks.
Larger tables which benefit from multiple locks will not have a single
lock per bucket anyway.

Signed-off-by: Thomas Graf <tgraf@suug.ch>
---
 lib/rhashtable.c | 166 +++++++++++++++++++++++--------------------------------
 1 file changed, 68 insertions(+), 98 deletions(-)

diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index 7413697..17eeabc 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -1,7 +1,7 @@
 /*
  * Resizable, Scalable, Concurrent Hash Table
  *
- * Copyright (c) 2014 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2014-2015 Thomas Graf <tgraf@suug.ch>
  * Copyright (c) 2008-2014 Patrick McHardy <kaber@trash.net>
  *
  * Based on the following paper:
@@ -34,7 +34,6 @@
 enum {
 	RHT_LOCK_NORMAL,
 	RHT_LOCK_NESTED,
-	RHT_LOCK_NESTED2,
 };
 
 /* The bucket lock is selected based on the hash and protects mutations
@@ -128,8 +127,8 @@ static int alloc_bucket_locks(struct rhashtable *ht, struct bucket_table *tbl)
 	nr_pcpus = min_t(unsigned int, nr_pcpus, 32UL);
 	size = roundup_pow_of_two(nr_pcpus * ht->p.locks_mul);
 
-	/* Never allocate more than one lock per bucket */
-	size = min_t(unsigned int, size, tbl->size);
+	/* Never allocate more than 0.5 locks per bucket */
+	size = min_t(unsigned int, size, tbl->size >> 1);
 
 	if (sizeof(spinlock_t) != 0) {
 #ifdef CONFIG_NUMA
@@ -211,13 +210,36 @@ bool rht_shrink_below_30(const struct rhashtable *ht, size_t new_size)
 }
 EXPORT_SYMBOL_GPL(rht_shrink_below_30);
 
-static void hashtable_chain_unzip(const struct rhashtable *ht,
+static void lock_buckets(struct bucket_table *new_tbl,
+			 struct bucket_table *old_tbl, unsigned int hash)
+	__acquires(old_bucket_lock)
+{
+	spin_lock_bh(bucket_lock(old_tbl, hash));
+	if (new_tbl != old_tbl)
+		spin_lock_bh_nested(bucket_lock(new_tbl, hash),
+				    RHT_LOCK_NESTED);
+}
+
+static void unlock_buckets(struct bucket_table *new_tbl,
+			   struct bucket_table *old_tbl, unsigned int hash)
+	__releases(old_bucket_lock)
+{
+	if (new_tbl != old_tbl)
+		spin_unlock_bh(bucket_lock(new_tbl, hash));
+	spin_unlock_bh(bucket_lock(old_tbl, hash));
+}
+
+/**
+ * Unlink entries on bucket which hash to different bucket.
+ *
+ * Returns true if no more work needs to be performed on the bucket.
+ */
+static bool hashtable_chain_unzip(const struct rhashtable *ht,
 				  const struct bucket_table *new_tbl,
 				  struct bucket_table *old_tbl,
 				  size_t old_hash)
 {
 	struct rhash_head *he, *p, *next;
-	spinlock_t *new_bucket_lock, *new_bucket_lock2 = NULL;
 	unsigned int new_hash, new_hash2;
 
 	ASSERT_BUCKET_LOCK(old_tbl, old_hash);
@@ -226,10 +248,10 @@ static void hashtable_chain_unzip(const struct rhashtable *ht,
 	p = rht_dereference_bucket(old_tbl->buckets[old_hash], old_tbl,
 				   old_hash);
 	if (rht_is_a_nulls(p))
-		return;
+		return false;
 
-	new_hash = new_hash2 = head_hashfn(ht, new_tbl, p);
-	new_bucket_lock = bucket_lock(new_tbl, new_hash);
+	new_hash = head_hashfn(ht, new_tbl, p);
+	ASSERT_BUCKET_LOCK(new_tbl, new_hash);
 
 	/* Advance the old bucket pointer one or more times until it
 	 * reaches a node that doesn't hash to the same bucket as the
@@ -237,22 +259,19 @@ static void hashtable_chain_unzip(const struct rhashtable *ht,
 	 */
 	rht_for_each_continue(he, p->next, old_tbl, old_hash) {
 		new_hash2 = head_hashfn(ht, new_tbl, he);
+
+		/* All entries in a chain must alwas map to a single
+		 * bucket lock. This is ensured because the bucket lock
+		 * hash map ignores the highest bit.
+		 */
+		ASSERT_BUCKET_LOCK(new_tbl, new_hash2);
+
 		if (new_hash != new_hash2)
 			break;
 		p = he;
 	}
 	rcu_assign_pointer(old_tbl->buckets[old_hash], p->next);
 
-	spin_lock_bh_nested(new_bucket_lock, RHT_LOCK_NESTED);
-
-	/* If we have encountered an entry that maps to a different bucket in
-	 * the new table, lock down that bucket as well as we might cut off
-	 * the end of the chain.
-	 */
-	new_bucket_lock2 = bucket_lock(new_tbl, new_hash);
-	if (new_bucket_lock != new_bucket_lock2)
-		spin_lock_bh_nested(new_bucket_lock2, RHT_LOCK_NESTED2);
-
 	/* Find the subsequent node which does hash to the same
 	 * bucket as node P, or NULL if no such node exists.
 	 */
@@ -271,21 +290,16 @@ static void hashtable_chain_unzip(const struct rhashtable *ht,
 	 */
 	rcu_assign_pointer(p->next, next);
 
-	if (new_bucket_lock != new_bucket_lock2)
-		spin_unlock_bh(new_bucket_lock2);
-	spin_unlock_bh(new_bucket_lock);
+	p = rht_dereference_bucket(old_tbl->buckets[old_hash], old_tbl,
+				   old_hash);
+
+	return !rht_is_a_nulls(p);
 }
 
 static void link_old_to_new(struct bucket_table *new_tbl,
 			    unsigned int new_hash, struct rhash_head *entry)
 {
-	spinlock_t *new_bucket_lock;
-
-	new_bucket_lock = bucket_lock(new_tbl, new_hash);
-
-	spin_lock_bh_nested(new_bucket_lock, RHT_LOCK_NESTED);
 	rcu_assign_pointer(*bucket_tail(new_tbl, new_hash), entry);
-	spin_unlock_bh(new_bucket_lock);
 }
 
 /**
@@ -308,7 +322,6 @@ int rhashtable_expand(struct rhashtable *ht)
 {
 	struct bucket_table *new_tbl, *old_tbl = rht_dereference(ht->tbl, ht);
 	struct rhash_head *he;
-	spinlock_t *old_bucket_lock;
 	unsigned int new_hash, old_hash;
 	bool complete = false;
 
@@ -338,16 +351,14 @@ int rhashtable_expand(struct rhashtable *ht)
 	 */
 	for (new_hash = 0; new_hash < new_tbl->size; new_hash++) {
 		old_hash = rht_bucket_index(old_tbl, new_hash);
-		old_bucket_lock = bucket_lock(old_tbl, old_hash);
-
-		spin_lock_bh(old_bucket_lock);
+		lock_buckets(new_tbl, old_tbl, new_hash);
 		rht_for_each(he, old_tbl, old_hash) {
 			if (head_hashfn(ht, new_tbl, he) == new_hash) {
 				link_old_to_new(new_tbl, new_hash, he);
 				break;
 			}
 		}
-		spin_unlock_bh(old_bucket_lock);
+		unlock_buckets(new_tbl, old_tbl, new_hash);
 	}
 
 	/* Publish the new table pointer. Lookups may now traverse
@@ -370,18 +381,13 @@ int rhashtable_expand(struct rhashtable *ht)
 		 */
 		complete = true;
 		for (old_hash = 0; old_hash < old_tbl->size; old_hash++) {
-			struct rhash_head *head;
-
-			old_bucket_lock = bucket_lock(old_tbl, old_hash);
-			spin_lock_bh(old_bucket_lock);
+			lock_buckets(new_tbl, old_tbl, old_hash);
 
-			hashtable_chain_unzip(ht, new_tbl, old_tbl, old_hash);
-			head = rht_dereference_bucket(old_tbl->buckets[old_hash],
-						      old_tbl, old_hash);
-			if (!rht_is_a_nulls(head))
+			if (hashtable_chain_unzip(ht, new_tbl, old_tbl,
+						  old_hash))
 				complete = false;
 
-			spin_unlock_bh(old_bucket_lock);
+			unlock_buckets(new_tbl, old_tbl, old_hash);
 		}
 	}
 
@@ -409,7 +415,6 @@ EXPORT_SYMBOL_GPL(rhashtable_expand);
 int rhashtable_shrink(struct rhashtable *ht)
 {
 	struct bucket_table *new_tbl, *tbl = rht_dereference(ht->tbl, ht);
-	spinlock_t *new_bucket_lock, *old_bucket_lock1, *old_bucket_lock2;
 	unsigned int new_hash;
 
 	ASSERT_RHT_MUTEX(ht);
@@ -432,31 +437,14 @@ int rhashtable_shrink(struct rhashtable *ht)
 	 * to lock down both matching buckets in the old table.
 	 */
 	for (new_hash = 0; new_hash < new_tbl->size; new_hash++) {
-		old_bucket_lock1 = bucket_lock(tbl, new_hash);
-		old_bucket_lock2 = bucket_lock(tbl, new_hash + new_tbl->size);
-		new_bucket_lock = bucket_lock(new_tbl, new_hash);
-
-		spin_lock_bh(old_bucket_lock1);
-
-		/* Depending on the lock per buckets mapping, the bucket in
-		 * the lower and upper region may map to the same lock.
-		 */
-		if (old_bucket_lock1 != old_bucket_lock2) {
-			spin_lock_bh_nested(old_bucket_lock2, RHT_LOCK_NESTED);
-			spin_lock_bh_nested(new_bucket_lock, RHT_LOCK_NESTED2);
-		} else {
-			spin_lock_bh_nested(new_bucket_lock, RHT_LOCK_NESTED);
-		}
+		lock_buckets(new_tbl, tbl, new_hash);
 
 		rcu_assign_pointer(*bucket_tail(new_tbl, new_hash),
 				   tbl->buckets[new_hash]);
 		rcu_assign_pointer(*bucket_tail(new_tbl, new_hash),
 				   tbl->buckets[new_hash + new_tbl->size]);
 
-		spin_unlock_bh(new_bucket_lock);
-		if (old_bucket_lock1 != old_bucket_lock2)
-			spin_unlock_bh(old_bucket_lock2);
-		spin_unlock_bh(old_bucket_lock1);
+		unlock_buckets(new_tbl, tbl, new_hash);
 	}
 
 	/* Publish the new, valid hash table */
@@ -539,19 +527,18 @@ static void __rhashtable_insert(struct rhashtable *ht, struct rhash_head *obj,
  */
 void rhashtable_insert(struct rhashtable *ht, struct rhash_head *obj)
 {
-	struct bucket_table *tbl;
-	spinlock_t *lock;
+	struct bucket_table *tbl, *old_tbl;
 	unsigned hash;
 
 	rcu_read_lock();
 
 	tbl = rht_dereference_rcu(ht->future_tbl, ht);
+	old_tbl = rht_dereference_rcu(ht->tbl, ht);
 	hash = head_hashfn(ht, tbl, obj);
-	lock = bucket_lock(tbl, hash);
 
-	spin_lock_bh(lock);
+	lock_buckets(tbl, old_tbl, hash);
 	__rhashtable_insert(ht, obj, tbl, hash);
-	spin_unlock_bh(lock);
+	unlock_buckets(tbl, old_tbl, hash);
 
 	rcu_read_unlock();
 }
@@ -574,21 +561,20 @@ EXPORT_SYMBOL_GPL(rhashtable_insert);
  */
 bool rhashtable_remove(struct rhashtable *ht, struct rhash_head *obj)
 {
-	struct bucket_table *tbl;
+	struct bucket_table *tbl, *new_tbl, *old_tbl;
 	struct rhash_head __rcu **pprev;
 	struct rhash_head *he;
-	spinlock_t *lock;
-	unsigned int hash;
+	unsigned int hash, new_hash;
 	bool ret = false;
 
 	rcu_read_lock();
-	tbl = rht_dereference_rcu(ht->tbl, ht);
-	hash = head_hashfn(ht, tbl, obj);
-
-	lock = bucket_lock(tbl, hash);
-	spin_lock_bh(lock);
+	tbl = old_tbl = rht_dereference_rcu(ht->tbl, ht);
+	new_tbl = rht_dereference_rcu(ht->future_tbl, ht);
+	new_hash = head_hashfn(ht, new_tbl, obj);
 
+	lock_buckets(new_tbl, old_tbl, new_hash);
 restart:
+	hash = rht_bucket_index(tbl, new_hash);
 	pprev = &tbl->buckets[hash];
 	rht_for_each(he, tbl, hash) {
 		if (he != obj) {
@@ -607,18 +593,12 @@ restart:
 	 * resizing. Thus traversing both is fine and the added cost is
 	 * very rare.
 	 */
-	if (tbl != rht_dereference_rcu(ht->future_tbl, ht)) {
-		spin_unlock_bh(lock);
-
-		tbl = rht_dereference_rcu(ht->future_tbl, ht);
-		hash = head_hashfn(ht, tbl, obj);
-
-		lock = bucket_lock(tbl, hash);
-		spin_lock_bh(lock);
+	if (tbl != new_tbl) {
+		tbl = new_tbl;
 		goto restart;
 	}
 
-	spin_unlock_bh(lock);
+	unlock_buckets(new_tbl, old_tbl, new_hash);
 
 	if (ret) {
 		atomic_dec(&ht->nelems);
@@ -774,24 +754,17 @@ bool rhashtable_lookup_compare_insert(struct rhashtable *ht,
 				      void *arg)
 {
 	struct bucket_table *new_tbl, *old_tbl;
-	spinlock_t *new_bucket_lock, *old_bucket_lock;
-	u32 new_hash, old_hash;
+	u32 new_hash;
 	bool success = true;
 
 	BUG_ON(!ht->p.key_len);
 
 	rcu_read_lock();
-
 	old_tbl = rht_dereference_rcu(ht->tbl, ht);
-	old_hash = head_hashfn(ht, old_tbl, obj);
-	old_bucket_lock = bucket_lock(old_tbl, old_hash);
-	spin_lock_bh(old_bucket_lock);
-
 	new_tbl = rht_dereference_rcu(ht->future_tbl, ht);
 	new_hash = head_hashfn(ht, new_tbl, obj);
-	new_bucket_lock = bucket_lock(new_tbl, new_hash);
-	if (unlikely(old_tbl != new_tbl))
-		spin_lock_bh_nested(new_bucket_lock, RHT_LOCK_NESTED);
+
+	lock_buckets(new_tbl, old_tbl, new_hash);
 
 	if (rhashtable_lookup_compare(ht, rht_obj(ht, obj) + ht->p.key_offset,
 				      compare, arg)) {
@@ -802,10 +775,7 @@ bool rhashtable_lookup_compare_insert(struct rhashtable *ht,
 	__rhashtable_insert(ht, obj, new_tbl, new_hash);
 
 exit:
-	if (unlikely(old_tbl != new_tbl))
-		spin_unlock_bh(new_bucket_lock);
-	spin_unlock_bh(old_bucket_lock);
-
+	unlock_buckets(new_tbl, old_tbl, new_hash);
 	rcu_read_unlock();
 
 	return success;
-- 
1.9.3

^ permalink raw reply related

* [PATCH 1/6] rhashtable: key_hashfn() must return full hash value
From: Thomas Graf @ 2015-01-30  0:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ying.xue
In-Reply-To: <cover.1422576760.git.tgraf@suug.ch>

The value computed by key_hashfn() is used by rhashtable_lookup_compare()
to traverse both tables during a resize. key_hashfn() must therefore
return the hash value without the buckets mask applied so it can be
masked to the size of each individual table.

Fixes: 97defe1ecf86 ("rhashtable: Per bucket locks & deferred expansion/shrinking")
Signed-off-by: Thomas Graf <tgraf@suug.ch>
---
 lib/rhashtable.c | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index bc2d0d8..7413697 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -94,13 +94,7 @@ static u32 obj_raw_hashfn(const struct rhashtable *ht, const void *ptr)
 
 static u32 key_hashfn(struct rhashtable *ht, const void *key, u32 len)
 {
-	struct bucket_table *tbl = rht_dereference_rcu(ht->tbl, ht);
-	u32 hash;
-
-	hash = ht->p.hashfn(key, len, ht->p.hash_rnd);
-	hash >>= HASH_RESERVED_SPACE;
-
-	return rht_bucket_index(tbl, hash);
+	return ht->p.hashfn(key, len, ht->p.hash_rnd) >> HASH_RESERVED_SPACE;
 }
 
 static u32 head_hashfn(const struct rhashtable *ht,
-- 
1.9.3

^ permalink raw reply related

* [PATCH 0/6 net-next] rhashtable fixes
From: Thomas Graf @ 2015-01-30  0:20 UTC (permalink / raw)
  To: davem; +Cc: netdev, ying.xue

This is a series of fixes which have accumulated while tracking
down the race condition reoprted by Ying Xue. The original
DEBUG_PAGEALLOC splat is resolved.

However, there is still a race (harder to trigger) remaining in
which certain entries are unfindable when removing them from the
table via netlink_remove() and thus they cause a use after free
later on.

Regardless, these fixes can go in now.

Thomas Graf (6):
  rhashtable: key_hashfn() must return full hash value
  rhashtable: Use a single bucket lock for sibling buckets
  rhashtable: Wait for RCU readers after final unzip work
  rhashtable: Dump bucket tables on locking violation under
    PROVE_LOCKING
  rhashtable: Add more lock verification
  rhashtable: Avoid bucket cross reference after removal

 lib/rhashtable.c | 301 ++++++++++++++++++++++++++++++-------------------------
 1 file changed, 166 insertions(+), 135 deletions(-)

-- 
1.9.3

^ permalink raw reply

* Re: [PATCH 2/4] NFC: nxp-nci: Add support for NXP NCI chips
From: Samuel Ortiz @ 2015-01-30  0:10 UTC (permalink / raw)
  To: clement.perrochaud-BPEPtVrvniRWk0Htik3J/w
  Cc: linux-nfc-hn68Rpc1hR1g9hUCZPvPmw, Clément Perrochaud,
	sunil.jogi-3arQi8VN3Tc, jerome.pele-3arQi8VN3Tc,
	Charles.Gorand-Effinnov-3arQi8VN3Tc,
	lauro.venancio-430g2QfJUUCGglJvpFV4uA,
	aloisio.almeida-430g2QfJUUCGglJvpFV4uA,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A, pawel.moll-5wv7dgnIgG8,
	mark.rutland-5wv7dgnIgG8, ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg,
	galak-sgV2jX0FEOL9JmXXK+q4OQ, davem-fT/PcQaiUtIeIZ0/mPfg9Q,
	grant.likely-QSEj5FYQhm4dnm+yROfE0A,
	lefrique-eYqpPyKDWXRBDgjK7y7TUQ,
	christophe.ricard-Re5JQEeQqe8AvxtiuMwx3w,
	cuissard-eYqpPyKDWXRBDgjK7y7TUQ, bzhao-eYqpPyKDWXRBDgjK7y7TUQ,
	hirent-eYqpPyKDWXRBDgjK7y7TUQ, akarwar-eYqpPyKDWXRBDgjK7y7TUQ,
	linux-wireless-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	netdev-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1421940460-14049-3-git-send-email-clement.perrochaud-BPEPtVrvniRWk0Htik3J/w@public.gmane.org>

Hi Clement,

On Thu, Jan 22, 2015 at 04:27:38PM +0100, clement.perrochaud-BPEPtVrvniRWk0Htik3J/w@public.gmane.org wrote:
> @@ -686,7 +686,7 @@ L:	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw@public.gmane.org (moderated for non-subscribers)
>  W:	http://blackfin.uclinux.org/
>  S:	Supported
>  F:	sound/soc/blackfin/*
> - 
> +
Unneeded change, please remove it.

> +config NFC_NXP_NCI
> +	tristate "NXP-NCI NFC driver"
> +	depends on NFC_NCI
> +	default n
> +	---help---
> +	  Generic core driver for NXP NCI chips.
It usually is nice to be more specific and describe which chipsets
(e.g. NPC100) this driver supports among all the NXP NCI chipsets.

> +struct nxp_nci_phy_ops {
> +	int (*enable)(void *id);
> +	int (*fw_enable)(void *id);
Wouldn't a int (*enable)(void *id, enum nxp_nci_mode mode); hook be
enough ?

Cheers,
Samuel.
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply

* Re: [PATCH 4/4] NFC: nxp-nci: Allow module removal during download
From: Samuel Ortiz @ 2015-01-30  0:08 UTC (permalink / raw)
  To: clement.perrochaud
  Cc: linux-nfc, Clément Perrochaud, sunil.jogi, jerome.pele,
	Charles.Gorand-Effinnov, lauro.venancio, aloisio.almeida, robh+dt,
	pawel.moll, mark.rutland, ijc+devicetree, galak, davem,
	grant.likely, lefrique, christophe.ricard, cuissard, bzhao,
	hirent, akarwar, linux-wireless, devicetree, linux-kernel, netdev
In-Reply-To: <1421940460-14049-5-git-send-email-clement.perrochaud@effinnov.com>

Hi Clement,

On Thu, Jan 22, 2015 at 04:27:40PM +0100, clement.perrochaud@effinnov.com wrote:
> From: Clément Perrochaud <clement.perrochaud@nxp.com>
> 
> Signed-off-by: Clément Perrochaud <clement.perrochaud@nxp.com>
> Signed-off-by: Clément Perrochaud <clement.perrochaud@effinnov.com>
> ---
>  drivers/nfc/nxp-nci/core.c     | 7 +++----
>  drivers/nfc/nxp-nci/firmware.c | 9 ++++++---
>  2 files changed, 9 insertions(+), 7 deletions(-)
Please squash that one into the first patch.

Cheers,
Samuel.

^ permalink raw reply

* Re: [PATCH 3/4] NFC: nxp-nci_i2c: Add I2C support to NXP NCI driver
From: Samuel Ortiz @ 2015-01-30  0:07 UTC (permalink / raw)
  To: clement.perrochaud
  Cc: linux-nfc, Clément Perrochaud, sunil.jogi, jerome.pele,
	Charles.Gorand-Effinnov, lauro.venancio, aloisio.almeida, robh+dt,
	pawel.moll, mark.rutland, ijc+devicetree, galak, davem,
	grant.likely, lefrique, christophe.ricard, cuissard, bzhao,
	hirent, akarwar, linux-wireless, devicetree, linux-kernel, netdev
In-Reply-To: <1421940460-14049-4-git-send-email-clement.perrochaud@effinnov.com>

Hi Clement,

On Thu, Jan 22, 2015 at 04:27:39PM +0100, clement.perrochaud@effinnov.com wrote:
> From: Clément Perrochaud <clement.perrochaud@nxp.com>
> 
> Signed-off-by: Clément Perrochaud <clement.perrochaud@nxp.com>
> Signed-off-by: Clément Perrochaud <clement.perrochaud@effinnov.com>
Are you sure you want both S-O-B lines ?


> +static int nxp_nci_i2c_fw_read(struct nxp_nci_i2c_phy *phy,
> +			       struct sk_buff **skb)
> +{
> +	struct i2c_client *client = phy->i2c_dev;
> +	u16 header;
> +	size_t frame_len;
> +	int r;
> +
> +	r = i2c_master_recv(client, (u8 *) &header, NXP_NCI_FW_HDR_LEN);
> +	if (r < 0) {
> +		goto fw_read_exit;
> +	} else if (r != NXP_NCI_FW_HDR_LEN) {
> +		nfc_err(&client->dev, "Incorrect header length: %u\n", r);
> +		r = -EBADMSG;
> +		goto fw_read_exit;
> +	}
> +
> +	frame_len = (get_unaligned_be16(&header) & NXP_NCI_FW_FRAME_LEN_MASK) +
> +		    NXP_NCI_FW_CRC_LEN;
> +
> +	*skb = alloc_skb(NXP_NCI_FW_HDR_LEN + frame_len, GFP_KERNEL);
> +	if (*skb == NULL) {
> +		r = -ENOMEM;
> +		goto fw_read_exit;
> +	}
> +
> +	memcpy(skb_put(*skb, NXP_NCI_FW_HDR_LEN), &header, NXP_NCI_FW_HDR_LEN);
> +
> +	r = i2c_master_recv(client, skb_put(*skb, frame_len), frame_len);
> +	if (r != frame_len) {
> +		nfc_err(&client->dev,
> +			"Invalid frame length: %u (expected %u)\n",
> +			r, frame_len);
> +		r = -EBADMSG;
> +		goto fw_read_exit_free_skb;
> +	}
> +
> +	r = 0;
> +	goto fw_read_exit;
return 0; is enough.

> +static int nxp_nci_i2c_nci_read(struct nxp_nci_i2c_phy *phy,
> +				struct sk_buff **skb)
> +{
> +	struct nci_ctrl_hdr header; /* May actually be a data header */
> +	struct i2c_client *client = phy->i2c_dev;
> +	int r;
> +
> +	r = i2c_master_recv(client, (u8 *) &header, NCI_CTRL_HDR_SIZE);
> +	if (r < 0) {
> +		goto nci_read_exit;
> +	} else if (r != NCI_CTRL_HDR_SIZE) {
> +		nfc_err(&client->dev, "Incorrect header length: %u\n", r);
> +		r = -EBADMSG;
> +		goto nci_read_exit;
> +	}
> +
> +	*skb = alloc_skb(NCI_CTRL_HDR_SIZE + header.plen, GFP_KERNEL);
> +	if (*skb == NULL) {
> +		r = -ENOMEM;
> +		goto nci_read_exit;
> +	}
> +
> +	memcpy(skb_put(*skb, NCI_CTRL_HDR_SIZE), (void *) &header,
> +	       NCI_CTRL_HDR_SIZE);
> +
> +	r = i2c_master_recv(client, skb_put(*skb, header.plen), header.plen);
> +	if (r != header.plen) {
> +		nfc_err(&client->dev,
> +			"Invalid frame payload length: %u (expected %u)\n",
> +			r, header.plen);
> +		r = -EBADMSG;
> +		goto nci_read_exit_free_skb;
> +	}
> +
> +	r = 0;
> +	goto nci_read_exit;
Ditto.

> +static irqreturn_t nxp_nci_i2c_irq_thread_fn(int irq, void *phy_id)
> +{
> +	struct nxp_nci_i2c_phy *phy = phy_id;
> +	struct i2c_client *client;
> +	struct nxp_nci_info *info;
> +
> +	struct sk_buff *skb = NULL;
> +	int r = 0;
> +
> +	if (!phy || !phy->ndev)
> +		goto exit_irq_none;
> +
> +	client = phy->i2c_dev;
> +
> +	if (!client || irq != client->irq)
> +		goto exit_irq_none;
> +
> +	if (phy->hard_fault != 0)
> +		goto exit_irq_handled;
> +
> +	info = nci_get_drvdata(phy->ndev);

You probably want to take the info_lock mutex here.

> +	switch (info->mode) {
> +	case NXP_NCI_MODE_NCI:
> +		r = nxp_nci_i2c_nci_read(phy, &skb);
> +		break;
> +	case NXP_NCI_MODE_FW:
> +		r = nxp_nci_i2c_fw_read(phy, &skb);
> +		break;
> +	case NXP_NCI_MODE_COLD:
> +		r = -EREMOTEIO;
> +		break;
> +	}
> +
> +	if (r == -EREMOTEIO) {
> +		phy->hard_fault = r;
> +		skb = NULL;
> +	} else if (r < 0) {
> +		nfc_err(&client->dev, "Read failed with error %d\n", r);
> +		goto exit_irq_handled;
> +	}
> +
> +	switch (info->mode) {
> +	case NXP_NCI_MODE_NCI:
> +		nci_recv_frame(phy->ndev, skb);
> +		break;
> +	case NXP_NCI_MODE_FW:
> +		nxp_nci_fw_recv_frame(phy->ndev, skb);
> +		break;
> +	case NXP_NCI_MODE_COLD:
> +		break;
> +	}
> +
> +exit_irq_handled:
> +	return IRQ_HANDLED;
> +exit_irq_none:
> +	WARN_ON_ONCE(1);
> +	return IRQ_NONE;
> +}
> +static int nxp_nci_i2c_request_gpios(struct nxp_nci_i2c_phy *phy)
> +{
> +	int r;
> +
> +	r = gpio_request(phy->gpio_en, "nxp_nci_en");
Please use the devm_gpiod_* API so that you don't need to free GPIOs
explicitely.


> +	if (r < 0)
> +		goto err_out;
> +
> +	r = gpio_direction_output(phy->gpio_en, 0);
> +	if (r < 0)
> +		goto err_gpio_en;
> +
> +	r = gpio_request(phy->gpio_fw, "nxp_nci_fw");
> +	if (r < 0)
> +		goto err_gpio_en;
> +
> +	r = gpio_direction_output(phy->gpio_fw, 0);
> +	if (r < 0)
> +		goto err_gpio_fw;
> +
> +	goto err_out;
> +
> +err_gpio_fw:
> +	gpio_free(phy->gpio_fw);
> +err_gpio_en:
> +	gpio_free(phy->gpio_en);
> +err_out:
> +	return r;
> +}
> +
> +static void nxp_nci_i2c_free_gpios(struct nxp_nci_i2c_phy *phy)
> +{
> +	gpio_free(phy->gpio_fw);
> +	gpio_free(phy->gpio_en);
> +}
You would no longer need that one with devm_gpiod_*.

^ permalink raw reply

* [PATCH net-next] ipv4: icmp: use percpu allocation
From: Eric Dumazet @ 2015-01-29 23:58 UTC (permalink / raw)
  To: David Miller; +Cc: netdev

From: Eric Dumazet <edumazet@google.com>

Get rid of nr_cpu_ids and use modern percpu allocation.

Note that the sockets themselves are not yet allocated
using NUMA affinity.

Signed-off-by: Eric Dumazet <edumazet@google.com>
---
 include/net/netns/ipv4.h |    3 ++-
 net/ipv4/icmp.c          |   17 ++++++++---------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h
index 24945cefc4fd..7283f4d39ae2 100644
--- a/include/net/netns/ipv4.h
+++ b/include/net/netns/ipv4.h
@@ -48,7 +48,8 @@ struct netns_ipv4 {
 	struct hlist_head	*fib_table_hash;
 	struct sock		*fibnl;
 
-	struct sock		**icmp_sk;
+	struct sock  * __percpu	*icmp_sk;
+
 	struct inet_peer_base	*peers;
 	struct tcpm_hash_bucket	*tcp_metrics_hash;
 	unsigned int		tcp_metrics_hash_log;
diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c
index 36f5584d93c5..5e564014a0b7 100644
--- a/net/ipv4/icmp.c
+++ b/net/ipv4/icmp.c
@@ -205,7 +205,7 @@ static const struct icmp_control icmp_pointers[NR_ICMP_TYPES+1];
  */
 static struct sock *icmp_sk(struct net *net)
 {
-	return net->ipv4.icmp_sk[smp_processor_id()];
+	return *this_cpu_ptr(net->ipv4.icmp_sk);
 }
 
 static inline struct sock *icmp_xmit_lock(struct net *net)
@@ -1140,8 +1140,8 @@ static void __net_exit icmp_sk_exit(struct net *net)
 	int i;
 
 	for_each_possible_cpu(i)
-		inet_ctl_sock_destroy(net->ipv4.icmp_sk[i]);
-	kfree(net->ipv4.icmp_sk);
+		inet_ctl_sock_destroy(*per_cpu_ptr(net->ipv4.icmp_sk, i));
+	free_percpu(net->ipv4.icmp_sk);
 	net->ipv4.icmp_sk = NULL;
 }
 
@@ -1149,9 +1149,8 @@ static int __net_init icmp_sk_init(struct net *net)
 {
 	int i, err;
 
-	net->ipv4.icmp_sk =
-		kzalloc(nr_cpu_ids * sizeof(struct sock *), GFP_KERNEL);
-	if (net->ipv4.icmp_sk == NULL)
+	net->ipv4.icmp_sk = alloc_percpu(struct sock *);
+	if (!net->ipv4.icmp_sk)
 		return -ENOMEM;
 
 	for_each_possible_cpu(i) {
@@ -1162,7 +1161,7 @@ static int __net_init icmp_sk_init(struct net *net)
 		if (err < 0)
 			goto fail;
 
-		net->ipv4.icmp_sk[i] = sk;
+		*per_cpu_ptr(net->ipv4.icmp_sk, i) = sk;
 
 		/* Enough space for 2 64K ICMP packets, including
 		 * sk_buff/skb_shared_info struct overhead.
@@ -1203,8 +1202,8 @@ static int __net_init icmp_sk_init(struct net *net)
 
 fail:
 	for_each_possible_cpu(i)
-		inet_ctl_sock_destroy(net->ipv4.icmp_sk[i]);
-	kfree(net->ipv4.icmp_sk);
+		inet_ctl_sock_destroy(*per_cpu_ptr(net->ipv4.icmp_sk, i));
+	free_percpu(net->ipv4.icmp_sk);
 	return err;
 }
 

^ permalink raw reply related

* Re: [PATCH net-next v8 0/4] net: Add Keystone NetCP ethernet driver support
From: Arnd Bergmann @ 2015-01-29 23:49 UTC (permalink / raw)
  To: Murali Karicheri
  Cc: Balbi, Felipe, mugunthanvnm, David Miller, devicetree,
	linux-kernel, netdev
In-Reply-To: <54CAC038.9000009@ti.com>

On Thursday 29 January 2015 18:20:24 Murali Karicheri wrote:
> 
> I have just posted a patch to nedev list to address this issue. Please 
> review and let me know your comments. This also require some additional 
> fixes in drivers/soc/ti/ to build them as modules as well. Santosh has 
> queued them against next merge window.

Ok, thanks for addressing this! As I wrote in reply to your patch, I'll
give it some extra build testing in random configurations.

	Arnd

^ permalink raw reply

* Re: [PATCH net-next] drivers: net: cpsw: make cpsw_ale.c a module to allow re-use on Keystone
From: Arnd Bergmann @ 2015-01-29 23:47 UTC (permalink / raw)
  To: Murali Karicheri
  Cc: w-kwok2, davem, mugunthanvnm, tony, prabhakar.csengg,
	grygorii.strashko, lokeshvutla, mpa, lsorense, netdev,
	linux-kernel
In-Reply-To: <1422573351-5603-2-git-send-email-m-karicheri2@ti.com>

On Thursday 29 January 2015 18:15:51 Murali Karicheri wrote:
> NetCP on Keystone has cpsw ale function similar to other TI SoCs
> and this driver is re-used. To allow both ti cpsw and keystone netcp
> to re-use the driver, convert the cpsw ale to a module and configure
> it through Kconfig option CONFIG_TI_CPSW_ALE. Currently it is statically
> linked to both TI CPSW and NetCP and this causes issues when the above
> drivers are built as dynamic modules. This patch addresses this issue
> 
> While at it, fix the Makefile and code to build both netcp_core and
> netcp_ethss as dynamic modules. This is needed to support arm allmodconfig.
> This also requires exporting of API calls provided by netcp_core so that
> both the above can be dynamic modules.
> 
> Signed-off-by: Murali Karicheri <m-karicheri2@ti.com>
> ---
>  drivers/net/ethernet/ti/Kconfig       |   19 +++++++++++++++++--
>  drivers/net/ethernet/ti/Makefile      |    8 +++++---
>  drivers/net/ethernet/ti/cpsw_ale.c    |   26 ++++++++++++++++++++++++--
>  drivers/net/ethernet/ti/netcp_core.c  |    8 ++++++++
>  drivers/net/ethernet/ti/netcp_ethss.c |    5 +++++
>  5 files changed, 59 insertions(+), 7 deletions(-)

I was hoping there would be a way without exporting all those symbols, but
I also couldn't come up with a better solution. I'm putting this into the
randconfig build test for now, but I'm guessing it's fine.

Unless you hear something else from me tomorrow after the tests are through:

Acked-by: Arnd Bergmann <arnd@arndb.de>

^ permalink raw reply

* [PATCH] iproute2: Add support for `ip link stt`
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar

Following patch add support for STT device management.
`ip link` command is extended to support ip_stt device.

Using `ip link` command user can configure STT key, source
addr, destination addr, TCP port, ToS, TTL, DF and link.
Syntax is same as other tunneling devices.

STT protocol is documented at:
http://www.ietf.org/archive/id/draft-davie-stt-06.txt

Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
---
To use this patch to create STT, first if_tunnel.h needs to be
update with STT netlink attributes from STT patch.
---
 include/utils.h |  11 ++
 ip/Makefile     |   2 +-
 ip/link_stt.c   | 309 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 321 insertions(+), 1 deletion(-)
 create mode 100644 ip/link_stt.c

diff --git a/include/utils.h b/include/utils.h
index e1fe7cf..f1060f8 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -162,4 +162,15 @@ int iplink_parse(int argc, char **argv, struct iplink_req *req,
 		char **name, char **type, char **link, char **dev,
 		int *group, int *index);
 
+static inline uint64_t
+htonll(uint64_t n)
+{
+    return htonl(1) == 1 ? n : ((uint64_t) htonl(n) << 32) | htonl(n >> 32);
+}
+
+static inline uint64_t
+ntohll(uint64_t n)
+{
+    return htonl(1) == 1 ? n : ((uint64_t) ntohl(n) << 32) | ntohl(n >> 32);
+}
 #endif /* __UTILS_H__ */
diff --git a/ip/Makefile b/ip/Makefile
index 2c742f3..e7b5415 100644
--- a/ip/Makefile
+++ b/ip/Makefile
@@ -6,7 +6,7 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \
     iplink_macvlan.o iplink_macvtap.o ipl2tp.o link_vti.o link_vti6.o \
     iplink_vxlan.o tcp_metrics.o iplink_ipoib.o ipnetconf.o link_ip6tnl.o \
     link_iptnl.o link_gre6.o iplink_bond.o iplink_bond_slave.o iplink_hsr.o \
-    iplink_bridge.o iplink_bridge_slave.o ipfou.o iplink_ipvlan.o
+    iplink_bridge.o iplink_bridge_slave.o ipfou.o iplink_ipvlan.o link_stt.o
 
 RTMONOBJ=rtmon.o
 
diff --git a/ip/link_stt.c b/ip/link_stt.c
new file mode 100644
index 0000000..2ca9804
--- /dev/null
+++ b/ip/link_stt.c
@@ -0,0 +1,309 @@
+/*
+ * link_stt.c	STT driver module
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		as published by the Free Software Foundation; either version
+ *		2 of the License, or (at your option) any later version.
+ *
+ * Authors:	Pravin Shelar <pshelar@nicira.com>
+ */
+
+#include <string.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "tunnel.h"
+
+#define STT_DST_PORT 7471
+
+static void print_usage(FILE *f)
+{
+	fprintf(f, "Usage: ip link { add | set | change | replace | del } NAME\n");
+	fprintf(f, "          type { stt } [ remote ADDR ] [ local ADDR ]\n");
+	fprintf(f, "          [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ]\n");
+	fprintf(f, "          [ port tcp_port ]\n");
+	fprintf(f, "          [ [i|o]key KEY ]\n");
+	fprintf(f, "          [ dev PHYS_DEV ]\n");
+	fprintf(f, "\n");
+	fprintf(f, "Where: NAME := STRING\n");
+	fprintf(f, "       ADDR := { IP_ADDRESS }\n");
+	fprintf(f, "       KEY  := { DOTTED_QUAD | NUMBER }\n");
+}
+
+static void usage(void) __attribute__((noreturn));
+static void usage(void)
+{
+	print_usage(stderr);
+	exit(-1);
+}
+
+static int stt_parse_opt(struct link_util *lu, int argc, char **argv,
+			 struct nlmsghdr *n)
+{
+	struct {
+		struct nlmsghdr n;
+		struct ifinfomsg i;
+		char buf[1024];
+	} req;
+	struct ifinfomsg *ifi = (struct ifinfomsg *)(n + 1);
+	struct rtattr *tb[IFLA_MAX + 1];
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
+	struct rtattr *sttinfo[IFLA_STT_MAX + 1];
+	unsigned dst_port = STT_DST_PORT;
+	__u64 ikey = 0;
+	__u64 okey = 0;
+	unsigned saddr = 0;
+	unsigned daddr = 0;
+	unsigned link = 0;
+	__u8 pmtudisc = 1;
+	int len;
+	__u8 ttl = 0;
+	__u8 tos = 0;
+
+	if (!(n->nlmsg_flags & NLM_F_CREATE)) {
+		memset(&req, 0, sizeof(req));
+
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.n.nlmsg_type = RTM_GETLINK;
+		req.i.ifi_family = preferred_family;
+		req.i.ifi_index = ifi->ifi_index;
+
+		if (rtnl_talk(&rth, &req.n, 0, 0, &req.n) < 0) {
+get_failed:
+			fprintf(stderr,
+				"Failed to get existing tunnel info.\n");
+			return -1;
+		}
+
+		len = req.n.nlmsg_len;
+		len -= NLMSG_LENGTH(sizeof(*ifi));
+		if (len < 0)
+			goto get_failed;
+
+		parse_rtattr(tb, IFLA_MAX, IFLA_RTA(&req.i), len);
+
+		if (!tb[IFLA_LINKINFO])
+			goto get_failed;
+
+		parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]);
+
+		if (!linkinfo[IFLA_INFO_DATA])
+			goto get_failed;
+
+		parse_rtattr_nested(sttinfo, IFLA_STT_MAX,
+				    linkinfo[IFLA_INFO_DATA]);
+
+		if (sttinfo[IFLA_STT_IKEY])
+			ikey = ntohll(*(__u64 *)RTA_DATA(sttinfo[IFLA_STT_IKEY]));
+
+		if (sttinfo[IFLA_STT_OKEY])
+			okey = ntohll(*(__u64 *)RTA_DATA(sttinfo[IFLA_STT_OKEY]));
+
+		if (sttinfo[IFLA_STT_LOCAL])
+			saddr = *(__u32 *)RTA_DATA(sttinfo[IFLA_STT_LOCAL]);
+
+		if (sttinfo[IFLA_STT_REMOTE])
+			daddr = *(__u32 *)RTA_DATA(sttinfo[IFLA_STT_REMOTE]);
+
+		if (sttinfo[IFLA_STT_TTL])
+			ttl = rta_getattr_u8(sttinfo[IFLA_STT_TTL]);
+
+		if (sttinfo[IFLA_STT_TOS])
+			tos = rta_getattr_u8(sttinfo[IFLA_STT_TOS]);
+
+		if (sttinfo[IFLA_STT_LINK])
+			link = *(__u8 *)RTA_DATA(sttinfo[IFLA_STT_LINK]);
+
+		if (sttinfo[IFLA_STT_DST_PORT])
+			dst_port = *(__u16 *)RTA_DATA(sttinfo[IFLA_STT_DST_PORT]);
+
+		if (sttinfo[IFLA_STT_DF])
+			pmtudisc = 1;
+	}
+
+	while (argc > 0) {
+		if (!matches(*argv, "key")) {
+			__u64 uval;
+
+			NEXT_ARG();
+			if (get_u64(&uval, *argv, 0) < 0) {
+				fprintf(stderr,
+					"Invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
+				exit(-1);
+			}
+			ikey = okey = uval;
+		} else if (!matches(*argv, "ikey")) {
+			__u64 uval;
+
+			NEXT_ARG();
+			if (get_u64(&uval, *argv, 0) < 0) {
+				fprintf(stderr,
+					"Invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
+				exit(-1);
+			}
+
+			ikey = uval;
+		} else if (!matches(*argv, "okey")) {
+			__u64 uval;
+
+			NEXT_ARG();
+			if (get_u64(&uval, *argv, 0) < 0) {
+				fprintf(stderr,
+					"Invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
+				exit(-1);
+			}
+			okey = uval;
+		} else if (!matches(*argv, "remote")) {
+			NEXT_ARG();
+			if (!strcmp(*argv, "any")) {
+				fprintf(stderr, "invalid value for \"remote\": \"%s\"\n", *argv);
+				exit(-1);
+			} else {
+				daddr = get_addr32(*argv);
+			}
+		} else if (!matches(*argv, "local")) {
+			NEXT_ARG();
+			if (!strcmp(*argv, "any")) {
+				fprintf(stderr, "invalid value for \"local\": \"%s\"\n", *argv);
+				exit(-1);
+			} else {
+				saddr = get_addr32(*argv);
+			}
+		} else if (!matches(*argv, "dev")) {
+			NEXT_ARG();
+			link = if_nametoindex(*argv);
+			if (link == 0) {
+				fprintf(stderr, "Cannot find device \"%s\"\n",
+					*argv);
+				exit(-1);
+			}
+		} else if (!matches(*argv, "port")) {
+			__u16 uval;
+
+			NEXT_ARG();
+			if (get_u16(&uval, *argv, 0) < 0) {
+				fprintf(stderr,
+					"Invalid value for \"key\": \"%s\"; it should be an unsigned integer\n", *argv);
+				exit(-1);
+			}
+			dst_port = uval;
+		} else if (!matches(*argv, "nopmtudisc")) {
+			pmtudisc = 0;
+		} else if (!matches(*argv, "pmtudisc")) {
+			pmtudisc = 1;
+		} else
+			usage();
+		argc--; argv++;
+	}
+
+	addattr64(n, 1024, IFLA_STT_IKEY, htonll(ikey));
+	addattr64(n, 1024, IFLA_STT_OKEY, htonll(okey));
+	addattr16(n, 1024, IFLA_STT_DST_PORT, dst_port);
+	addattr_l(n, 1024, IFLA_STT_LOCAL, &saddr, 4);
+	addattr_l(n, 1024, IFLA_STT_REMOTE, &daddr, 4);
+	addattr_l(n, 1024, IFLA_STT_TTL, &ttl, 1);
+	addattr_l(n, 1024, IFLA_STT_TOS, &tos, 1);
+
+	if (pmtudisc)
+		addattr(n, 1024, IFLA_STT_DF);
+	if (link)
+		addattr32(n, 1024, IFLA_STT_LINK, link);
+
+	return 0;
+}
+
+static void stt_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
+{
+	char s1[1024];
+	char s2[64];
+	const char *local = "any";
+	const char *remote = "any";
+
+	if (!tb)
+		return;
+
+	if (tb[IFLA_STT_REMOTE]) {
+		unsigned addr = *(__u32 *)RTA_DATA(tb[IFLA_STT_REMOTE]);
+
+		if (addr)
+			remote = format_host(AF_INET, 4, &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "remote %s ", remote);
+
+	if (tb[IFLA_STT_LOCAL]) {
+		unsigned addr = *(__u32 *)RTA_DATA(tb[IFLA_STT_LOCAL]);
+
+		if (addr)
+			local = format_host(AF_INET, 4, &addr, s1, sizeof(s1));
+	}
+
+	fprintf(f, "local %s ", local);
+
+	if (tb[IFLA_STT_LINK] && *(__u32 *)RTA_DATA(tb[IFLA_STT_LINK])) {
+		unsigned link = *(__u32 *)RTA_DATA(tb[IFLA_STT_LINK]);
+		const char *n = if_indextoname(link, s2);
+
+		if (n)
+			fprintf(f, "dev %s ", n);
+		else
+			fprintf(f, "dev %u ", link);
+	}
+
+	if (tb[IFLA_STT_IKEY]) {
+		__u64 key = *(__u64 *)RTA_DATA(tb[IFLA_STT_IKEY]);
+		fprintf(f, "ikey %"PRIx64" ", ntohll(key));
+	}
+
+	if (tb[IFLA_STT_OKEY]) {
+		__u64 key = *(__u64 *)RTA_DATA(tb[IFLA_STT_OKEY]);
+		fprintf(f, "okey %"PRIx64" ", htonll(key));
+	}
+
+	if (tb[IFLA_STT_DST_PORT]) {
+		__u16 port = *(__u16 *)RTA_DATA(tb[IFLA_STT_DST_PORT]);
+		fprintf(f, "dst-port %u ", port);
+	}
+
+	if (tb[IFLA_STT_TTL] && rta_getattr_u8(tb[IFLA_STT_TTL]))
+		fprintf(f, "ttl %d ", rta_getattr_u8(tb[IFLA_STT_TTL]));
+	else
+		fprintf(f, "ttl inherit ");
+
+	if (tb[IFLA_STT_TOS] && rta_getattr_u8(tb[IFLA_STT_TOS])) {
+		int tos = rta_getattr_u8(tb[IFLA_STT_TOS]);
+
+		fputs("tos ", f);
+		if (tos == 1)
+			fputs("inherit ", f);
+		else
+			fprintf(f, "0x%x ", tos);
+	}
+
+	if (tb[IFLA_STT_DF])
+		fputs("nopmtudisc ", f);
+}
+
+static void stt_print_help(struct link_util *lu, int argc, char **argv,
+	FILE *f)
+{
+	print_usage(f);
+}
+
+struct link_util stt_link_util = {
+	.id = "stt",
+	.maxattr = IFLA_STT_MAX,
+	.parse_opt = stt_parse_opt,
+	.print_opt = stt_print_opt,
+	.print_help = stt_print_help,
+};
-- 
1.9.1

^ permalink raw reply related

* [PATCH net-next v2 6/6] net: Add STT device.
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar

Following patch adds STT device driver so that user can create
standalone STT device without Open vSwitch.  I have provided netlink
interface to manage STT device from userspace.

Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
---
 include/uapi/linux/if_tunnel.h |  17 ++
 net/ipv4/Kconfig               |   9 +
 net/ipv4/Makefile              |   1 +
 net/ipv4/ip_stt.c              | 410 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 437 insertions(+)
 create mode 100644 net/ipv4/ip_stt.c

diff --git a/include/uapi/linux/if_tunnel.h b/include/uapi/linux/if_tunnel.h
index bd3cc11..1d4e799 100644
--- a/include/uapi/linux/if_tunnel.h
+++ b/include/uapi/linux/if_tunnel.h
@@ -131,4 +131,21 @@ enum {
 };
 
 #define IFLA_VTI_MAX	(__IFLA_VTI_MAX - 1)
+
+enum {
+	IFLA_STT_UNSPEC,
+	IFLA_STT_LINK,
+	IFLA_STT_LOCAL,
+	IFLA_STT_REMOTE,
+	IFLA_STT_TTL,
+	IFLA_STT_TOS,
+	IFLA_STT_DF,
+	IFLA_STT_IKEY,
+	IFLA_STT_OKEY,
+	IFLA_STT_DST_PORT,
+	__IFLA_STT_MAX,
+};
+#define IFLA_STT_MAX	(__IFLA_STT_MAX - 1)
+
+
 #endif /* _UAPI_IF_TUNNEL_H_ */
diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index 3ab00be..f1044fb 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -356,6 +356,15 @@ config STT
 
 	  To compile this driver as a module, choose M here: the module
 
+config IP_STT
+	tristate "STT device"
+	depends on STT
+	---help---
+	This allows one to create STT device that provide Layer 2 Networks
+	over Layer 3 (IPv4) Networks.
+
+	  To compile this driver as a module, choose M here: the module
+
 config INET_AH
 	tristate "IP: AH transformation"
 	select XFRM_ALGO
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index d504fde..313ef86 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_MEMCG_KMEM) += tcp_memcontrol.o
 obj-$(CONFIG_NETLABEL) += cipso_ipv4.o
 obj-$(CONFIG_GENEVE) += geneve.o
 obj-$(CONFIG_STT) += stt.o
+obj-$(CONFIG_IP_STT) += ip_stt.o
 
 obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \
 		      xfrm4_output.o xfrm4_protocol.o
diff --git a/net/ipv4/ip_stt.c b/net/ipv4/ip_stt.c
new file mode 100644
index 0000000..882a333
--- /dev/null
+++ b/net/ipv4/ip_stt.c
@@ -0,0 +1,410 @@
+/*
+ * Stateless TCP Tunnel (STT) device.
+ *
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/delay.h>
+#include <linux/flex_array.h>
+#include <linux/if.h>
+#include <linux/if_vlan.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/jhash.h>
+#include <linux/list.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/percpu.h>
+#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <linux/workqueue.h>
+
+#include <net/icmp.h>
+#include <net/inet_ecn.h>
+#include <net/ip.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+#include <net/sock.h>
+#include <net/stt.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+static bool log_ecn_error = true;
+module_param(log_ecn_error, bool, 0644);
+MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");
+
+static int stt_net_id __read_mostly;
+
+static int stt_tunnel_init(struct net_device *dev);
+static struct rtnl_link_ops stt_link_ops __read_mostly;
+
+static void stt_rcv(struct stt_sock *stt_sock, struct sk_buff *skb)
+{
+	struct net *net = dev_net(skb->dev);
+	struct ip_tunnel_net *itn = net_generic(net, stt_net_id);
+	struct ip_tunnel *tunnel;
+	struct tnl_ptk_info tpi;
+	const struct iphdr *iph;
+	struct stthdr *stth;
+	struct sk_buff *next;
+
+	iph = ip_hdr(skb);
+	stth = stt_hdr(skb);
+	tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, 0,
+				  iph->saddr, iph->daddr, tcp_hdr(skb)->dest,
+				  get_unaligned(&stth->key));
+	if (!tunnel)
+		goto error;
+
+	tpi.proto = htons(ETH_P_TEB);
+	tpi.key = get_unaligned(&stth->key);
+	tpi.flags = TUNNEL_KEY;
+	tpi.seq = 0;
+	do {
+		next = skb->next;
+		skb->next = NULL;
+		ip_tunnel_rcv(tunnel, skb, &tpi, log_ecn_error);
+	} while ((skb = next));
+error:
+	kfree_skb_list(skb);
+}
+
+static netdev_tx_t stt_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct ip_tunnel *tunnel = netdev_priv(dev);
+	struct tunnel_info *info = &tunnel->info;
+	struct net *net = dev_net(skb->dev);
+	const struct iphdr *inner_iph;
+	bool connected = true;
+	struct rtable *rt;
+	__u8 ttl, tos;
+	__be32 saddr;
+	__be16 df, sport;
+	int err;
+
+	inner_iph = (const struct iphdr *)skb_network_header(skb);
+	/* Calculate ToS */
+	tos = info->tos;
+	if (tos & 0x1) {
+		tos &= ~0x1;
+		if (skb->protocol == htons(ETH_P_IP)) {
+			tos = inner_iph->tos;
+			connected = false;
+#if IS_ENABLED(CONFIG_IPV6)
+		} else if (skb->protocol == htons(ETH_P_IPV6)) {
+			tos = ipv6_get_dsfield((const struct ipv6hdr *)inner_iph);
+			connected = false;
+		}
+#endif
+	}
+
+	/* Calculate saddr */
+	saddr = info->saddr;
+	rt = connected ? tunnel_rtable_get(tunnel, 0, &saddr) : NULL;
+	if (!rt) {
+		struct flowi4 fl;
+
+		memset(&fl, 0, sizeof(fl));
+		fl.daddr = info->daddr;
+		fl.saddr = info->saddr;
+		fl.flowi4_tos = RT_TOS(tos);
+		fl.flowi4_mark = skb->mark;
+		fl.flowi4_proto = IPPROTO_TCP;
+
+		rt = ip_route_output_key(net, &fl);
+		if (IS_ERR(rt)) {
+			err = PTR_ERR(rt);
+			goto error;
+		}
+		if (connected)
+			tunnel_dst_set(tunnel, &rt->dst, fl.saddr);
+		saddr = fl.saddr;
+	}
+
+	/* Calculate src-port */
+	sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
+	skb->ignore_df = 1;
+
+	/* Calculate ttl */
+	ttl = info->ttl;
+	if (ttl == 0) {
+		if (skb->protocol == htons(ETH_P_IP))
+			ttl = inner_iph->ttl;
+#if IS_ENABLED(CONFIG_IPV6)
+		else if (skb->protocol == htons(ETH_P_IPV6))
+			ttl = ((const struct ipv6hdr *)inner_iph)->hop_limit;
+#endif
+		else
+			ttl = ip4_dst_hoplimit(&rt->dst);
+	}
+
+	/* Calculate df */
+	df = info->df;
+	if (skb->protocol == htons(ETH_P_IP))
+		df |= (inner_iph->frag_off&htons(IP_DF));
+
+	tos = ip_tunnel_ecn_encap(tos, inner_iph, skb);
+	err = stt_xmit_skb(skb, rt, saddr, info->daddr, tos, ttl, df,
+			   sport, info->portno, info->o_key);
+
+	iptunnel_xmit_stats(err, &dev->stats, dev->tstats);
+	return NETDEV_TX_OK;
+error:
+	kfree_skb(skb);
+	return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops stt_netdev_ops = {
+	.ndo_init       = stt_tunnel_init,
+	.ndo_uninit     = ip_tunnel_uninit,
+	.ndo_start_xmit	= stt_tunnel_xmit,
+	.ndo_change_mtu = ip_tunnel_change_mtu,
+	.ndo_get_stats64 = ip_tunnel_get_stats64,
+};
+
+#define STT_DST_PORT 7471
+
+static int stt_tunnel_init(struct net_device *dev)
+{
+	struct ip_tunnel *tunnel = netdev_priv(dev);
+	int t_hlen;
+
+	tunnel->info.portno = htons(STT_DST_PORT);
+	memcpy(dev->dev_addr, &tunnel->info.saddr, 4);
+	memcpy(dev->broadcast, &tunnel->info.daddr, 4);
+
+	tunnel->tun_hlen = STT_HEADER_LEN;
+	tunnel->hlen = tunnel->tun_hlen;
+	t_hlen = tunnel->hlen + sizeof(struct iphdr);
+
+	dev->needed_headroom	= LL_MAX_HEADER + t_hlen;
+	dev->mtu		= ETH_DATA_LEN - t_hlen;
+
+	return ip_tunnel_init(dev);
+}
+
+#define STT_FEATURES (NETIF_F_SG |		\
+		       NETIF_F_FRAGLIST |	\
+		       NETIF_F_HIGHDMA |	\
+		       NETIF_F_GSO_SOFTWARE |	\
+		       NETIF_F_HW_CSUM)
+
+static void stt_tunnel_setup(struct net_device *dev)
+{
+	ether_setup(dev);
+	dev->netdev_ops		= &stt_netdev_ops;
+	dev->priv_flags		|= IFF_LIVE_ADDR_CHANGE;
+
+	dev->features		|= STT_FEATURES;
+	dev->hw_features	|= STT_FEATURES;
+	ip_tunnel_setup(dev, stt_net_id);
+}
+
+static void stt_netlink_parms(struct nlattr *data[],
+			      struct tunnel_info *info)
+{
+	memset(info, 0, sizeof(*info));
+
+	info->protocol = IPPROTO_TCP;
+
+	if (!data)
+		return;
+
+	if (data[IFLA_STT_LINK])
+		info->link = nla_get_u32(data[IFLA_STT_LINK]);
+
+	if (data[IFLA_STT_LOCAL])
+		info->saddr = nla_get_be32(data[IFLA_STT_LOCAL]);
+
+	if (data[IFLA_STT_REMOTE])
+		info->daddr = nla_get_be32(data[IFLA_STT_REMOTE]);
+
+	if (data[IFLA_STT_TTL]) {
+		info->ttl = nla_get_u8(data[IFLA_STT_TTL]);
+		if (info->ttl)
+			info->df = htons(IP_DF);
+	}
+
+	if (data[IFLA_STT_TOS])
+		info->tos = nla_get_u8(data[IFLA_STT_TOS]);
+
+	if (data[IFLA_STT_DF])
+		info->df = htons(IP_DF);
+
+	if (data[IFLA_STT_IKEY])
+		info->i_key = nla_get_u64(data[IFLA_STT_IKEY]);
+
+	if (data[IFLA_STT_OKEY])
+		info->o_key = nla_get_u64(data[IFLA_STT_OKEY]);
+
+	if (data[IFLA_STT_DST_PORT])
+		info->portno = htons(nla_get_u16(data[IFLA_STT_DST_PORT]));
+}
+
+static int stt_newlink(struct net *src_net, struct net_device *dev,
+		       struct nlattr *tb[], struct nlattr *data[])
+{
+	struct net *net = dev_net(dev);
+	struct tunnel_info info;
+	struct stt_sock *stt_sock;
+
+	stt_netlink_parms(data, &info);
+
+	stt_sock = stt_sock_add(net, info.portno, stt_rcv, NULL);
+	if (IS_ERR(stt_sock))
+		return PTR_ERR(stt_sock);
+
+	return ip_tunnel_newlink(dev, tb, &info);
+}
+
+static int stt_changelink(struct net_device *dev, struct nlattr *tb[],
+			  struct nlattr *data[])
+{
+	struct ip_tunnel *tunnel = netdev_priv(dev);
+	struct tunnel_info info;
+
+	stt_netlink_parms(data, &info);
+
+	if (info.portno != tunnel->info.portno)
+		return -EINVAL;
+
+	return ip_tunnel_changelink(dev, tb, &info);
+}
+
+static size_t stt_get_size(const struct net_device *dev)
+{
+	return
+		/* IFLA_STT_LINK */
+		nla_total_size(4) +
+		/* IFLA_STT_LOCAL */
+		nla_total_size(4) +
+		/* IFLA_STT_REMOTE */
+		nla_total_size(4) +
+		/* IFLA_STT_TTL */
+		nla_total_size(1) +
+		/* IFLA_STT_TOS */
+		nla_total_size(1) +
+		/* IFLA_STT_DF */
+		nla_total_size(1) +
+		/* IFLA_STT_IKEY */
+		nla_total_size(8) +
+		/* IFLA_STT_OKEY */
+		nla_total_size(8);
+}
+
+static int stt_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+	struct ip_tunnel *tunnel = netdev_priv(dev);
+	struct tunnel_info *info = &tunnel->info;
+
+	if (nla_put_u32(skb, IFLA_STT_LINK, info->link) ||
+	    nla_put_be32(skb, IFLA_STT_LOCAL, info->saddr) ||
+	    nla_put_be32(skb, IFLA_STT_REMOTE, info->daddr) ||
+	    nla_put_u8(skb, IFLA_STT_TTL, info->ttl) ||
+	    nla_put_u8(skb, IFLA_STT_TOS, info->tos) ||
+	    nla_put_u64(skb, IFLA_STT_DST_PORT, ntohs(info->portno)) ||
+	    nla_put_u64(skb, IFLA_STT_IKEY, info->i_key) ||
+	    nla_put_u64(skb, IFLA_STT_OKEY, info->o_key))
+		goto nla_put_failure;
+
+	if (info->df & htons(IP_DF) &&
+	    nla_put_flag(skb, IFLA_STT_DF))
+		goto nla_put_failure;
+	return 0;
+
+nla_put_failure:
+	return -EMSGSIZE;
+}
+
+static const struct nla_policy stt_policy[IFLA_STT_MAX + 1] = {
+	[IFLA_STT_LINK]		= { .type = NLA_U32 },
+	[IFLA_STT_LOCAL]	= { .type = NLA_U32 },
+	[IFLA_STT_REMOTE]	= { .type = NLA_U32 },
+	[IFLA_STT_TTL]		= { .type = NLA_U8 },
+	[IFLA_STT_TOS]		= { .type = NLA_U8 },
+	[IFLA_STT_DF]		= { .type = NLA_FLAG },
+	[IFLA_STT_IKEY]		= { .type = NLA_U64 },
+	[IFLA_STT_OKEY]		= { .type = NLA_U64 },
+	[IFLA_STT_DST_PORT]	= { .type = NLA_U16 },
+};
+
+void stt_dellink(struct net_device *dev, struct list_head *head)
+{
+	struct ip_tunnel *tunnel = netdev_priv(dev);
+	struct tunnel_info *info = &tunnel->info;
+	struct net *net = dev_net(dev);
+
+	stt_sock_release(net, info->portno);
+	ip_tunnel_dellink(dev, head);
+}
+
+static struct rtnl_link_ops stt_link_ops __read_mostly = {
+	.kind		= "stt",
+	.maxtype	= IFLA_STT_MAX,
+	.policy		= stt_policy,
+	.priv_size	= sizeof(struct ip_tunnel),
+	.setup		= stt_tunnel_setup,
+	.newlink	= stt_newlink,
+	.changelink	= stt_changelink,
+	.dellink	= stt_dellink,
+	.get_size	= stt_get_size,
+	.fill_info	= stt_fill_info,
+	.get_link_net	= ip_tunnel_get_link_net,
+};
+
+static int __net_init stt_init_net(struct net *net)
+{
+	return ip_tunnel_init_net(net, stt_net_id, &stt_link_ops, NULL);
+}
+
+static void __net_exit stt_exit_net(struct net *net)
+{
+	struct ip_tunnel_net *itn = net_generic(net, stt_net_id);
+
+	ip_tunnel_delete_net(itn, &stt_link_ops);
+}
+
+static struct pernet_operations stt_net_ops = {
+	.init = stt_init_net,
+	.exit = stt_exit_net,
+	.id   = &stt_net_id,
+	.size = sizeof(struct ip_tunnel_net),
+};
+
+static int __init stt_init(void)
+{
+	int err;
+
+	err = register_pernet_device(&stt_net_ops);
+	if (err < 0)
+		return err;
+	err = rtnl_link_register(&stt_link_ops);
+	if (err < 0)
+		goto error;
+
+	return 0;
+
+error:
+	unregister_pernet_device(&stt_net_ops);
+	return err;
+}
+
+static void __exit stt_fini(void)
+{
+	rtnl_link_unregister(&stt_link_ops);
+	unregister_pernet_device(&stt_net_ops);
+}
+
+module_init(stt_init);
+module_exit(stt_fini);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_RTNL_LINK("stt");
+MODULE_ALIAS_NETDEV("stt0");
-- 
1.9.1

^ permalink raw reply related

* [PATCH net-next v2 5/6] ip_tunnel: Extend tunnel_info for STT.
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar

Next patch introduces stt kernel device. I am planning on using
generic ip_tunnel layer for setting up control plane. But
STT has 64-bit key and it has TCP port. Following patch enhances
ip_tunnel layer so that it can be used by STT.

Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
---
 include/net/ip_tunnels.h | 33 +++++++++++++++++++++++-----
 net/ipv4/gre_demux.c     |  4 ++--
 net/ipv4/ip_gre.c        | 14 ++++++------
 net/ipv4/ip_tunnel.c     | 56 +++++++++++++++++++++++++++++++-----------------
 net/ipv4/ip_vti.c        |  4 ++--
 net/ipv4/ipip.c          |  4 ++--
 6 files changed, 77 insertions(+), 38 deletions(-)

diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index b46b05d..0816d49 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -52,11 +52,12 @@ struct ip_tunnel_dst {
 };
 
 struct tunnel_info {
-	__be32		i_key;
-	__be32		o_key;
+	__be64		i_key;
+	__be64		o_key;
 	__be32		saddr;
 	__be32		daddr;
 	int		link;
+	__be16		portno;
 	__be16		i_flags;
 	__be16		o_flags;
 	__be16		df;
@@ -117,10 +118,10 @@ struct ip_tunnel {
 #define TUNNEL_OPTIONS_PRESENT	(TUNNEL_GENEVE_OPT | TUNNEL_VXLAN_OPT)
 
 struct tnl_ptk_info {
+	__be64 key;
+	__be32 seq;
 	__be16 flags;
 	__be16 proto;
-	__be32 key;
-	__be32 seq;
 };
 
 #define PACKET_RCVD	0
@@ -174,7 +175,7 @@ struct rtnl_link_stats64 *ip_tunnel_get_stats64(struct net_device *dev,
 struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 				   int link, __be16 flags,
 				   __be32 remote, __be32 local,
-				   __be32 key);
+				   __be16 portno, __be64 key);
 
 int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
 		  const struct tnl_ptk_info *tpi, bool log_ecn_error);
@@ -240,6 +241,28 @@ static inline void iptunnel_xmit_stats(int err,
 	}
 }
 
+void tunnel_dst_set(struct ip_tunnel *t, struct dst_entry *dst, __be32 saddr);
+struct rtable *tunnel_rtable_get(struct ip_tunnel *t,
+				 u32 cookie, __be32 *saddr);
+
+static inline __be64 tunnel_id32_to_key(__be32 key)
+{
+#ifdef __BIG_ENDIAN
+	return (__force __be64)(key);
+#else
+	return (__force __be64)((__force u64)key << 32);
+#endif
+}
+
+static inline __be32 key_to_tunnel_id32(__be64 x)
+{
+#ifdef __BIG_ENDIAN
+	return (__force __be32)x;
+#else
+	return (__force __be32)((__force u64)x >> 32);
+#endif
+}
+
 #endif /* CONFIG_INET */
 
 #endif /* __NET_IP_TUNNELS_H */
diff --git a/net/ipv4/gre_demux.c b/net/ipv4/gre_demux.c
index 4a7b5b2..5097581 100644
--- a/net/ipv4/gre_demux.c
+++ b/net/ipv4/gre_demux.c
@@ -81,7 +81,7 @@ void gre_build_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
 			ptr--;
 		}
 		if (tpi->flags&TUNNEL_KEY) {
-			*ptr = tpi->key;
+			*ptr = key_to_tunnel_id32(tpi->key);
 			ptr--;
 		}
 		if (tpi->flags&TUNNEL_CSUM &&
@@ -132,7 +132,7 @@ static int parse_gre_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
 	}
 
 	if (greh->flags & GRE_KEY) {
-		tpi->key = *options;
+		tpi->key = tunnel_id32_to_key(*options);
 		options++;
 	} else
 		tpi->key = 0;
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index fa9ee75..93bbff1 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -180,7 +180,7 @@ static int ipgre_err(struct sk_buff *skb, u32 info,
 
 	iph = (const struct iphdr *)(icmp_hdr(skb) + 1);
 	t = ip_tunnel_lookup(itn, skb->dev->ifindex, tpi->flags,
-			     iph->daddr, iph->saddr, tpi->key);
+			     iph->daddr, iph->saddr, 0, tpi->key);
 
 	if (t == NULL)
 		return PACKET_REJECT;
@@ -214,7 +214,7 @@ static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
 
 	iph = ip_hdr(skb);
 	tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, tpi->flags,
-				  iph->saddr, iph->daddr, tpi->key);
+				  iph->saddr, iph->daddr, 0, tpi->key);
 
 	if (tunnel) {
 		skb_pop_mac_header(skb);
@@ -452,7 +452,7 @@ static int ipgre_open(struct net_device *dev)
 		rt = ip_route_output_gre(t->net, &fl4,
 					 t->info.daddr,
 					 t->info.saddr,
-					 t->info.o_key,
+					 key_to_tunnel_id32(t->info.o_key),
 					 RT_TOS(t->info.tos),
 					 t->info.link);
 		if (IS_ERR(rt))
@@ -650,10 +650,10 @@ static void ipgre_netlink_info(struct nlattr *data[], struct nlattr *tb[],
 		info->o_flags = gre_flags_to_tnl_flags(nla_get_be16(data[IFLA_GRE_OFLAGS]));
 
 	if (data[IFLA_GRE_IKEY])
-		info->i_key = nla_get_be32(data[IFLA_GRE_IKEY]);
+		info->i_key = tunnel_id32_to_key(nla_get_be32(data[IFLA_GRE_IKEY]));
 
 	if (data[IFLA_GRE_OKEY])
-		info->o_key = nla_get_be32(data[IFLA_GRE_OKEY]);
+		info->o_key = tunnel_id32_to_key(nla_get_be32(data[IFLA_GRE_OKEY]));
 
 	if (data[IFLA_GRE_LOCAL])
 		info->saddr = nla_get_be32(data[IFLA_GRE_LOCAL]);
@@ -809,8 +809,8 @@ static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
 	if (nla_put_u32(skb, IFLA_GRE_LINK, info->link) ||
 	    nla_put_be16(skb, IFLA_GRE_IFLAGS, tnl_flags_to_gre_flags(info->i_flags)) ||
 	    nla_put_be16(skb, IFLA_GRE_OFLAGS, tnl_flags_to_gre_flags(info->o_flags)) ||
-	    nla_put_be32(skb, IFLA_GRE_IKEY, info->i_key) ||
-	    nla_put_be32(skb, IFLA_GRE_OKEY, info->o_key) ||
+	    nla_put_be32(skb, IFLA_GRE_IKEY, key_to_tunnel_id32(info->i_key)) ||
+	    nla_put_be32(skb, IFLA_GRE_OKEY, key_to_tunnel_id32(info->o_key)) ||
 	    nla_put_be32(skb, IFLA_GRE_LOCAL, info->saddr) ||
 	    nla_put_be32(skb, IFLA_GRE_REMOTE, info->daddr) ||
 	    nla_put_u8(skb, IFLA_GRE_TTL, info->ttl) ||
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index fc078ae..00177db 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -72,8 +72,8 @@ void tunnel_info_to_parm(char dev_name[], struct tunnel_info *info,
 	p->link = info->link;
 	p->i_flags = info->i_flags;
 	p->o_flags = info->o_flags;
-	p->i_key = info->i_key;
-	p->o_key = info->o_key;
+	p->i_key = key_to_tunnel_id32(info->i_key);
+	p->o_key = key_to_tunnel_id32(info->o_key);
 
 	p->iph.version = 4;
 	p->iph.ihl = 5;
@@ -94,8 +94,8 @@ void ip_tunnel_parm_to_info(struct ip_tunnel_parm *p,
 	info->link = p->link;
 	info->i_flags = p->i_flags;
 	info->o_flags = p->o_flags;
-	info->i_key = p->i_key;
-	info->o_key = p->o_key;
+	info->i_key = tunnel_id32_to_key(p->i_key);
+	info->o_key = tunnel_id32_to_key(p->o_key);
 
 	info->tos = p->iph.tos;
 	info->df = p->iph.frag_off;
@@ -106,10 +106,13 @@ void ip_tunnel_parm_to_info(struct ip_tunnel_parm *p,
 }
 EXPORT_SYMBOL_GPL(ip_tunnel_parm_to_info);
 
-static unsigned int ip_tunnel_hash(__be32 key, __be32 remote)
+static unsigned int ip_tunnel_hash(__be64 key, __be32 remote, __be16 portno)
 {
-	return hash_32((__force u32)key ^ (__force u32)remote,
-			 IP_TNL_HASH_BITS);
+	u32 initval;
+
+	initval = (__force u32) hash_64((__force u64)key, 32);
+	return jhash_2words((__force u32)remote, (__force u32)portno,
+			    initval) & (IP_TNL_HASH_SIZE - 1);
 }
 
 static void __tunnel_dst_set(struct ip_tunnel_dst *idst,
@@ -123,11 +126,11 @@ static void __tunnel_dst_set(struct ip_tunnel_dst *idst,
 	idst->saddr = saddr;
 }
 
-static noinline void tunnel_dst_set(struct ip_tunnel *t,
-			   struct dst_entry *dst, __be32 saddr)
+void tunnel_dst_set(struct ip_tunnel *t, struct dst_entry *dst, __be32 saddr)
 {
 	__tunnel_dst_set(raw_cpu_ptr(t->dst_cache), dst, saddr);
 }
+EXPORT_SYMBOL_GPL(tunnel_dst_set);
 
 static void tunnel_dst_reset(struct ip_tunnel *t)
 {
@@ -143,8 +146,8 @@ void ip_tunnel_dst_reset_all(struct ip_tunnel *t)
 }
 EXPORT_SYMBOL(ip_tunnel_dst_reset_all);
 
-static struct rtable *tunnel_rtable_get(struct ip_tunnel *t,
-					u32 cookie, __be32 *saddr)
+struct rtable *tunnel_rtable_get(struct ip_tunnel *t,
+				 u32 cookie, __be32 *saddr)
 {
 	struct ip_tunnel_dst *idst;
 	struct dst_entry *dst;
@@ -166,9 +169,10 @@ static struct rtable *tunnel_rtable_get(struct ip_tunnel *t,
 	rcu_read_unlock();
 	return (struct rtable *)dst;
 }
+EXPORT_SYMBOL_GPL(tunnel_rtable_get);
 
 static bool ip_tunnel_key_match(const struct tunnel_info *t,
-				__be16 flags, __be32 key)
+				__be16 flags, __be64 key)
 {
 	if (t->i_flags & TUNNEL_KEY) {
 		if (flags & TUNNEL_KEY)
@@ -194,13 +198,13 @@ static bool ip_tunnel_key_match(const struct tunnel_info *t,
 struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 				   int link, __be16 flags,
 				   __be32 remote, __be32 local,
-				   __be32 key)
+				   __be16 portno, __be64 key)
 {
 	unsigned int hash;
 	struct ip_tunnel *t, *cand = NULL;
 	struct hlist_head *head;
 
-	hash = ip_tunnel_hash(key, remote);
+	hash = ip_tunnel_hash(key, remote, portno);
 	head = &itn->tunnels[hash];
 
 	hlist_for_each_entry_rcu(t, head, hash_node) {
@@ -212,6 +216,9 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 		if (!ip_tunnel_key_match(&t->info, flags, key))
 			continue;
 
+		if (portno != t->info.portno)
+			continue;
+
 		if (t->info.link == link)
 			return t;
 		else
@@ -227,13 +234,16 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 		if (!ip_tunnel_key_match(&t->info, flags, key))
 			continue;
 
+		if (portno != t->info.portno)
+			continue;
+
 		if (t->info.link == link)
 			return t;
 		else if (!cand)
 			cand = t;
 	}
 
-	hash = ip_tunnel_hash(key, 0);
+	hash = ip_tunnel_hash(key, 0, portno);
 	head = &itn->tunnels[hash];
 
 	hlist_for_each_entry_rcu(t, head, hash_node) {
@@ -247,6 +257,9 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 		if (!ip_tunnel_key_match(&t->info, flags, key))
 			continue;
 
+		if (portno != t->info.portno)
+			continue;
+
 		if (t->info.link == link)
 			return t;
 		else if (!cand)
@@ -260,6 +273,7 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 		if (t->info.i_key != key ||
 		    t->info.saddr != 0 ||
 		    t->info.daddr != 0 ||
+		    t->info.portno != portno ||
 		    !(t->dev->flags & IFF_UP))
 			continue;
 
@@ -286,7 +300,7 @@ static struct hlist_head *ip_bucket(struct ip_tunnel_net *itn,
 {
 	unsigned int h;
 	__be32 remote;
-	__be32 i_key = info->i_key;
+	__be64 i_key = info->i_key;
 
 	if (info->daddr && !ipv4_is_multicast(info->daddr))
 		remote = info->daddr;
@@ -296,7 +310,7 @@ static struct hlist_head *ip_bucket(struct ip_tunnel_net *itn,
 	if (!(info->i_flags & TUNNEL_KEY) && (info->i_flags & VTI_ISVTI))
 		i_key = 0;
 
-	h = ip_tunnel_hash(i_key, remote);
+	h = ip_tunnel_hash(i_key, remote, info->portno);
 	return &itn->tunnels[h];
 }
 
@@ -318,8 +332,9 @@ static struct ip_tunnel *ip_tunnel_find(struct ip_tunnel_net *itn,
 {
 	__be32 remote = info->daddr;
 	__be32 local = info->saddr;
-	__be32 key = info->i_key;
+	__be64 key = info->i_key;
 	__be16 flags = info->i_flags;
+	__be16 portno = info->portno;
 	int link = info->link;
 	struct ip_tunnel *t = NULL;
 	struct hlist_head *head = ip_bucket(itn, info);
@@ -329,6 +344,7 @@ static struct ip_tunnel *ip_tunnel_find(struct ip_tunnel_net *itn,
 		    remote == t->info.daddr &&
 		    link == t->info.link &&
 		    type == t->dev->type &&
+		    portno == t->info.portno &&
 		    ip_tunnel_key_match(&t->info, flags, key))
 			break;
 	}
@@ -385,7 +401,7 @@ failed:
 static inline void init_tunnel_flow(struct flowi4 *fl4,
 				    int proto,
 				    __be32 daddr, __be32 saddr,
-				    __be32 key, __u8 tos, int oif)
+				    __be64 key, __u8 tos, int oif)
 {
 	memset(fl4, 0, sizeof(*fl4));
 	fl4->flowi4_oif = oif;
@@ -393,7 +409,7 @@ static inline void init_tunnel_flow(struct flowi4 *fl4,
 	fl4->saddr = saddr;
 	fl4->flowi4_tos = tos;
 	fl4->flowi4_proto = proto;
-	fl4->fl4_gre_key = key;
+	fl4->fl4_gre_key = key_to_tunnel_id32(key);
 }
 
 static int ip_tunnel_bind_dev(struct net_device *dev)
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
index f25222d..43eafa2 100644
--- a/net/ipv4/ip_vti.c
+++ b/net/ipv4/ip_vti.c
@@ -59,7 +59,7 @@ static int vti_input(struct sk_buff *skb, int nexthdr, __be32 spi,
 	struct ip_tunnel_net *itn = net_generic(net, vti_net_id);
 
 	tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, TUNNEL_NO_KEY,
-				  iph->saddr, iph->daddr, 0);
+				  iph->saddr, iph->daddr, 0, 0);
 	if (tunnel != NULL) {
 		if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
 			goto drop;
@@ -251,7 +251,7 @@ static int vti4_err(struct sk_buff *skb, u32 info)
 	struct ip_tunnel_net *itn = net_generic(net, vti_net_id);
 
 	tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, TUNNEL_NO_KEY,
-				  iph->daddr, iph->saddr, 0);
+				  iph->daddr, iph->saddr, 0, 0);
 	if (!tunnel)
 		return -1;
 
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
index b14b33d..a047e20 100644
--- a/net/ipv4/ipip.c
+++ b/net/ipv4/ipip.c
@@ -143,7 +143,7 @@ static int ipip_err(struct sk_buff *skb, u32 info)
 
 	err = -ENOENT;
 	t = ip_tunnel_lookup(itn, skb->dev->ifindex, TUNNEL_NO_KEY,
-			     iph->daddr, iph->saddr, 0);
+			     iph->daddr, iph->saddr, 0, 0);
 	if (t == NULL)
 		goto out;
 
@@ -192,7 +192,7 @@ static int ipip_rcv(struct sk_buff *skb)
 
 	iph = ip_hdr(skb);
 	tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, TUNNEL_NO_KEY,
-			iph->saddr, iph->daddr, 0);
+				  iph->saddr, iph->daddr, 0, 0);
 	if (tunnel) {
 		if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
 			goto drop;
-- 
1.9.1

^ permalink raw reply related

* [PATCH net-next v2 4/6] ip_tunnel: Introduce tunnel_info struct.
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar

Tunnel information is stored in ip_tunnel_parm, but this is part
of tunnel ioctl interface, so it is not extensible.
STT need to extend tunnel layer, so this patch introduces tunnel_info
struct to store kernel tunnel information. with this new struct
we can get rid of iphdr which is used to store iphdr parameters. Plus
redundant tunnel name is also removed. This simplifies code in ip_tunnel.
This patch does not change actual tunnel parameters.

Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
---
 include/net/ip_tunnels.h |  34 ++++--
 net/ipv4/ip_gre.c        | 179 ++++++++++++++++++-------------
 net/ipv4/ip_tunnel.c     | 268 ++++++++++++++++++++++++++---------------------
 net/ipv4/ip_vti.c        |  71 ++++++-------
 net/ipv4/ipip.c          |  84 ++++++++-------
 net/ipv6/sit.c           | 152 +++++++++++++--------------
 6 files changed, 440 insertions(+), 348 deletions(-)

diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index 2c47061..b46b05d 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -51,6 +51,20 @@ struct ip_tunnel_dst {
 	__be32				 saddr;
 };
 
+struct tunnel_info {
+	__be32		i_key;
+	__be32		o_key;
+	__be32		saddr;
+	__be32		daddr;
+	int		link;
+	__be16		i_flags;
+	__be16		o_flags;
+	__be16		df;
+	__u8		tos;
+	__u8		ttl;
+	__u8		protocol;
+};
+
 struct ip_tunnel {
 	struct ip_tunnel __rcu	*next;
 	struct hlist_node hash_node;
@@ -69,7 +83,7 @@ struct ip_tunnel {
 
 	struct ip_tunnel_dst __percpu *dst_cache;
 
-	struct ip_tunnel_parm parms;
+	struct tunnel_info info;
 
 	int		encap_hlen;	/* Encap header length (FOU,GUE) */
 	struct ip_tunnel_encap encap;
@@ -143,13 +157,14 @@ void ip_tunnel_uninit(struct net_device *dev);
 void  ip_tunnel_dellink(struct net_device *dev, struct list_head *head);
 struct net *ip_tunnel_get_link_net(const struct net_device *dev);
 int ip_tunnel_init_net(struct net *net, int ip_tnl_net_id,
-		       struct rtnl_link_ops *ops, char *devname);
+		       struct rtnl_link_ops *ops, char devname[]);
 
 void ip_tunnel_delete_net(struct ip_tunnel_net *itn, struct rtnl_link_ops *ops);
-
 void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
-		    const struct iphdr *tnl_params, const u8 protocol);
-int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd);
+		    __be32 saddr, __be32 dst, u8 tos, u8 ttl, __be16 df,
+		    u8 protocol);
+int ip_tunnel_ioctl(struct net_device *dev, char *dev_name,
+		    struct tunnel_info *info, int cmd);
 int ip_tunnel_encap(struct sk_buff *skb, struct ip_tunnel *t,
 		    u8 *protocol, struct flowi4 *fl4);
 int ip_tunnel_change_mtu(struct net_device *dev, int new_mtu);
@@ -164,14 +179,19 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
 		  const struct tnl_ptk_info *tpi, bool log_ecn_error);
 int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
-			 struct ip_tunnel_parm *p);
+			 struct tunnel_info *info);
 int ip_tunnel_newlink(struct net_device *dev, struct nlattr *tb[],
-		      struct ip_tunnel_parm *p);
+		      struct tunnel_info *info);
 void ip_tunnel_setup(struct net_device *dev, int net_id);
 void ip_tunnel_dst_reset_all(struct ip_tunnel *t);
 int ip_tunnel_encap_setup(struct ip_tunnel *t,
 			  struct ip_tunnel_encap *ipencap);
 
+void tunnel_info_to_parm(char dev_name[], struct tunnel_info *info,
+			    struct ip_tunnel_parm *p);
+void ip_tunnel_parm_to_info(struct ip_tunnel_parm *p,
+			    struct tunnel_info *info);
+
 /* Extract dsfield from inner protocol */
 static inline u8 ip_tunnel_get_dsfield(const struct iphdr *iph,
 				       const struct sk_buff *skb)
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 6e7727f2..fa9ee75 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -185,11 +185,11 @@ static int ipgre_err(struct sk_buff *skb, u32 info,
 	if (t == NULL)
 		return PACKET_REJECT;
 
-	if (t->parms.iph.daddr == 0 ||
-	    ipv4_is_multicast(t->parms.iph.daddr))
+	if (t->info.daddr == 0 ||
+	    ipv4_is_multicast(t->info.daddr))
 		return PACKET_RCVD;
 
-	if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+	if (t->info.ttl == 0 && type == ICMP_TIME_EXCEEDED)
 		return PACKET_RCVD;
 
 	if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
@@ -224,17 +224,17 @@ static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi)
 	return PACKET_REJECT;
 }
 
-static void __gre_xmit(struct sk_buff *skb, struct net_device *dev,
-		       const struct iphdr *tnl_params,
-		       __be16 proto)
+static void __gre_xmit(struct sk_buff *skb, struct net_device *dev, __be32 src,
+		       __be32 dst, __u8 tos, __u8 ttl, __be16 df,
+		       __be16 inner_proto)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
 	struct tnl_ptk_info tpi;
 
-	tpi.flags = tunnel->parms.o_flags;
-	tpi.proto = proto;
-	tpi.key = tunnel->parms.o_key;
-	if (tunnel->parms.o_flags & TUNNEL_SEQ)
+	tpi.flags = tunnel->info.o_flags;
+	tpi.proto = inner_proto;
+	tpi.key = tunnel->info.o_key;
+	if (tunnel->info.o_flags & TUNNEL_SEQ)
 		tunnel->o_seqno++;
 	tpi.seq = htonl(tunnel->o_seqno);
 
@@ -243,22 +243,32 @@ static void __gre_xmit(struct sk_buff *skb, struct net_device *dev,
 
 	skb_set_inner_protocol(skb, tpi.proto);
 
-	ip_tunnel_xmit(skb, dev, tnl_params, tnl_params->protocol);
+	ip_tunnel_xmit(skb, dev, src, dst, tos, ttl, df, IPPROTO_GRE);
 }
 
 static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
 			      struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	const struct iphdr *tnl_params;
+	__be16 df;
+	__be32 src, dst;
+	__u8 tos, ttl;
 
 	if (dev->header_ops) {
+		const struct iphdr *iph;
+
 		/* Need space for new headers */
 		if (skb_cow_head(skb, dev->needed_headroom -
 				      (tunnel->hlen + sizeof(struct iphdr))))
 			goto free_skb;
 
-		tnl_params = (const struct iphdr *)skb->data;
+		iph = (const struct iphdr *)skb->data;
+
+		src = iph->saddr;
+		dst = iph->daddr;
+		tos = iph->tos;
+		ttl = iph->ttl;
+		df = iph->frag_off;
 
 		/* Pull skb since ip_tunnel_xmit() needs skb->data pointing
 		 * to gre header.
@@ -266,17 +276,23 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
 		skb_pull(skb, tunnel->hlen + sizeof(struct iphdr));
 		skb_reset_mac_header(skb);
 	} else {
+		struct tunnel_info *info = &tunnel->info;
+
 		if (skb_cow_head(skb, dev->needed_headroom))
 			goto free_skb;
 
-		tnl_params = &tunnel->parms.iph;
+		src = info->saddr;
+		dst = info->daddr;
+		tos = info->tos;
+		ttl = info->ttl;
+		df = info->df;
 	}
 
-	skb = gre_handle_offloads(skb, !!(tunnel->parms.o_flags&TUNNEL_CSUM));
+	skb = gre_handle_offloads(skb, !!(tunnel->info.o_flags&TUNNEL_CSUM));
 	if (IS_ERR(skb))
 		goto out;
 
-	__gre_xmit(skb, dev, tnl_params, skb->protocol);
+	__gre_xmit(skb, dev, src, dst, tos, ttl, df, skb->protocol);
 
 	return NETDEV_TX_OK;
 
@@ -291,15 +307,17 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
 				struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
+	struct tunnel_info *info = &tunnel->info;
 
-	skb = gre_handle_offloads(skb, !!(tunnel->parms.o_flags&TUNNEL_CSUM));
+	skb = gre_handle_offloads(skb, !!(info->o_flags&TUNNEL_CSUM));
 	if (IS_ERR(skb))
 		goto out;
 
 	if (skb_cow_head(skb, dev->needed_headroom))
 		goto free_skb;
 
-	__gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB));
+	__gre_xmit(skb, dev, info->saddr, info->daddr, info->tos, info->ttl,
+		   info->df, htons(ETH_P_TEB));
 
 	return NETDEV_TX_OK;
 
@@ -313,8 +331,9 @@ out:
 static int ipgre_tunnel_ioctl(struct net_device *dev,
 			      struct ifreq *ifr, int cmd)
 {
-	int err;
+	struct tunnel_info info;
 	struct ip_tunnel_parm p;
+	int err;
 
 	if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
 		return -EFAULT;
@@ -327,9 +346,11 @@ static int ipgre_tunnel_ioctl(struct net_device *dev,
 	p.i_flags = gre_flags_to_tnl_flags(p.i_flags);
 	p.o_flags = gre_flags_to_tnl_flags(p.o_flags);
 
-	err = ip_tunnel_ioctl(dev, &p, cmd);
+	ip_tunnel_parm_to_info(&p, &info);
+	err = ip_tunnel_ioctl(dev, p.name, &info, cmd);
 	if (err)
 		return err;
+	tunnel_info_to_parm(p.name, &info, &p);
 
 	p.i_flags = tnl_flags_to_gre_flags(p.i_flags);
 	p.o_flags = tnl_flags_to_gre_flags(p.o_flags);
@@ -371,21 +392,36 @@ static int ipgre_header(struct sk_buff *skb, struct net_device *dev,
 			const void *daddr, const void *saddr, unsigned int len)
 {
 	struct ip_tunnel *t = netdev_priv(dev);
+	struct tunnel_info *info = &t->info;
 	struct iphdr *iph;
 	struct gre_base_hdr *greh;
 
 	iph = (struct iphdr *)skb_push(skb, t->hlen + sizeof(*iph));
 	greh = (struct gre_base_hdr *)(iph+1);
-	greh->flags = tnl_flags_to_gre_flags(t->parms.o_flags);
+	greh->flags = tnl_flags_to_gre_flags(t->info.o_flags);
 	greh->protocol = htons(type);
 
-	memcpy(iph, &t->parms.iph, sizeof(struct iphdr));
+	iph->ihl = 5;
+	iph->version = 4;
+	iph->tos = info->tos;
+	iph->tot_len = 0;
+	iph->id = 0;
+	iph->frag_off = info->df;
+	iph->ttl = info->ttl;
+	iph->protocol = IPPROTO_GRE;
+	iph->check = 0;
 
 	/* Set the source hardware address. */
 	if (saddr)
 		memcpy(&iph->saddr, saddr, 4);
+	else
+		memcpy(&iph->saddr, &info->saddr, 4);
+
 	if (daddr)
 		memcpy(&iph->daddr, daddr, 4);
+	else
+		memcpy(&iph->daddr, &info->daddr, 4);
+
 	if (iph->daddr)
 		return t->hlen + sizeof(*iph);
 
@@ -409,16 +445,16 @@ static int ipgre_open(struct net_device *dev)
 {
 	struct ip_tunnel *t = netdev_priv(dev);
 
-	if (ipv4_is_multicast(t->parms.iph.daddr)) {
+	if (ipv4_is_multicast(t->info.daddr)) {
 		struct flowi4 fl4;
 		struct rtable *rt;
 
 		rt = ip_route_output_gre(t->net, &fl4,
-					 t->parms.iph.daddr,
-					 t->parms.iph.saddr,
-					 t->parms.o_key,
-					 RT_TOS(t->parms.iph.tos),
-					 t->parms.link);
+					 t->info.daddr,
+					 t->info.saddr,
+					 t->info.o_key,
+					 RT_TOS(t->info.tos),
+					 t->info.link);
 		if (IS_ERR(rt))
 			return -EADDRNOTAVAIL;
 		dev = rt->dst.dev;
@@ -426,7 +462,7 @@ static int ipgre_open(struct net_device *dev)
 		if (__in_dev_get_rtnl(dev) == NULL)
 			return -EADDRNOTAVAIL;
 		t->mlink = dev->ifindex;
-		ip_mc_inc_group(__in_dev_get_rtnl(dev), t->parms.iph.daddr);
+		ip_mc_inc_group(__in_dev_get_rtnl(dev), t->info.daddr);
 	}
 	return 0;
 }
@@ -435,11 +471,11 @@ static int ipgre_close(struct net_device *dev)
 {
 	struct ip_tunnel *t = netdev_priv(dev);
 
-	if (ipv4_is_multicast(t->parms.iph.daddr) && t->mlink) {
+	if (ipv4_is_multicast(t->info.daddr) && t->mlink) {
 		struct in_device *in_dev;
 		in_dev = inetdev_by_index(t->net, t->mlink);
 		if (in_dev)
-			ip_mc_dec_group(in_dev, t->parms.iph.daddr);
+			ip_mc_dec_group(in_dev, t->info.daddr);
 	}
 	return 0;
 }
@@ -476,8 +512,7 @@ static void __gre_tunnel_init(struct net_device *dev)
 	int t_hlen;
 
 	tunnel = netdev_priv(dev);
-	tunnel->tun_hlen = ip_gre_calc_hlen(tunnel->parms.o_flags);
-	tunnel->parms.iph.protocol = IPPROTO_GRE;
+	tunnel->tun_hlen = ip_gre_calc_hlen(tunnel->info.o_flags);
 
 	tunnel->hlen = tunnel->tun_hlen + tunnel->encap_hlen;
 
@@ -489,7 +524,7 @@ static void __gre_tunnel_init(struct net_device *dev)
 	dev->features		|= GRE_FEATURES;
 	dev->hw_features	|= GRE_FEATURES;
 
-	if (!(tunnel->parms.o_flags & TUNNEL_SEQ)) {
+	if (!(tunnel->info.o_flags & TUNNEL_SEQ)) {
 		/* TCP offload with GRE SEQ is not supported. */
 		dev->features    |= NETIF_F_GSO_SOFTWARE;
 		dev->hw_features |= NETIF_F_GSO_SOFTWARE;
@@ -503,21 +538,21 @@ static void __gre_tunnel_init(struct net_device *dev)
 static int ipgre_tunnel_init(struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct iphdr *iph = &tunnel->parms.iph;
+	struct tunnel_info *info = &tunnel->info;
 
 	__gre_tunnel_init(dev);
 
-	memcpy(dev->dev_addr, &iph->saddr, 4);
-	memcpy(dev->broadcast, &iph->daddr, 4);
+	memcpy(dev->dev_addr, &info->saddr, 4);
+	memcpy(dev->broadcast, &info->daddr, 4);
 
 	dev->flags		= IFF_NOARP;
 	netif_keep_dst(dev);
 	dev->addr_len		= 4;
 
-	if (iph->daddr) {
+	if (info->daddr) {
 #ifdef CONFIG_NET_IPGRE_BROADCAST
-		if (ipv4_is_multicast(iph->daddr)) {
-			if (!iph->saddr)
+		if (ipv4_is_multicast(info->daddr)) {
+			if (!info->saddr)
 				return -EINVAL;
 			dev->flags = IFF_BROADCAST;
 			dev->header_ops = &ipgre_header_ops;
@@ -595,45 +630,45 @@ out:
 	return ipgre_tunnel_validate(tb, data);
 }
 
-static void ipgre_netlink_parms(struct nlattr *data[], struct nlattr *tb[],
-			       struct ip_tunnel_parm *parms)
+static void ipgre_netlink_info(struct nlattr *data[], struct nlattr *tb[],
+			       struct tunnel_info *info)
 {
-	memset(parms, 0, sizeof(*parms));
+	memset(info, 0, sizeof(*info));
 
-	parms->iph.protocol = IPPROTO_GRE;
+	info->protocol = IPPROTO_GRE;
 
 	if (!data)
 		return;
 
 	if (data[IFLA_GRE_LINK])
-		parms->link = nla_get_u32(data[IFLA_GRE_LINK]);
+		info->link = nla_get_u32(data[IFLA_GRE_LINK]);
 
 	if (data[IFLA_GRE_IFLAGS])
-		parms->i_flags = gre_flags_to_tnl_flags(nla_get_be16(data[IFLA_GRE_IFLAGS]));
+		info->i_flags = gre_flags_to_tnl_flags(nla_get_be16(data[IFLA_GRE_IFLAGS]));
 
 	if (data[IFLA_GRE_OFLAGS])
-		parms->o_flags = gre_flags_to_tnl_flags(nla_get_be16(data[IFLA_GRE_OFLAGS]));
+		info->o_flags = gre_flags_to_tnl_flags(nla_get_be16(data[IFLA_GRE_OFLAGS]));
 
 	if (data[IFLA_GRE_IKEY])
-		parms->i_key = nla_get_be32(data[IFLA_GRE_IKEY]);
+		info->i_key = nla_get_be32(data[IFLA_GRE_IKEY]);
 
 	if (data[IFLA_GRE_OKEY])
-		parms->o_key = nla_get_be32(data[IFLA_GRE_OKEY]);
+		info->o_key = nla_get_be32(data[IFLA_GRE_OKEY]);
 
 	if (data[IFLA_GRE_LOCAL])
-		parms->iph.saddr = nla_get_be32(data[IFLA_GRE_LOCAL]);
+		info->saddr = nla_get_be32(data[IFLA_GRE_LOCAL]);
 
 	if (data[IFLA_GRE_REMOTE])
-		parms->iph.daddr = nla_get_be32(data[IFLA_GRE_REMOTE]);
+		info->daddr = nla_get_be32(data[IFLA_GRE_REMOTE]);
 
 	if (data[IFLA_GRE_TTL])
-		parms->iph.ttl = nla_get_u8(data[IFLA_GRE_TTL]);
+		info->ttl = nla_get_u8(data[IFLA_GRE_TTL]);
 
 	if (data[IFLA_GRE_TOS])
-		parms->iph.tos = nla_get_u8(data[IFLA_GRE_TOS]);
+		info->tos = nla_get_u8(data[IFLA_GRE_TOS]);
 
 	if (!data[IFLA_GRE_PMTUDISC] || nla_get_u8(data[IFLA_GRE_PMTUDISC]))
-		parms->iph.frag_off = htons(IP_DF);
+		info->df = htons(IP_DF);
 }
 
 /* This function returns true when ENCAP attributes are present in the nl msg */
@@ -699,8 +734,8 @@ static void ipgre_tap_setup(struct net_device *dev)
 static int ipgre_newlink(struct net *src_net, struct net_device *dev,
 			 struct nlattr *tb[], struct nlattr *data[])
 {
-	struct ip_tunnel_parm p;
 	struct ip_tunnel_encap ipencap;
+	struct tunnel_info info;
 
 	if (ipgre_netlink_encap_parms(data, &ipencap)) {
 		struct ip_tunnel *t = netdev_priv(dev);
@@ -710,15 +745,15 @@ static int ipgre_newlink(struct net *src_net, struct net_device *dev,
 			return err;
 	}
 
-	ipgre_netlink_parms(data, tb, &p);
-	return ip_tunnel_newlink(dev, tb, &p);
+	ipgre_netlink_info(data, tb, &info);
+	return ip_tunnel_newlink(dev, tb, &info);
 }
 
 static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
 			    struct nlattr *data[])
 {
-	struct ip_tunnel_parm p;
 	struct ip_tunnel_encap ipencap;
+	struct tunnel_info info;
 
 	if (ipgre_netlink_encap_parms(data, &ipencap)) {
 		struct ip_tunnel *t = netdev_priv(dev);
@@ -728,8 +763,8 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
 			return err;
 	}
 
-	ipgre_netlink_parms(data, tb, &p);
-	return ip_tunnel_changelink(dev, tb, &p);
+	ipgre_netlink_info(data, tb, &info);
+	return ip_tunnel_changelink(dev, tb, &info);
 }
 
 static size_t ipgre_get_size(const struct net_device *dev)
@@ -769,19 +804,19 @@ static size_t ipgre_get_size(const struct net_device *dev)
 static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
 	struct ip_tunnel *t = netdev_priv(dev);
-	struct ip_tunnel_parm *p = &t->parms;
-
-	if (nla_put_u32(skb, IFLA_GRE_LINK, p->link) ||
-	    nla_put_be16(skb, IFLA_GRE_IFLAGS, tnl_flags_to_gre_flags(p->i_flags)) ||
-	    nla_put_be16(skb, IFLA_GRE_OFLAGS, tnl_flags_to_gre_flags(p->o_flags)) ||
-	    nla_put_be32(skb, IFLA_GRE_IKEY, p->i_key) ||
-	    nla_put_be32(skb, IFLA_GRE_OKEY, p->o_key) ||
-	    nla_put_be32(skb, IFLA_GRE_LOCAL, p->iph.saddr) ||
-	    nla_put_be32(skb, IFLA_GRE_REMOTE, p->iph.daddr) ||
-	    nla_put_u8(skb, IFLA_GRE_TTL, p->iph.ttl) ||
-	    nla_put_u8(skb, IFLA_GRE_TOS, p->iph.tos) ||
+	struct tunnel_info *info = &t->info;
+
+	if (nla_put_u32(skb, IFLA_GRE_LINK, info->link) ||
+	    nla_put_be16(skb, IFLA_GRE_IFLAGS, tnl_flags_to_gre_flags(info->i_flags)) ||
+	    nla_put_be16(skb, IFLA_GRE_OFLAGS, tnl_flags_to_gre_flags(info->o_flags)) ||
+	    nla_put_be32(skb, IFLA_GRE_IKEY, info->i_key) ||
+	    nla_put_be32(skb, IFLA_GRE_OKEY, info->o_key) ||
+	    nla_put_be32(skb, IFLA_GRE_LOCAL, info->saddr) ||
+	    nla_put_be32(skb, IFLA_GRE_REMOTE, info->daddr) ||
+	    nla_put_u8(skb, IFLA_GRE_TTL, info->ttl) ||
+	    nla_put_u8(skb, IFLA_GRE_TOS, info->tos) ||
 	    nla_put_u8(skb, IFLA_GRE_PMTUDISC,
-		       !!(p->iph.frag_off & htons(IP_DF))))
+		       !!(info->df & htons(IP_DF))))
 		goto nla_put_failure;
 
 	if (nla_put_u16(skb, IFLA_GRE_ENCAP_TYPE,
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 2cd0828..fc078ae 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -63,6 +63,49 @@
 #include <net/ip6_route.h>
 #endif
 
+void tunnel_info_to_parm(char dev_name[], struct tunnel_info *info,
+			 struct ip_tunnel_parm *p)
+{
+	memset(p, 0, sizeof(*p));
+
+	strcpy(p->name, dev_name);
+	p->link = info->link;
+	p->i_flags = info->i_flags;
+	p->o_flags = info->o_flags;
+	p->i_key = info->i_key;
+	p->o_key = info->o_key;
+
+	p->iph.version = 4;
+	p->iph.ihl = 5;
+	p->iph.tos = info->tos;
+	p->iph.frag_off = info->df;
+	p->iph.ttl = info->ttl;
+	p->iph.protocol = info->protocol;
+	p->iph.saddr = info->saddr;
+	p->iph.daddr = info->daddr;
+}
+EXPORT_SYMBOL_GPL(tunnel_info_to_parm);
+
+void ip_tunnel_parm_to_info(struct ip_tunnel_parm *p,
+			    struct tunnel_info *info)
+{
+	memset(info, 0, sizeof(*info));
+
+	info->link = p->link;
+	info->i_flags = p->i_flags;
+	info->o_flags = p->o_flags;
+	info->i_key = p->i_key;
+	info->o_key = p->o_key;
+
+	info->tos = p->iph.tos;
+	info->df = p->iph.frag_off;
+	info->ttl = p->iph.ttl;
+	info->protocol = p->iph.protocol;
+	info->saddr = p->iph.saddr;
+	info->daddr = p->iph.daddr;
+}
+EXPORT_SYMBOL_GPL(ip_tunnel_parm_to_info);
+
 static unsigned int ip_tunnel_hash(__be32 key, __be32 remote)
 {
 	return hash_32((__force u32)key ^ (__force u32)remote,
@@ -124,12 +167,12 @@ static struct rtable *tunnel_rtable_get(struct ip_tunnel *t,
 	return (struct rtable *)dst;
 }
 
-static bool ip_tunnel_key_match(const struct ip_tunnel_parm *p,
+static bool ip_tunnel_key_match(const struct tunnel_info *t,
 				__be16 flags, __be32 key)
 {
-	if (p->i_flags & TUNNEL_KEY) {
+	if (t->i_flags & TUNNEL_KEY) {
 		if (flags & TUNNEL_KEY)
-			return key == p->i_key;
+			return key == t->i_key;
 		else
 			/* key expected, none present */
 			return false;
@@ -161,30 +204,30 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 	head = &itn->tunnels[hash];
 
 	hlist_for_each_entry_rcu(t, head, hash_node) {
-		if (local != t->parms.iph.saddr ||
-		    remote != t->parms.iph.daddr ||
+		if (local != t->info.saddr ||
+		    remote != t->info.daddr ||
 		    !(t->dev->flags & IFF_UP))
 			continue;
 
-		if (!ip_tunnel_key_match(&t->parms, flags, key))
+		if (!ip_tunnel_key_match(&t->info, flags, key))
 			continue;
 
-		if (t->parms.link == link)
+		if (t->info.link == link)
 			return t;
 		else
 			cand = t;
 	}
 
 	hlist_for_each_entry_rcu(t, head, hash_node) {
-		if (remote != t->parms.iph.daddr ||
-		    t->parms.iph.saddr != 0 ||
+		if (remote != t->info.daddr ||
+		    t->info.saddr != 0 ||
 		    !(t->dev->flags & IFF_UP))
 			continue;
 
-		if (!ip_tunnel_key_match(&t->parms, flags, key))
+		if (!ip_tunnel_key_match(&t->info, flags, key))
 			continue;
 
-		if (t->parms.link == link)
+		if (t->info.link == link)
 			return t;
 		else if (!cand)
 			cand = t;
@@ -194,17 +237,17 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 	head = &itn->tunnels[hash];
 
 	hlist_for_each_entry_rcu(t, head, hash_node) {
-		if ((local != t->parms.iph.saddr || t->parms.iph.daddr != 0) &&
-		    (local != t->parms.iph.daddr || !ipv4_is_multicast(local)))
+		if ((local != t->info.saddr || t->info.daddr != 0) &&
+		    (local != t->info.daddr || !ipv4_is_multicast(local)))
 			continue;
 
 		if (!(t->dev->flags & IFF_UP))
 			continue;
 
-		if (!ip_tunnel_key_match(&t->parms, flags, key))
+		if (!ip_tunnel_key_match(&t->info, flags, key))
 			continue;
 
-		if (t->parms.link == link)
+		if (t->info.link == link)
 			return t;
 		else if (!cand)
 			cand = t;
@@ -214,13 +257,13 @@ struct ip_tunnel *ip_tunnel_lookup(struct ip_tunnel_net *itn,
 		goto skip_key_lookup;
 
 	hlist_for_each_entry_rcu(t, head, hash_node) {
-		if (t->parms.i_key != key ||
-		    t->parms.iph.saddr != 0 ||
-		    t->parms.iph.daddr != 0 ||
+		if (t->info.i_key != key ||
+		    t->info.saddr != 0 ||
+		    t->info.daddr != 0 ||
 		    !(t->dev->flags & IFF_UP))
 			continue;
 
-		if (t->parms.link == link)
+		if (t->info.link == link)
 			return t;
 		else if (!cand)
 			cand = t;
@@ -239,18 +282,18 @@ skip_key_lookup:
 EXPORT_SYMBOL_GPL(ip_tunnel_lookup);
 
 static struct hlist_head *ip_bucket(struct ip_tunnel_net *itn,
-				    struct ip_tunnel_parm *parms)
+				    struct tunnel_info *info)
 {
 	unsigned int h;
 	__be32 remote;
-	__be32 i_key = parms->i_key;
+	__be32 i_key = info->i_key;
 
-	if (parms->iph.daddr && !ipv4_is_multicast(parms->iph.daddr))
-		remote = parms->iph.daddr;
+	if (info->daddr && !ipv4_is_multicast(info->daddr))
+		remote = info->daddr;
 	else
 		remote = 0;
 
-	if (!(parms->i_flags & TUNNEL_KEY) && (parms->i_flags & VTI_ISVTI))
+	if (!(info->i_flags & TUNNEL_KEY) && (info->i_flags & VTI_ISVTI))
 		i_key = 0;
 
 	h = ip_tunnel_hash(i_key, remote);
@@ -259,7 +302,7 @@ static struct hlist_head *ip_bucket(struct ip_tunnel_net *itn,
 
 static void ip_tunnel_add(struct ip_tunnel_net *itn, struct ip_tunnel *t)
 {
-	struct hlist_head *head = ip_bucket(itn, &t->parms);
+	struct hlist_head *head = ip_bucket(itn, &t->info);
 
 	hlist_add_head_rcu(&t->hash_node, head);
 }
@@ -270,23 +313,23 @@ static void ip_tunnel_del(struct ip_tunnel *t)
 }
 
 static struct ip_tunnel *ip_tunnel_find(struct ip_tunnel_net *itn,
-					struct ip_tunnel_parm *parms,
+					struct tunnel_info *info,
 					int type)
 {
-	__be32 remote = parms->iph.daddr;
-	__be32 local = parms->iph.saddr;
-	__be32 key = parms->i_key;
-	__be16 flags = parms->i_flags;
-	int link = parms->link;
+	__be32 remote = info->daddr;
+	__be32 local = info->saddr;
+	__be32 key = info->i_key;
+	__be16 flags = info->i_flags;
+	int link = info->link;
 	struct ip_tunnel *t = NULL;
-	struct hlist_head *head = ip_bucket(itn, parms);
+	struct hlist_head *head = ip_bucket(itn, info);
 
 	hlist_for_each_entry_rcu(t, head, hash_node) {
-		if (local == t->parms.iph.saddr &&
-		    remote == t->parms.iph.daddr &&
-		    link == t->parms.link &&
+		if (local == t->info.saddr &&
+		    remote == t->info.daddr &&
+		    link == t->info.link &&
 		    type == t->dev->type &&
-		    ip_tunnel_key_match(&t->parms, flags, key))
+		    ip_tunnel_key_match(&t->info, flags, key))
 			break;
 	}
 	return t;
@@ -294,15 +337,16 @@ static struct ip_tunnel *ip_tunnel_find(struct ip_tunnel_net *itn,
 
 static struct net_device *__ip_tunnel_create(struct net *net,
 					     const struct rtnl_link_ops *ops,
-					     struct ip_tunnel_parm *parms)
+					     char *dev_name,
+					     struct tunnel_info *info)
 {
 	int err;
 	struct ip_tunnel *tunnel;
 	struct net_device *dev;
 	char name[IFNAMSIZ];
 
-	if (parms->name[0])
-		strlcpy(name, parms->name, IFNAMSIZ);
+	if (dev_name && dev_name[0])
+		strlcpy(name, dev_name, IFNAMSIZ);
 	else {
 		if (strlen(ops->kind) > (IFNAMSIZ - 3)) {
 			err = -E2BIG;
@@ -323,7 +367,7 @@ static struct net_device *__ip_tunnel_create(struct net *net,
 	dev->rtnl_link_ops = ops;
 
 	tunnel = netdev_priv(dev);
-	tunnel->parms = *parms;
+	tunnel->info = *info;
 	tunnel->net = net;
 
 	err = register_netdevice(dev);
@@ -356,21 +400,19 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
 {
 	struct net_device *tdev = NULL;
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	const struct iphdr *iph;
+	struct tunnel_info *info = &tunnel->info;
 	int hlen = LL_MAX_HEADER;
 	int mtu = ETH_DATA_LEN;
 	int t_hlen = tunnel->hlen + sizeof(struct iphdr);
 
-	iph = &tunnel->parms.iph;
-
 	/* Guess output device to choose reasonable mtu and needed_headroom */
-	if (iph->daddr) {
+	if (info->daddr) {
 		struct flowi4 fl4;
 		struct rtable *rt;
 
-		init_tunnel_flow(&fl4, iph->protocol, iph->daddr,
-				 iph->saddr, tunnel->parms.o_key,
-				 RT_TOS(iph->tos), tunnel->parms.link);
+		init_tunnel_flow(&fl4, info->protocol, info->daddr,
+				 info->saddr, info->o_key,
+				 RT_TOS(info->tos), info->link);
 		rt = ip_route_output_key(tunnel->net, &fl4);
 
 		if (!IS_ERR(rt)) {
@@ -382,14 +424,14 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
 			dev->flags |= IFF_POINTOPOINT;
 	}
 
-	if (!tdev && tunnel->parms.link)
-		tdev = __dev_get_by_index(tunnel->net, tunnel->parms.link);
+	if (!tdev && info->link)
+		tdev = __dev_get_by_index(tunnel->net, info->link);
 
 	if (tdev) {
 		hlen = tdev->hard_header_len + tdev->needed_headroom;
 		mtu = tdev->mtu;
 	}
-	dev->iflink = tunnel->parms.link;
+	dev->iflink = info->link;
 
 	dev->needed_headroom = t_hlen + hlen;
 	mtu -= (dev->hard_header_len + t_hlen);
@@ -402,13 +444,15 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
 
 static struct ip_tunnel *ip_tunnel_create(struct net *net,
 					  struct ip_tunnel_net *itn,
-					  struct ip_tunnel_parm *parms)
+					  char *dev_name,
+					  struct tunnel_info *info)
 {
 	struct ip_tunnel *nt;
 	struct net_device *dev;
 
 	BUG_ON(!itn->fb_tunnel_dev);
-	dev = __ip_tunnel_create(net, itn->fb_tunnel_dev->rtnl_link_ops, parms);
+	dev = __ip_tunnel_create(net, itn->fb_tunnel_dev->rtnl_link_ops,
+				 dev_name, info);
 	if (IS_ERR(dev))
 		return ERR_CAST(dev);
 
@@ -422,6 +466,7 @@ static struct ip_tunnel *ip_tunnel_create(struct net *net,
 int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
 		  const struct tnl_ptk_info *tpi, bool log_ecn_error)
 {
+	struct tunnel_info *info = &tunnel->info;
 	struct pcpu_sw_netstats *tstats;
 	const struct iphdr *iph = ip_hdr(skb);
 	int err;
@@ -433,14 +478,14 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
 	}
 #endif
 
-	if ((!(tpi->flags&TUNNEL_CSUM) &&  (tunnel->parms.i_flags&TUNNEL_CSUM)) ||
-	     ((tpi->flags&TUNNEL_CSUM) && !(tunnel->parms.i_flags&TUNNEL_CSUM))) {
+	if ((!(tpi->flags&TUNNEL_CSUM) &&  (info->i_flags&TUNNEL_CSUM)) ||
+	    ((tpi->flags&TUNNEL_CSUM) && !(info->i_flags&TUNNEL_CSUM))) {
 		tunnel->dev->stats.rx_crc_errors++;
 		tunnel->dev->stats.rx_errors++;
 		goto drop;
 	}
 
-	if (tunnel->parms.i_flags&TUNNEL_SEQ) {
+	if (info->i_flags&TUNNEL_SEQ) {
 		if (!(tpi->flags&TUNNEL_SEQ) ||
 		    (tunnel->i_seqno && (s32)(ntohl(tpi->seq) - tunnel->i_seqno) < 0)) {
 			tunnel->dev->stats.rx_fifo_errors++;
@@ -616,8 +661,8 @@ static int tnl_update_pmtu(struct net_device *dev, struct sk_buff *skb,
 
 		if (rt6 && mtu < dst_mtu(skb_dst(skb)) &&
 			   mtu >= IPV6_MIN_MTU) {
-			if ((tunnel->parms.iph.daddr &&
-			    !ipv4_is_multicast(tunnel->parms.iph.daddr)) ||
+			if ((tunnel->info.daddr &&
+			    !ipv4_is_multicast(tunnel->info.daddr)) ||
 			    rt6->rt6i_dst.plen == 128) {
 				rt6->rt6i_flags |= RTF_MODIFIED;
 				dst_metric_set(skb_dst(skb), RTAX_MTU, mtu);
@@ -635,23 +680,21 @@ static int tnl_update_pmtu(struct net_device *dev, struct sk_buff *skb,
 }
 
 void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
-		    const struct iphdr *tnl_params, u8 protocol)
+		    __be32 saddr, __be32 dst, u8 tos, u8 ttl, __be16 df,
+		    u8 protocol)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
+	struct tunnel_info *info = &tunnel->info;
 	const struct iphdr *inner_iph;
 	struct flowi4 fl4;
-	u8     tos, ttl;
-	__be16 df;
 	struct rtable *rt;		/* Route to the other host */
 	unsigned int max_headroom;	/* The extra header space needed */
-	__be32 dst;
 	int err;
 	bool connected;
 
 	inner_iph = (const struct iphdr *)skb_inner_network_header(skb);
-	connected = (tunnel->parms.iph.daddr != 0);
+	connected = (tunnel->info.daddr != 0);
 
-	dst = tnl_params->daddr;
 	if (dst == 0) {
 		/* NBMA tunnel */
 
@@ -701,7 +744,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
 		connected = false;
 	}
 
-	tos = tnl_params->tos;
 	if (tos & 0x1) {
 		tos &= ~0x1;
 		if (skb->protocol == htons(ETH_P_IP)) {
@@ -713,8 +755,8 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
 		}
 	}
 
-	init_tunnel_flow(&fl4, protocol, dst, tnl_params->saddr,
-			 tunnel->parms.o_key, RT_TOS(tos), tunnel->parms.link);
+	init_tunnel_flow(&fl4, protocol, dst, saddr,
+			 info->o_key, RT_TOS(tos), info->link);
 
 	if (ip_tunnel_encap(skb, tunnel, &protocol, &fl4) < 0)
 		goto tx_error;
@@ -738,7 +780,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
 		goto tx_error;
 	}
 
-	if (tnl_update_pmtu(dev, skb, rt, tnl_params->frag_off)) {
+	if (tnl_update_pmtu(dev, skb, rt, df)) {
 		ip_rt_put(rt);
 		goto tx_error;
 	}
@@ -755,7 +797,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
 	}
 
 	tos = ip_tunnel_ecn_encap(tos, inner_iph, skb);
-	ttl = tnl_params->ttl;
 	if (ttl == 0) {
 		if (skb->protocol == htons(ETH_P_IP))
 			ttl = inner_iph->ttl;
@@ -767,7 +808,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
 			ttl = ip4_dst_hoplimit(&rt->dst);
 	}
 
-	df = tnl_params->frag_off;
 	if (skb->protocol == htons(ETH_P_IP))
 		df |= (inner_iph->frag_off&htons(IP_DF));
 
@@ -802,28 +842,28 @@ EXPORT_SYMBOL_GPL(ip_tunnel_xmit);
 static void ip_tunnel_update(struct ip_tunnel_net *itn,
 			     struct ip_tunnel *t,
 			     struct net_device *dev,
-			     struct ip_tunnel_parm *p,
+			     struct tunnel_info *info,
 			     bool set_mtu)
 {
 	ip_tunnel_del(t);
-	t->parms.iph.saddr = p->iph.saddr;
-	t->parms.iph.daddr = p->iph.daddr;
-	t->parms.i_key = p->i_key;
-	t->parms.o_key = p->o_key;
+	t->info.saddr = info->saddr;
+	t->info.daddr = info->daddr;
+	t->info.i_key = info->i_key;
+	t->info.o_key = info->o_key;
 	if (dev->type != ARPHRD_ETHER) {
-		memcpy(dev->dev_addr, &p->iph.saddr, 4);
-		memcpy(dev->broadcast, &p->iph.daddr, 4);
+		memcpy(dev->dev_addr, &info->saddr, 4);
+		memcpy(dev->broadcast, &info->daddr, 4);
 	}
 	ip_tunnel_add(itn, t);
 
-	t->parms.iph.ttl = p->iph.ttl;
-	t->parms.iph.tos = p->iph.tos;
-	t->parms.iph.frag_off = p->iph.frag_off;
+	t->info.ttl = info->ttl;
+	t->info.tos = info->tos;
+	t->info.df = info->df;
 
-	if (t->parms.link != p->link) {
+	if (t->info.link != info->link) {
 		int mtu;
 
-		t->parms.link = p->link;
+		t->info.link = info->link;
 		mtu = ip_tunnel_bind_dev(dev);
 		if (set_mtu)
 			dev->mtu = mtu;
@@ -832,7 +872,8 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
 	netdev_state_change(dev);
 }
 
-int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
+int ip_tunnel_ioctl(struct net_device *dev, char dev_name[],
+		    struct tunnel_info *info, int cmd)
 {
 	int err = 0;
 	struct ip_tunnel *t = netdev_priv(dev);
@@ -843,11 +884,12 @@ int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
 	switch (cmd) {
 	case SIOCGETTUNNEL:
 		if (dev == itn->fb_tunnel_dev) {
-			t = ip_tunnel_find(itn, p, itn->fb_tunnel_dev->type);
+			t = ip_tunnel_find(itn, info, itn->fb_tunnel_dev->type);
 			if (t == NULL)
 				t = netdev_priv(dev);
 		}
-		memcpy(p, &t->parms, sizeof(*p));
+		strlcpy(dev_name, t->dev->name, IFNAMSIZ);
+		memcpy(info, &t->info, sizeof(*info));
 		break;
 
 	case SIOCADDTUNNEL:
@@ -855,20 +897,20 @@ int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
 		err = -EPERM;
 		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
 			goto done;
-		if (p->iph.ttl)
-			p->iph.frag_off |= htons(IP_DF);
-		if (!(p->i_flags & VTI_ISVTI)) {
-			if (!(p->i_flags & TUNNEL_KEY))
-				p->i_key = 0;
-			if (!(p->o_flags & TUNNEL_KEY))
-				p->o_key = 0;
+		if (info->ttl)
+			info->df |= htons(IP_DF);
+		if (!(info->i_flags & VTI_ISVTI)) {
+			if (!(info->i_flags & TUNNEL_KEY))
+				info->i_key = 0;
+			if (!(info->o_flags & TUNNEL_KEY))
+				info->o_key = 0;
 		}
 
-		t = ip_tunnel_find(itn, p, itn->fb_tunnel_dev->type);
+		t = ip_tunnel_find(itn, info, itn->fb_tunnel_dev->type);
 
 		if (cmd == SIOCADDTUNNEL) {
 			if (!t) {
-				t = ip_tunnel_create(net, itn, p);
+				t = ip_tunnel_create(net, itn, dev_name, info);
 				err = PTR_ERR_OR_ZERO(t);
 				break;
 			}
@@ -885,9 +927,9 @@ int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
 			} else {
 				unsigned int nflags = 0;
 
-				if (ipv4_is_multicast(p->iph.daddr))
+				if (ipv4_is_multicast(info->daddr))
 					nflags = IFF_BROADCAST;
-				else if (p->iph.daddr)
+				else if (info->daddr)
 					nflags = IFF_POINTOPOINT;
 
 				if ((dev->flags^nflags)&(IFF_POINTOPOINT|IFF_BROADCAST)) {
@@ -901,7 +943,7 @@ int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
 
 		if (t) {
 			err = 0;
-			ip_tunnel_update(itn, t, dev, p, true);
+			ip_tunnel_update(itn, t, dev, info, true);
 		} else {
 			err = -ENOENT;
 		}
@@ -914,7 +956,7 @@ int ip_tunnel_ioctl(struct net_device *dev, struct ip_tunnel_parm *p, int cmd)
 
 		if (dev == itn->fb_tunnel_dev) {
 			err = -ENOENT;
-			t = ip_tunnel_find(itn, p, itn->fb_tunnel_dev->type);
+			t = ip_tunnel_find(itn, info, itn->fb_tunnel_dev->type);
 			if (t == NULL)
 				goto done;
 			err = -EPERM;
@@ -981,10 +1023,10 @@ struct net *ip_tunnel_get_link_net(const struct net_device *dev)
 EXPORT_SYMBOL(ip_tunnel_get_link_net);
 
 int ip_tunnel_init_net(struct net *net, int ip_tnl_net_id,
-				  struct rtnl_link_ops *ops, char *devname)
+		       struct rtnl_link_ops *ops, char *dev_name)
 {
 	struct ip_tunnel_net *itn = net_generic(net, ip_tnl_net_id);
-	struct ip_tunnel_parm parms;
+	struct tunnel_info info;
 	unsigned int i;
 
 	for (i = 0; i < IP_TNL_HASH_SIZE; i++)
@@ -995,12 +1037,9 @@ int ip_tunnel_init_net(struct net *net, int ip_tnl_net_id,
 		return 0;
 	}
 
-	memset(&parms, 0, sizeof(parms));
-	if (devname)
-		strlcpy(parms.name, devname, IFNAMSIZ);
-
+	memset(&info, 0, sizeof(info));
 	rtnl_lock();
-	itn->fb_tunnel_dev = __ip_tunnel_create(net, ops, &parms);
+	itn->fb_tunnel_dev = __ip_tunnel_create(net, ops, dev_name, &info);
 	/* FB netdevice is special: we have one, and only one per netns.
 	 * Allowing to move it to another netns is clearly unsafe.
 	 */
@@ -1052,7 +1091,7 @@ void ip_tunnel_delete_net(struct ip_tunnel_net *itn, struct rtnl_link_ops *ops)
 EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
 
 int ip_tunnel_newlink(struct net_device *dev, struct nlattr *tb[],
-		      struct ip_tunnel_parm *p)
+		      struct tunnel_info *info)
 {
 	struct ip_tunnel *nt;
 	struct net *net = dev_net(dev);
@@ -1063,11 +1102,11 @@ int ip_tunnel_newlink(struct net_device *dev, struct nlattr *tb[],
 	nt = netdev_priv(dev);
 	itn = net_generic(net, nt->ip_tnl_net_id);
 
-	if (ip_tunnel_find(itn, p, dev->type))
+	if (ip_tunnel_find(itn, info, dev->type))
 		return -EEXIST;
 
 	nt->net = net;
-	nt->parms = *p;
+	nt->info = *info;
 	err = register_netdevice(dev);
 	if (err)
 		goto out;
@@ -1087,7 +1126,7 @@ out:
 EXPORT_SYMBOL_GPL(ip_tunnel_newlink);
 
 int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
-			 struct ip_tunnel_parm *p)
+			 struct tunnel_info *info)
 {
 	struct ip_tunnel *t;
 	struct ip_tunnel *tunnel = netdev_priv(dev);
@@ -1097,7 +1136,7 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
 	if (dev == itn->fb_tunnel_dev)
 		return -EINVAL;
 
-	t = ip_tunnel_find(itn, p, dev->type);
+	t = ip_tunnel_find(itn, info, dev->type);
 
 	if (t) {
 		if (t->dev != dev)
@@ -1108,9 +1147,9 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
 		if (dev->type != ARPHRD_ETHER) {
 			unsigned int nflags = 0;
 
-			if (ipv4_is_multicast(p->iph.daddr))
+			if (ipv4_is_multicast(info->daddr))
 				nflags = IFF_BROADCAST;
-			else if (p->iph.daddr)
+			else if (info->daddr)
 				nflags = IFF_POINTOPOINT;
 
 			if ((dev->flags ^ nflags) &
@@ -1119,7 +1158,7 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
 		}
 	}
 
-	ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU]);
+	ip_tunnel_update(itn, t, dev, info, !tb[IFLA_MTU]);
 	return 0;
 }
 EXPORT_SYMBOL_GPL(ip_tunnel_changelink);
@@ -1127,7 +1166,6 @@ EXPORT_SYMBOL_GPL(ip_tunnel_changelink);
 int ip_tunnel_init(struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct iphdr *iph = &tunnel->parms.iph;
 	int err;
 
 	dev->destructor	= ip_tunnel_dev_free;
@@ -1150,10 +1188,6 @@ int ip_tunnel_init(struct net_device *dev)
 
 	tunnel->dev = dev;
 	tunnel->net = dev_net(dev);
-	strcpy(tunnel->parms.name, dev->name);
-	iph->version		= 4;
-	iph->ihl		= 5;
-
 	return 0;
 }
 EXPORT_SYMBOL_GPL(ip_tunnel_init);
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
index 94efe14..f25222d 100644
--- a/net/ipv4/ip_vti.c
+++ b/net/ipv4/ip_vti.c
@@ -65,7 +65,7 @@ static int vti_input(struct sk_buff *skb, int nexthdr, __be32 spi,
 			goto drop;
 
 		XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = tunnel;
-		skb->mark = be32_to_cpu(tunnel->parms.i_key);
+		skb->mark = be32_to_cpu(tunnel->info.i_key);
 
 		return xfrm_input(skb, nexthdr, spi, encap_type);
 	}
@@ -148,7 +148,7 @@ static netdev_tx_t vti_xmit(struct sk_buff *skb, struct net_device *dev,
 			    struct flowi *fl)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct ip_tunnel_parm *parms = &tunnel->parms;
+	struct tunnel_info *info = &tunnel->info;
 	struct dst_entry *dst = skb_dst(skb);
 	struct net_device *tdev;	/* Device to other host */
 	int err;
@@ -165,7 +165,7 @@ static netdev_tx_t vti_xmit(struct sk_buff *skb, struct net_device *dev,
 		goto tx_error_icmp;
 	}
 
-	if (!vti_state_check(dst->xfrm, parms->iph.daddr, parms->iph.saddr)) {
+	if (!vti_state_check(dst->xfrm, info->daddr, info->saddr)) {
 		dev->stats.tx_carrier_errors++;
 		dst_release(dst);
 		goto tx_error_icmp;
@@ -216,7 +216,7 @@ static netdev_tx_t vti_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 
 	memset(&fl, 0, sizeof(fl));
 
-	skb->mark = be32_to_cpu(tunnel->parms.o_key);
+	skb->mark = be32_to_cpu(tunnel->info.o_key);
 
 	switch (skb->protocol) {
 	case htons(ETH_P_IP):
@@ -255,7 +255,7 @@ static int vti4_err(struct sk_buff *skb, u32 info)
 	if (!tunnel)
 		return -1;
 
-	mark = be32_to_cpu(tunnel->parms.o_key);
+	mark = be32_to_cpu(tunnel->info.o_key);
 
 	switch (protocol) {
 	case IPPROTO_ESP:
@@ -301,8 +301,9 @@ static int vti4_err(struct sk_buff *skb, u32 info)
 static int
 vti_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
-	int err = 0;
+	struct tunnel_info info;
 	struct ip_tunnel_parm p;
+	int err = 0;
 
 	if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
 		return -EFAULT;
@@ -320,10 +321,12 @@ vti_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 
 	p.i_flags = VTI_ISVTI;
 
-	err = ip_tunnel_ioctl(dev, &p, cmd);
+	ip_tunnel_parm_to_info(&p, &info);
+	err = ip_tunnel_ioctl(dev, p.name, &info, cmd);
 	if (err)
 		return err;
 
+	tunnel_info_to_parm(p.name, &info, &p);
 	if (cmd != SIOCDELTUNNEL) {
 		p.i_flags |= GRE_KEY;
 		p.o_flags |= GRE_KEY;
@@ -353,10 +356,10 @@ static void vti_tunnel_setup(struct net_device *dev)
 static int vti_tunnel_init(struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct iphdr *iph = &tunnel->parms.iph;
+	struct tunnel_info *info = &tunnel->info;
 
-	memcpy(dev->dev_addr, &iph->saddr, 4);
-	memcpy(dev->broadcast, &iph->daddr, 4);
+	memcpy(dev->dev_addr, &info->saddr, 4);
+	memcpy(dev->broadcast, &info->daddr, 4);
 
 	dev->hard_header_len	= LL_MAX_HEADER + sizeof(struct iphdr);
 	dev->mtu		= ETH_DATA_LEN;
@@ -372,11 +375,9 @@ static int vti_tunnel_init(struct net_device *dev)
 static void __net_init vti_fb_tunnel_init(struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct iphdr *iph = &tunnel->parms.iph;
+	struct tunnel_info *info = &tunnel->info;
 
-	iph->version		= 4;
-	iph->protocol		= IPPROTO_IPIP;
-	iph->ihl		= 5;
+	info->protocol		= IPPROTO_IPIP;
 }
 
 static struct xfrm4_protocol vti_esp4_protocol __read_mostly = {
@@ -435,50 +436,50 @@ static int vti_tunnel_validate(struct nlattr *tb[], struct nlattr *data[])
 }
 
 static void vti_netlink_parms(struct nlattr *data[],
-			      struct ip_tunnel_parm *parms)
+			      struct tunnel_info *info)
 {
-	memset(parms, 0, sizeof(*parms));
+	memset(info, 0, sizeof(*info));
 
-	parms->iph.protocol = IPPROTO_IPIP;
+	info->protocol = IPPROTO_IPIP;
 
 	if (!data)
 		return;
 
-	parms->i_flags = VTI_ISVTI;
+	info->i_flags = VTI_ISVTI;
 
 	if (data[IFLA_VTI_LINK])
-		parms->link = nla_get_u32(data[IFLA_VTI_LINK]);
+		info->link = nla_get_u32(data[IFLA_VTI_LINK]);
 
 	if (data[IFLA_VTI_IKEY])
-		parms->i_key = nla_get_be32(data[IFLA_VTI_IKEY]);
+		info->i_key = nla_get_be32(data[IFLA_VTI_IKEY]);
 
 	if (data[IFLA_VTI_OKEY])
-		parms->o_key = nla_get_be32(data[IFLA_VTI_OKEY]);
+		info->o_key = nla_get_be32(data[IFLA_VTI_OKEY]);
 
 	if (data[IFLA_VTI_LOCAL])
-		parms->iph.saddr = nla_get_be32(data[IFLA_VTI_LOCAL]);
+		info->saddr = nla_get_be32(data[IFLA_VTI_LOCAL]);
 
 	if (data[IFLA_VTI_REMOTE])
-		parms->iph.daddr = nla_get_be32(data[IFLA_VTI_REMOTE]);
+		info->daddr = nla_get_be32(data[IFLA_VTI_REMOTE]);
 
 }
 
 static int vti_newlink(struct net *src_net, struct net_device *dev,
 		       struct nlattr *tb[], struct nlattr *data[])
 {
-	struct ip_tunnel_parm parms;
+	struct tunnel_info info;
 
-	vti_netlink_parms(data, &parms);
-	return ip_tunnel_newlink(dev, tb, &parms);
+	vti_netlink_parms(data, &info);
+	return ip_tunnel_newlink(dev, tb, &info);
 }
 
 static int vti_changelink(struct net_device *dev, struct nlattr *tb[],
 			  struct nlattr *data[])
 {
-	struct ip_tunnel_parm p;
+	struct tunnel_info info;
 
-	vti_netlink_parms(data, &p);
-	return ip_tunnel_changelink(dev, tb, &p);
+	vti_netlink_parms(data, &info);
+	return ip_tunnel_changelink(dev, tb, &info);
 }
 
 static size_t vti_get_size(const struct net_device *dev)
@@ -500,13 +501,13 @@ static size_t vti_get_size(const struct net_device *dev)
 static int vti_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
 	struct ip_tunnel *t = netdev_priv(dev);
-	struct ip_tunnel_parm *p = &t->parms;
+	struct tunnel_info *info = &t->info;
 
-	nla_put_u32(skb, IFLA_VTI_LINK, p->link);
-	nla_put_be32(skb, IFLA_VTI_IKEY, p->i_key);
-	nla_put_be32(skb, IFLA_VTI_OKEY, p->o_key);
-	nla_put_be32(skb, IFLA_VTI_LOCAL, p->iph.saddr);
-	nla_put_be32(skb, IFLA_VTI_REMOTE, p->iph.daddr);
+	nla_put_u32(skb, IFLA_VTI_LINK, info->link);
+	nla_put_be32(skb, IFLA_VTI_IKEY, info->i_key);
+	nla_put_be32(skb, IFLA_VTI_OKEY, info->o_key);
+	nla_put_be32(skb, IFLA_VTI_LOCAL, info->saddr);
+	nla_put_be32(skb, IFLA_VTI_REMOTE, info->daddr);
 
 	return 0;
 }
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
index b58d668..b14b33d 100644
--- a/net/ipv4/ipip.c
+++ b/net/ipv4/ipip.c
@@ -149,23 +149,23 @@ static int ipip_err(struct sk_buff *skb, u32 info)
 
 	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
 		ipv4_update_pmtu(skb, dev_net(skb->dev), info,
-				 t->parms.link, 0, IPPROTO_IPIP, 0);
+				 t->info.link, 0, IPPROTO_IPIP, 0);
 		err = 0;
 		goto out;
 	}
 
 	if (type == ICMP_REDIRECT) {
-		ipv4_redirect(skb, dev_net(skb->dev), t->parms.link, 0,
+		ipv4_redirect(skb, dev_net(skb->dev), t->info.link, 0,
 			      IPPROTO_IPIP, 0);
 		err = 0;
 		goto out;
 	}
 
-	if (t->parms.iph.daddr == 0)
+	if (t->info.daddr == 0)
 		goto out;
 
 	err = 0;
-	if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+	if (t->info.ttl == 0 && type == ICMP_TIME_EXCEEDED)
 		goto out;
 
 	if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
@@ -215,7 +215,7 @@ drop:
 static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	const struct iphdr  *tiph = &tunnel->parms.iph;
+	struct tunnel_info *info = &tunnel->info;
 
 	if (unlikely(skb->protocol != htons(ETH_P_IP)))
 		goto tx_error;
@@ -226,7 +226,8 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 
 	skb_set_inner_ipproto(skb, IPPROTO_IPIP);
 
-	ip_tunnel_xmit(skb, dev, tiph, tiph->protocol);
+	ip_tunnel_xmit(skb, dev, info->saddr, info->daddr, info->tos,
+		       info->ttl, info->df, IPPROTO_IPIP);
 	return NETDEV_TX_OK;
 
 tx_error:
@@ -239,8 +240,9 @@ out:
 static int
 ipip_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
-	int err = 0;
+	struct tunnel_info info;
 	struct ip_tunnel_parm p;
+	int err = 0;
 
 	if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
 		return -EFAULT;
@@ -251,14 +253,19 @@ ipip_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 			return -EINVAL;
 	}
 
-	p.i_key = p.o_key = p.i_flags = p.o_flags = 0;
+	p.i_key = 0;
+	p.o_key = 0;
+	p.i_flags = 0;
+	p.o_flags = 0;
 	if (p.iph.ttl)
 		p.iph.frag_off |= htons(IP_DF);
 
-	err = ip_tunnel_ioctl(dev, &p, cmd);
+	ip_tunnel_parm_to_info(&p, &info);
+	err = ip_tunnel_ioctl(dev, p.name, &info, cmd);
 	if (err)
 		return err;
 
+	tunnel_info_to_parm(p.name, &info, &p);
 	if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
 		return -EFAULT;
 
@@ -300,47 +307,44 @@ static int ipip_tunnel_init(struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
 
-	memcpy(dev->dev_addr, &tunnel->parms.iph.saddr, 4);
-	memcpy(dev->broadcast, &tunnel->parms.iph.daddr, 4);
+	memcpy(dev->dev_addr, &tunnel->info.saddr, 4);
+	memcpy(dev->broadcast, &tunnel->info.daddr, 4);
 
 	tunnel->tun_hlen = 0;
 	tunnel->hlen = tunnel->tun_hlen + tunnel->encap_hlen;
-	tunnel->parms.iph.protocol = IPPROTO_IPIP;
 	return ip_tunnel_init(dev);
 }
 
 static void ipip_netlink_parms(struct nlattr *data[],
-			       struct ip_tunnel_parm *parms)
+			       struct tunnel_info *info)
 {
-	memset(parms, 0, sizeof(*parms));
+	memset(info, 0, sizeof(*info));
 
-	parms->iph.version = 4;
-	parms->iph.protocol = IPPROTO_IPIP;
-	parms->iph.ihl = 5;
+	info->protocol = IPPROTO_IPIP;
 
 	if (!data)
 		return;
 
 	if (data[IFLA_IPTUN_LINK])
-		parms->link = nla_get_u32(data[IFLA_IPTUN_LINK]);
+		info->link = nla_get_u32(data[IFLA_IPTUN_LINK]);
 
 	if (data[IFLA_IPTUN_LOCAL])
-		parms->iph.saddr = nla_get_be32(data[IFLA_IPTUN_LOCAL]);
+		info->saddr = nla_get_be32(data[IFLA_IPTUN_LOCAL]);
 
 	if (data[IFLA_IPTUN_REMOTE])
-		parms->iph.daddr = nla_get_be32(data[IFLA_IPTUN_REMOTE]);
+		info->daddr = nla_get_be32(data[IFLA_IPTUN_REMOTE]);
 
 	if (data[IFLA_IPTUN_TTL]) {
-		parms->iph.ttl = nla_get_u8(data[IFLA_IPTUN_TTL]);
-		if (parms->iph.ttl)
-			parms->iph.frag_off = htons(IP_DF);
+		info->ttl = nla_get_u8(data[IFLA_IPTUN_TTL]);
+		if (info->ttl)
+			info->df = htons(IP_DF);
 	}
 
 	if (data[IFLA_IPTUN_TOS])
-		parms->iph.tos = nla_get_u8(data[IFLA_IPTUN_TOS]);
+		info->tos = nla_get_u8(data[IFLA_IPTUN_TOS]);
 
 	if (!data[IFLA_IPTUN_PMTUDISC] || nla_get_u8(data[IFLA_IPTUN_PMTUDISC]))
-		parms->iph.frag_off = htons(IP_DF);
+		info->df = htons(IP_DF);
 }
 
 /* This function returns true when ENCAP attributes are present in the nl msg */
@@ -380,7 +384,7 @@ static bool ipip_netlink_encap_parms(struct nlattr *data[],
 static int ipip_newlink(struct net *src_net, struct net_device *dev,
 			struct nlattr *tb[], struct nlattr *data[])
 {
-	struct ip_tunnel_parm p;
+	struct tunnel_info info;
 	struct ip_tunnel_encap ipencap;
 
 	if (ipip_netlink_encap_parms(data, &ipencap)) {
@@ -391,14 +395,14 @@ static int ipip_newlink(struct net *src_net, struct net_device *dev,
 			return err;
 	}
 
-	ipip_netlink_parms(data, &p);
-	return ip_tunnel_newlink(dev, tb, &p);
+	ipip_netlink_parms(data, &info);
+	return ip_tunnel_newlink(dev, tb, &info);
 }
 
 static int ipip_changelink(struct net_device *dev, struct nlattr *tb[],
 			   struct nlattr *data[])
 {
-	struct ip_tunnel_parm p;
+	struct tunnel_info info;
 	struct ip_tunnel_encap ipencap;
 
 	if (ipip_netlink_encap_parms(data, &ipencap)) {
@@ -409,13 +413,13 @@ static int ipip_changelink(struct net_device *dev, struct nlattr *tb[],
 			return err;
 	}
 
-	ipip_netlink_parms(data, &p);
+	ipip_netlink_parms(data, &info);
 
-	if (((dev->flags & IFF_POINTOPOINT) && !p.iph.daddr) ||
-	    (!(dev->flags & IFF_POINTOPOINT) && p.iph.daddr))
+	if (((dev->flags & IFF_POINTOPOINT) && !info.daddr) ||
+	    (!(dev->flags & IFF_POINTOPOINT) && info.daddr))
 		return -EINVAL;
 
-	return ip_tunnel_changelink(dev, tb, &p);
+	return ip_tunnel_changelink(dev, tb, &info);
 }
 
 static size_t ipip_get_size(const struct net_device *dev)
@@ -447,15 +451,15 @@ static size_t ipip_get_size(const struct net_device *dev)
 static int ipip_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct ip_tunnel_parm *parm = &tunnel->parms;
+	struct tunnel_info *info = &tunnel->info;
 
-	if (nla_put_u32(skb, IFLA_IPTUN_LINK, parm->link) ||
-	    nla_put_be32(skb, IFLA_IPTUN_LOCAL, parm->iph.saddr) ||
-	    nla_put_be32(skb, IFLA_IPTUN_REMOTE, parm->iph.daddr) ||
-	    nla_put_u8(skb, IFLA_IPTUN_TTL, parm->iph.ttl) ||
-	    nla_put_u8(skb, IFLA_IPTUN_TOS, parm->iph.tos) ||
+	if (nla_put_u32(skb, IFLA_IPTUN_LINK, info->link) ||
+	    nla_put_be32(skb, IFLA_IPTUN_LOCAL, info->saddr) ||
+	    nla_put_be32(skb, IFLA_IPTUN_REMOTE, info->daddr) ||
+	    nla_put_u8(skb, IFLA_IPTUN_TTL, info->ttl) ||
+	    nla_put_u8(skb, IFLA_IPTUN_TOS, info->tos) ||
 	    nla_put_u8(skb, IFLA_IPTUN_PMTUDISC,
-		       !!(parm->iph.frag_off & htons(IP_DF))))
+		       !!(info->df & htons(IP_DF))))
 		goto nla_put_failure;
 
 	if (nla_put_u16(skb, IFLA_IPTUN_ENCAP_TYPE,
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index 3cc197c..02eb387 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -99,21 +99,21 @@ static struct ip_tunnel *ipip6_tunnel_lookup(struct net *net,
 	struct sit_net *sitn = net_generic(net, sit_net_id);
 
 	for_each_ip_tunnel_rcu(t, sitn->tunnels_r_l[h0 ^ h1]) {
-		if (local == t->parms.iph.saddr &&
-		    remote == t->parms.iph.daddr &&
-		    (!dev || !t->parms.link || dev->ifindex == t->parms.link) &&
+		if (local == t->info.saddr &&
+		    remote == t->info.daddr &&
+		    (!dev || !t->info.link || dev->ifindex == t->info.link) &&
 		    (t->dev->flags & IFF_UP))
 			return t;
 	}
 	for_each_ip_tunnel_rcu(t, sitn->tunnels_r[h0]) {
-		if (remote == t->parms.iph.daddr &&
-		    (!dev || !t->parms.link || dev->ifindex == t->parms.link) &&
+		if (remote == t->info.daddr &&
+		    (!dev || !t->info.link || dev->ifindex == t->info.link) &&
 		    (t->dev->flags & IFF_UP))
 			return t;
 	}
 	for_each_ip_tunnel_rcu(t, sitn->tunnels_l[h1]) {
-		if (local == t->parms.iph.saddr &&
-		    (!dev || !t->parms.link || dev->ifindex == t->parms.link) &&
+		if (local == t->info.saddr &&
+		    (!dev || !t->info.link || dev->ifindex == t->info.link) &&
 		    (t->dev->flags & IFF_UP))
 			return t;
 	}
@@ -124,10 +124,9 @@ static struct ip_tunnel *ipip6_tunnel_lookup(struct net *net,
 }
 
 static struct ip_tunnel __rcu **__ipip6_bucket(struct sit_net *sitn,
-		struct ip_tunnel_parm *parms)
+					       __be32 remote,
+					       __be32 local)
 {
-	__be32 remote = parms->iph.daddr;
-	__be32 local = parms->iph.saddr;
 	unsigned int h = 0;
 	int prio = 0;
 
@@ -145,7 +144,7 @@ static struct ip_tunnel __rcu **__ipip6_bucket(struct sit_net *sitn,
 static inline struct ip_tunnel __rcu **ipip6_bucket(struct sit_net *sitn,
 		struct ip_tunnel *t)
 {
-	return __ipip6_bucket(sitn, &t->parms);
+	return __ipip6_bucket(sitn, t->info.daddr, t->info.saddr);
 }
 
 static void ipip6_tunnel_unlink(struct sit_net *sitn, struct ip_tunnel *t)
@@ -188,17 +187,20 @@ static void ipip6_tunnel_clone_6rd(struct net_device *dev, struct sit_net *sitn)
 #endif
 }
 
-static int ipip6_tunnel_create(struct net_device *dev)
+static int ipip6_tunnel_create(struct net_device *dev,
+			       struct ip_tunnel_parm *parms)
 {
 	struct ip_tunnel *t = netdev_priv(dev);
 	struct net *net = dev_net(dev);
 	struct sit_net *sitn = net_generic(net, sit_net_id);
 	int err;
 
-	memcpy(dev->dev_addr, &t->parms.iph.saddr, 4);
-	memcpy(dev->broadcast, &t->parms.iph.daddr, 4);
+	ip_tunnel_parm_to_info(parms, &t->info);
+
+	memcpy(dev->dev_addr, &t->info.saddr, 4);
+	memcpy(dev->broadcast, &t->info.daddr, 4);
 
-	if ((__force u16)t->parms.i_flags & SIT_ISATAP)
+	if ((__force u16)t->info.i_flags & SIT_ISATAP)
 		dev->priv_flags |= IFF_ISATAP;
 
 	err = register_netdevice(dev);
@@ -229,12 +231,12 @@ static struct ip_tunnel *ipip6_tunnel_locate(struct net *net,
 	char name[IFNAMSIZ];
 	struct sit_net *sitn = net_generic(net, sit_net_id);
 
-	for (tp = __ipip6_bucket(sitn, parms);
+	for (tp = __ipip6_bucket(sitn, remote, local);
 	    (t = rtnl_dereference(*tp)) != NULL;
 	     tp = &t->next) {
-		if (local == t->parms.iph.saddr &&
-		    remote == t->parms.iph.daddr &&
-		    parms->link == t->parms.link) {
+		if (local == t->info.saddr &&
+		    remote == t->info.daddr &&
+		    parms->link == t->info.link) {
 			if (create)
 				return NULL;
 			else
@@ -258,8 +260,7 @@ static struct ip_tunnel *ipip6_tunnel_locate(struct net *net,
 
 	nt = netdev_priv(dev);
 
-	nt->parms = *parms;
-	if (ipip6_tunnel_create(dev) < 0)
+	if (ipip6_tunnel_create(dev, parms) < 0)
 		goto failed_free;
 
 	return nt;
@@ -560,25 +561,25 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
 
 	if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
 		ipv4_update_pmtu(skb, dev_net(skb->dev), info,
-				 t->parms.link, 0, IPPROTO_IPV6, 0);
+				 t->info.link, 0, IPPROTO_IPV6, 0);
 		err = 0;
 		goto out;
 	}
 	if (type == ICMP_REDIRECT) {
-		ipv4_redirect(skb, dev_net(skb->dev), t->parms.link, 0,
+		ipv4_redirect(skb, dev_net(skb->dev), t->info.link, 0,
 			      IPPROTO_IPV6, 0);
 		err = 0;
 		goto out;
 	}
 
-	if (t->parms.iph.daddr == 0)
+	if (t->info.daddr == 0)
 		goto out;
 
 	err = 0;
 	if (!ipip6_err_gen_icmpv6_unreach(skb))
 		goto out;
 
-	if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+	if (t->info.ttl == 0 && type == ICMP_TIME_EXCEEDED)
 		goto out;
 
 	if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
@@ -674,8 +675,8 @@ static int ipip6_rcv(struct sk_buff *skb)
 	if (tunnel != NULL) {
 		struct pcpu_sw_netstats *tstats;
 
-		if (tunnel->parms.iph.protocol != IPPROTO_IPV6 &&
-		    tunnel->parms.iph.protocol != 0)
+		if (tunnel->info.protocol != IPPROTO_IPV6 &&
+		    tunnel->info.protocol != 0)
 			goto out;
 
 		skb->mac_header = skb->network_header;
@@ -734,8 +735,8 @@ static int ipip_rcv(struct sk_buff *skb)
 	tunnel = ipip6_tunnel_lookup(dev_net(skb->dev), skb->dev,
 				     iph->saddr, iph->daddr);
 	if (tunnel != NULL) {
-		if (tunnel->parms.iph.protocol != IPPROTO_IPIP &&
-		    tunnel->parms.iph.protocol != 0)
+		if (tunnel->info.protocol != IPPROTO_IPIP &&
+		    tunnel->info.protocol != 0)
 			goto drop;
 
 		if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
@@ -807,14 +808,14 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
 				     struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	const struct iphdr  *tiph = &tunnel->parms.iph;
+	const struct tunnel_info *info = &tunnel->info;
 	const struct ipv6hdr *iph6 = ipv6_hdr(skb);
-	u8     tos = tunnel->parms.iph.tos;
-	__be16 df = tiph->frag_off;
+	u8     tos = tunnel->info.tos;
+	__be16 df = info->df;
 	struct rtable *rt;		/* Route to the other host */
 	struct net_device *tdev;	/* Device to other host */
 	unsigned int max_headroom;	/* The extra header space needed */
-	__be32 dst = tiph->daddr;
+	__be32 dst = info->daddr;
 	struct flowi4 fl4;
 	int    mtu;
 	const struct in6_addr *addr6;
@@ -891,10 +892,10 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
 	}
 
 	rt = ip_route_output_ports(tunnel->net, &fl4, NULL,
-				   dst, tiph->saddr,
+				   dst, info->saddr,
 				   0, 0,
 				   IPPROTO_IPV6, RT_TOS(tos),
-				   tunnel->parms.link);
+				   tunnel->info.link);
 	if (IS_ERR(rt)) {
 		dev->stats.tx_carrier_errors++;
 		goto tx_error_icmp;
@@ -932,7 +933,7 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
 			df = 0;
 		}
 
-		if (tunnel->parms.iph.daddr && skb_dst(skb))
+		if (tunnel->info.daddr && skb_dst(skb))
 			skb_dst(skb)->ops->update_pmtu(skb_dst(skb), NULL, skb, mtu);
 
 		if (skb->len > mtu && !skb_is_gso(skb)) {
@@ -971,7 +972,7 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
 		skb = new_skb;
 		iph6 = ipv6_hdr(skb);
 	}
-	ttl = tiph->ttl;
+	ttl = info->ttl;
 	if (ttl == 0)
 		ttl = iph6->hop_limit;
 	tos = INET_ECN_encapsulate(tos, ipv6_get_dsfield(iph6));
@@ -1001,15 +1002,15 @@ out:
 static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	const struct iphdr  *tiph = &tunnel->parms.iph;
+	const struct tunnel_info *info = &tunnel->info;
 
 	skb = iptunnel_handle_offloads(skb, false, SKB_GSO_IPIP);
 	if (IS_ERR(skb))
 		goto out;
 
 	skb_set_inner_ipproto(skb, IPPROTO_IPIP);
-
-	ip_tunnel_xmit(skb, dev, tiph, IPPROTO_IPIP);
+	ip_tunnel_xmit(skb, dev, info->saddr, info->daddr, info->tos, info->ttl,
+		       info->df, IPPROTO_IPIP);
 	return NETDEV_TX_OK;
 out:
 	dev->stats.tx_errors++;
@@ -1043,20 +1044,20 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev)
 {
 	struct net_device *tdev = NULL;
 	struct ip_tunnel *tunnel;
-	const struct iphdr *iph;
+	struct tunnel_info *info;
 	struct flowi4 fl4;
 
 	tunnel = netdev_priv(dev);
-	iph = &tunnel->parms.iph;
+	info = &tunnel->info;
 
-	if (iph->daddr) {
+	if (info->daddr) {
 		struct rtable *rt = ip_route_output_ports(tunnel->net, &fl4,
 							  NULL,
-							  iph->daddr, iph->saddr,
+							  info->daddr, info->saddr,
 							  0, 0,
 							  IPPROTO_IPV6,
-							  RT_TOS(iph->tos),
-							  tunnel->parms.link);
+							  RT_TOS(info->tos),
+							  tunnel->info.link);
 
 		if (!IS_ERR(rt)) {
 			tdev = rt->dst.dev;
@@ -1065,8 +1066,8 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev)
 		dev->flags |= IFF_POINTOPOINT;
 	}
 
-	if (!tdev && tunnel->parms.link)
-		tdev = __dev_get_by_index(tunnel->net, tunnel->parms.link);
+	if (!tdev && tunnel->info.link)
+		tdev = __dev_get_by_index(tunnel->net, tunnel->info.link);
 
 	if (tdev) {
 		int t_hlen = tunnel->hlen + sizeof(struct iphdr);
@@ -1076,7 +1077,7 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev)
 		if (dev->mtu < IPV6_MIN_MTU)
 			dev->mtu = IPV6_MIN_MTU;
 	}
-	dev->iflink = tunnel->parms.link;
+	dev->iflink = tunnel->info.link;
 }
 
 static void ipip6_tunnel_update(struct ip_tunnel *t, struct ip_tunnel_parm *p)
@@ -1086,15 +1087,15 @@ static void ipip6_tunnel_update(struct ip_tunnel *t, struct ip_tunnel_parm *p)
 
 	ipip6_tunnel_unlink(sitn, t);
 	synchronize_net();
-	t->parms.iph.saddr = p->iph.saddr;
-	t->parms.iph.daddr = p->iph.daddr;
+	t->info.saddr = p->iph.saddr;
+	t->info.daddr = p->iph.daddr;
 	memcpy(t->dev->dev_addr, &p->iph.saddr, 4);
 	memcpy(t->dev->broadcast, &p->iph.daddr, 4);
 	ipip6_tunnel_link(sitn, t);
-	t->parms.iph.ttl = p->iph.ttl;
-	t->parms.iph.tos = p->iph.tos;
-	if (t->parms.link != p->link) {
-		t->parms.link = p->link;
+	t->info.ttl = p->iph.ttl;
+	t->info.tos = p->iph.tos;
+	if (t->info.link != p->link) {
+		t->info.link = p->link;
 		ipip6_tunnel_bind_dev(t->dev);
 	}
 	ip_tunnel_dst_reset_all(t);
@@ -1164,7 +1165,7 @@ ipip6_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 
 		err = -EFAULT;
 		if (cmd == SIOCGETTUNNEL) {
-			memcpy(&p, &t->parms, sizeof(p));
+			tunnel_info_to_parm(t->dev->name, &t->info, &p);
 			if (copy_to_user(ifr->ifr_ifru.ifru_data, &p,
 					 sizeof(p)))
 				goto done;
@@ -1225,7 +1226,8 @@ ipip6_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 
 		if (t) {
 			err = 0;
-			if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
+			tunnel_info_to_parm(t->dev->name, &t->info, &p);
+			if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
 				err = -EFAULT;
 		} else
 			err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
@@ -1379,7 +1381,6 @@ static int ipip6_tunnel_init(struct net_device *dev)
 
 	tunnel->dev = dev;
 	tunnel->net = dev_net(dev);
-	strcpy(tunnel->parms.name, dev->name);
 
 	ipip6_tunnel_bind_dev(dev);
 	dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
@@ -1398,17 +1399,15 @@ static int ipip6_tunnel_init(struct net_device *dev)
 static int __net_init ipip6_fb_tunnel_init(struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct iphdr *iph = &tunnel->parms.iph;
+	struct tunnel_info *info = &tunnel->info;
 	struct net *net = dev_net(dev);
 	struct sit_net *sitn = net_generic(net, sit_net_id);
 
 	tunnel->dev = dev;
 	tunnel->net = dev_net(dev);
 
-	iph->version		= 4;
-	iph->protocol		= IPPROTO_IPV6;
-	iph->ihl		= 5;
-	iph->ttl		= 64;
+	info->protocol		= IPPROTO_IPV6;
+	info->ttl		= 64;
 
 	dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
 	if (!dev->tstats)
@@ -1561,6 +1560,7 @@ static int ipip6_newlink(struct net *src_net, struct net_device *dev,
 	struct net *net = dev_net(dev);
 	struct ip_tunnel *nt;
 	struct ip_tunnel_encap ipencap;
+	struct ip_tunnel_parm parms;
 #ifdef CONFIG_IPV6_SIT_6RD
 	struct ip_tunnel_6rd ip6rd;
 #endif
@@ -1574,12 +1574,12 @@ static int ipip6_newlink(struct net *src_net, struct net_device *dev,
 			return err;
 	}
 
-	ipip6_netlink_parms(data, &nt->parms);
+	ipip6_netlink_parms(data, &parms);
 
-	if (ipip6_tunnel_locate(net, &nt->parms, 0))
+	if (ipip6_tunnel_locate(net, &parms, 0))
 		return -EEXIST;
 
-	err = ipip6_tunnel_create(dev);
+	err = ipip6_tunnel_create(dev, &parms);
 	if (err < 0)
 		return err;
 
@@ -1680,17 +1680,17 @@ static size_t ipip6_get_size(const struct net_device *dev)
 static int ipip6_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	struct ip_tunnel_parm *parm = &tunnel->parms;
+	struct tunnel_info *info = &tunnel->info;
 
-	if (nla_put_u32(skb, IFLA_IPTUN_LINK, parm->link) ||
-	    nla_put_be32(skb, IFLA_IPTUN_LOCAL, parm->iph.saddr) ||
-	    nla_put_be32(skb, IFLA_IPTUN_REMOTE, parm->iph.daddr) ||
-	    nla_put_u8(skb, IFLA_IPTUN_TTL, parm->iph.ttl) ||
-	    nla_put_u8(skb, IFLA_IPTUN_TOS, parm->iph.tos) ||
+	if (nla_put_u32(skb, IFLA_IPTUN_LINK, info->link) ||
+	    nla_put_be32(skb, IFLA_IPTUN_LOCAL, info->saddr) ||
+	    nla_put_be32(skb, IFLA_IPTUN_REMOTE, info->daddr) ||
+	    nla_put_u8(skb, IFLA_IPTUN_TTL, info->ttl) ||
+	    nla_put_u8(skb, IFLA_IPTUN_TOS, info->tos) ||
 	    nla_put_u8(skb, IFLA_IPTUN_PMTUDISC,
-		       !!(parm->iph.frag_off & htons(IP_DF))) ||
-	    nla_put_u8(skb, IFLA_IPTUN_PROTO, parm->iph.protocol) ||
-	    nla_put_be16(skb, IFLA_IPTUN_FLAGS, parm->i_flags))
+		       !!(info->df & htons(IP_DF))) ||
+	    nla_put_u8(skb, IFLA_IPTUN_PROTO, info->protocol) ||
+	    nla_put_be16(skb, IFLA_IPTUN_FLAGS, info->i_flags))
 		goto nla_put_failure;
 
 #ifdef CONFIG_IPV6_SIT_6RD
@@ -1843,8 +1843,6 @@ static int __net_init sit_init_net(struct net *net)
 		goto err_reg_dev;
 
 	t = netdev_priv(sitn->fb_tunnel_dev);
-
-	strcpy(t->parms.name, sitn->fb_tunnel_dev->name);
 	return 0;
 
 err_reg_dev:
-- 
1.9.1

^ permalink raw reply related

* [PATCH net-next v2 3/6] openvswitch: Add support for STT tunneling.
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar, Jesse Gross

Use STT driver to create STT tunnel port.

Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: Jesse Gross <jesse@nicira.com>
---
 include/uapi/linux/openvswitch.h |   1 +
 net/openvswitch/Kconfig          |  10 ++
 net/openvswitch/Makefile         |   1 +
 net/openvswitch/vport-stt.c      | 216 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 228 insertions(+)
 create mode 100644 net/openvswitch/vport-stt.c

diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h
index 7a8785a..a1342f6 100644
--- a/include/uapi/linux/openvswitch.h
+++ b/include/uapi/linux/openvswitch.h
@@ -204,6 +204,7 @@ enum ovs_vport_type {
 	OVS_VPORT_TYPE_GRE,      /* GRE tunnel. */
 	OVS_VPORT_TYPE_VXLAN,	 /* VXLAN tunnel. */
 	OVS_VPORT_TYPE_GENEVE,	 /* Geneve tunnel. */
+	OVS_VPORT_TYPE_STT,	 /* STT tunnel. */
 	__OVS_VPORT_TYPE_MAX
 };
 
diff --git a/net/openvswitch/Kconfig b/net/openvswitch/Kconfig
index b7d818c..65b5fb7 100644
--- a/net/openvswitch/Kconfig
+++ b/net/openvswitch/Kconfig
@@ -64,3 +64,13 @@ config OPENVSWITCH_GENEVE
 	  If you say Y here, then the Open vSwitch will be able create geneve vport.
 
 	  Say N to exclude this support and reduce the binary size.
+
+config OPENVSWITCH_STT
+	tristate "Open vSwitch STT tunneling support"
+	depends on OPENVSWITCH
+	depends on STT
+	default OPENVSWITCH
+	---help---
+	  If you say Y here, then the Open vSwitch will be able create stt vport.
+
+	  Say N to exclude this support and reduce the binary size.
diff --git a/net/openvswitch/Makefile b/net/openvswitch/Makefile
index 91b9478..352bd05 100644
--- a/net/openvswitch/Makefile
+++ b/net/openvswitch/Makefile
@@ -18,3 +18,4 @@ openvswitch-y := \
 obj-$(CONFIG_OPENVSWITCH_GENEVE)+= vport-geneve.o
 obj-$(CONFIG_OPENVSWITCH_VXLAN)	+= vport-vxlan.o
 obj-$(CONFIG_OPENVSWITCH_GRE)	+= vport-gre.o
+obj-$(CONFIG_OPENVSWITCH_STT)	+= vport-stt.o
diff --git a/net/openvswitch/vport-stt.c b/net/openvswitch/vport-stt.c
new file mode 100644
index 0000000..d892da0
--- /dev/null
+++ b/net/openvswitch/vport-stt.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2014 Nicira, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/if_vlan.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/rculist.h>
+#include <linux/udp.h>
+
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/stt.h>
+#include <net/udp.h>
+
+#include "datapath.h"
+#include "vport.h"
+
+static struct vport_ops ovs_stt_vport_ops;
+
+/**
+ * struct stt_port - Keeps track of open UDP ports
+ * @stt_sock: The socket created for this port number.
+ * @name: vport name.
+ */
+struct stt_port {
+	struct stt_sock *stt_sock;
+	char name[IFNAMSIZ];
+};
+
+static inline struct stt_port *stt_vport(const struct vport *vport)
+{
+	return vport_priv(vport);
+}
+
+static void stt_rcv(struct stt_sock *stt_sock, struct sk_buff *skb)
+{
+	struct vport *vport = stt_sock->rcv_data;
+	struct stthdr *stth = stt_hdr(skb);
+	struct ovs_tunnel_info tun_info;
+	struct sk_buff *next;
+
+	ovs_flow_tun_info_init(&tun_info, ip_hdr(skb),
+			       tcp_hdr(skb)->source, tcp_hdr(skb)->dest,
+			       get_unaligned(&stth->key),
+			       TUNNEL_KEY | TUNNEL_CSUM,
+			       NULL, 0);
+	do {
+		next = skb->next;
+		skb->next = NULL;
+		ovs_vport_receive(vport, skb, &tun_info);
+	} while ((skb = next));
+}
+
+static int stt_tnl_get_options(const struct vport *vport,
+			       struct sk_buff *skb)
+{
+	struct stt_port *stt_port = stt_vport(vport);
+	struct inet_sock *sk = inet_sk(stt_port->stt_sock->sock->sk);
+
+	if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(sk->inet_sport)))
+		return -EMSGSIZE;
+	return 0;
+}
+
+static void stt_tnl_destroy(struct vport *vport)
+{
+	struct stt_port *stt_port = stt_vport(vport);
+	struct net *net = ovs_dp_get_net(vport->dp);
+	__be16 dport = inet_sk(stt_port->stt_sock->sock->sk)->inet_sport;
+
+	stt_sock_release(net, dport);
+	ovs_vport_deferred_free(vport);
+}
+
+static struct vport *stt_tnl_create(const struct vport_parms *parms)
+{
+	struct net *net = ovs_dp_get_net(parms->dp);
+	struct nlattr *options = parms->options;
+	struct stt_port *stt_port;
+	struct stt_sock *stt_sock;
+	struct vport *vport;
+	struct nlattr *a;
+	int err;
+	u16 dst_port;
+
+	if (!options) {
+		err = -EINVAL;
+		goto error;
+	}
+
+	a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT);
+	if (a && nla_len(a) == sizeof(u16)) {
+		dst_port = nla_get_u16(a);
+	} else {
+		/* Require destination port from userspace. */
+		err = -EINVAL;
+		goto error;
+	}
+
+	vport = ovs_vport_alloc(sizeof(struct stt_port),
+				&ovs_stt_vport_ops, parms);
+	if (IS_ERR(vport))
+		return vport;
+
+	stt_port = stt_vport(vport);
+	strncpy(stt_port->name, parms->name, IFNAMSIZ);
+
+	stt_sock = stt_sock_add(net, htons(dst_port), stt_rcv, vport);
+	if (IS_ERR(stt_sock)) {
+		ovs_vport_free(vport);
+		return (void *)stt_sock;
+	}
+	stt_port->stt_sock = stt_sock;
+
+	return vport;
+error:
+	return ERR_PTR(err);
+}
+
+static int stt_tnl_send(struct vport *vport, struct sk_buff *skb)
+{
+	struct net *net = ovs_dp_get_net(vport->dp);
+	struct stt_port *stt_port = stt_vport(vport);
+	__be16 dport = inet_sk(stt_port->stt_sock->sock->sk)->inet_sport;
+	const struct ovs_key_ipv4_tunnel *tun_key;
+	const struct ovs_tunnel_info *tun_info;
+	struct rtable *rt;
+	struct flowi4 fl;
+	__be16 sport;
+	__be16 df;
+	int err;
+
+	tun_info = OVS_CB(skb)->egress_tun_info;
+	if (unlikely(!tun_info)) {
+		err = -EINVAL;
+		goto error;
+	}
+
+	tun_key = &tun_info->tunnel;
+	rt = ovs_tunnel_route_lookup(net, tun_key, skb->mark, &fl, IPPROTO_TCP);
+	if (IS_ERR(rt)) {
+		err = PTR_ERR(rt);
+		goto error;
+	}
+
+	df = tun_key->tun_flags & TUNNEL_DONT_FRAGMENT ? htons(IP_DF) : 0;
+	sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
+	skb->ignore_df = 1;
+
+	return stt_xmit_skb(skb, rt, fl.saddr, tun_key->ipv4_dst,
+			   tun_key->ipv4_tos, tun_key->ipv4_ttl,
+			   df, sport, dport, tun_key->tun_id);
+error:
+	kfree_skb(skb);
+	return err;
+}
+
+static const char *stt_tnl_get_name(const struct vport *vport)
+{
+	return stt_vport(vport)->name;
+}
+
+static int stt_get_egress_tun_info(struct vport *vport, struct sk_buff *skb,
+				   struct ovs_tunnel_info *egress_tun_info)
+{
+	struct stt_port *stt_port = stt_vport(vport);
+	struct net *net = ovs_dp_get_net(vport->dp);
+	__be16 dport = inet_sk(stt_port->stt_sock->sock->sk)->inet_sport;
+	__be16 sport = udp_flow_src_port(net, skb, 1, USHRT_MAX, true);
+
+	/* Get tp_src and tp_dst, refert to stt_build_header().
+	 */
+	return ovs_tunnel_get_egress_info(egress_tun_info,
+					  ovs_dp_get_net(vport->dp),
+					  OVS_CB(skb)->egress_tun_info,
+					  IPPROTO_UDP, skb->mark, sport, dport);
+}
+
+static struct vport_ops ovs_stt_vport_ops = {
+	.type		= OVS_VPORT_TYPE_STT,
+	.create		= stt_tnl_create,
+	.destroy	= stt_tnl_destroy,
+	.get_name	= stt_tnl_get_name,
+	.get_options	= stt_tnl_get_options,
+	.send		= stt_tnl_send,
+	.owner          = THIS_MODULE,
+	.get_egress_tun_info	= stt_get_egress_tun_info,
+};
+
+static int __init ovs_stt_tnl_init(void)
+{
+	return ovs_vport_ops_register(&ovs_stt_vport_ops);
+}
+
+static void __exit ovs_stt_tnl_exit(void)
+{
+	ovs_vport_ops_unregister(&ovs_stt_vport_ops);
+}
+
+module_init(ovs_stt_tnl_init);
+module_exit(ovs_stt_tnl_exit);
+
+MODULE_DESCRIPTION("OVS: STT swiching port");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("vport-type-6");
-- 
1.9.1

^ permalink raw reply related

* [PATCH net-next v2 2/6] net: Add STT tunneling protocol.
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar, Jesse Gross

This adds a device level support for Stateless TCP Tunnel (STT)
protocol encapsulation. NF-hook is used for receiving STT
packets from networking stack.
Open vSwitch can be used for configuring, set up and tear down
STT tunnels.

The protocol is documented at
http://www.ietf.org/archive/id/draft-davie-stt-06.txt

Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: Jesse Gross <jesse@nicira.com>
---
 include/net/stt.h |   59 +++
 net/ipv4/Kconfig  |   11 +
 net/ipv4/Makefile |    1 +
 net/ipv4/stt.c    | 1398 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1469 insertions(+)
 create mode 100644 include/net/stt.h
 create mode 100644 net/ipv4/stt.c

diff --git a/include/net/stt.h b/include/net/stt.h
new file mode 100644
index 0000000..30db4d2
--- /dev/null
+++ b/include/net/stt.h
@@ -0,0 +1,59 @@
+#ifndef __NET_STT_H
+#define __NET_STT_H  1
+
+#include <net/ip_tunnels.h>
+
+struct stthdr {
+	__u8		version;
+	__u8		flags;
+	__u8		l4_offset;
+	__u8		reserved;
+	__be16		mss;
+	__be16		vlan_tci;
+	__be64		key;
+};
+
+/* Padding after the end of the tunnel headers to provide alignment
+ * for inner packet IP header after 14 byte Ethernet header.
+ */
+#define STT_ETH_PAD 2
+
+#define STT_BASE_HLEN   (sizeof(struct stthdr) + STT_ETH_PAD)
+#define STT_HEADER_LEN	(sizeof(struct tcphdr) + STT_BASE_HLEN)
+
+static inline struct stthdr *stt_hdr(const struct sk_buff *skb)
+{
+	return (struct stthdr *)(skb_transport_header(skb) +
+				 sizeof(struct tcphdr));
+}
+
+struct stt_sock;
+typedef void (stt_rcv_t)(struct stt_sock *stt_sock, struct sk_buff *skb);
+
+/* @list: Per-net list of STT ports.
+ * @rcv: The callback is called on STT packet recv, STT reassembly can generate
+ * multiple packets, in this case first packet has tunnel outer header, rest
+ * of the packets are inner packet segments with no stt header.
+ * @rcv_data: user data.
+ * @sock: Fake TCP socket for the STT port.
+ */
+struct stt_sock {
+	struct list_head	list;
+	stt_rcv_t		*rcv;
+	void			*rcv_data;
+	struct socket		*sock;
+	struct rcu_head		rcu;
+	int			refcnt;
+};
+
+struct stt_sock *stt_sock_add(struct net *net, __be16 port,
+			      stt_rcv_t *rcv, void *data);
+
+void stt_sock_release(struct net *net, __be16 port);
+
+int stt_xmit_skb(struct sk_buff *skb, struct rtable *rt,
+		 __be32 src, __be32 dst, __u8 tos,
+		 __u8 ttl, __be16 df, __be16 src_port, __be16 dst_port,
+		 __be64 tun_id);
+
+#endif /*ifdef__NET_STT_H */
diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig
index bd29016..3ab00be 100644
--- a/net/ipv4/Kconfig
+++ b/net/ipv4/Kconfig
@@ -344,6 +344,17 @@ config GENEVE
 
 	  To compile this driver as a module, choose M here: the module
 
+config STT
+	tristate "STT Encapsulation"
+	depends on INET
+	depends on NETFILTER
+	---help---
+	This allows one to create STT virtual interfaces that provide
+	Layer 2 Networks over Layer 3 Networks. The STT protocol
+	described in the draft:
+	http://www.ietf.org/archive/id/draft-davie-stt-06.txt
+
+	  To compile this driver as a module, choose M here: the module
 
 config INET_AH
 	tristate "IP: AH transformation"
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 518c04e..d504fde 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -57,6 +57,7 @@ obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o
 obj-$(CONFIG_MEMCG_KMEM) += tcp_memcontrol.o
 obj-$(CONFIG_NETLABEL) += cipso_ipv4.o
 obj-$(CONFIG_GENEVE) += geneve.o
+obj-$(CONFIG_STT) += stt.o
 
 obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \
 		      xfrm4_output.o xfrm4_protocol.o
diff --git a/net/ipv4/stt.c b/net/ipv4/stt.c
new file mode 100644
index 0000000..e3fde1f
--- /dev/null
+++ b/net/ipv4/stt.c
@@ -0,0 +1,1398 @@
+/*
+ * Stateless TCP Tunnel (STT) vport.
+ *
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/unaligned.h>
+
+#include <linux/delay.h>
+#include <linux/flex_array.h>
+#include <linux/if.h>
+#include <linux/if_vlan.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/jhash.h>
+#include <linux/list.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/percpu.h>
+#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <linux/workqueue.h>
+
+#include <net/icmp.h>
+#include <net/inet_ecn.h>
+#include <net/ip.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+#include <net/sock.h>
+#include <net/stt.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+#define STT_VER 0
+
+#define STT_CSUM_VERIFIED	BIT(0)
+#define STT_CSUM_PARTIAL	BIT(1)
+#define STT_PROTO_IPV4		BIT(2)
+#define STT_PROTO_TCP		BIT(3)
+#define STT_PROTO_TYPES		(STT_PROTO_IPV4 | STT_PROTO_TCP)
+
+/* The length and offset of a fragment are encoded in the sequence number.
+ * STT_SEQ_LEN_SHIFT is the left shift needed to store the length.
+ * STT_SEQ_OFFSET_MASK is the mask to extract the offset.
+ */
+#define STT_SEQ_LEN_SHIFT 16
+#define STT_SEQ_OFFSET_MASK (BIT(STT_SEQ_LEN_SHIFT) - 1)
+
+/* The maximum amount of memory used to store packets waiting to be reassembled
+ * on a given CPU.  Once this threshold is exceeded we will begin freeing the
+ * least recently used fragments.
+ */
+#define REASM_HI_THRESH (4 * 1024 * 1024)
+/* The target for the high memory evictor.  Once we have exceeded
+ * REASM_HI_THRESH, we will continue freeing fragments until we hit
+ * this limit.
+ */
+#define REASM_LO_THRESH (3 * 1024 * 1024)
+/* The length of time a given packet has to be reassembled from the time the
+ * first fragment arrives.  Once this limit is exceeded it becomes available
+ * for cleaning.
+ */
+#define FRAG_EXP_TIME (30 * HZ)
+/* Number of hash entries.  Each entry has only a single slot to hold a packet
+ * so if there are collisions, we will drop packets.  This is allocated
+ * per-cpu and each entry consists of struct pkt_frag.
+ */
+#define FRAG_HASH_SHIFT		8
+#define FRAG_HASH_ENTRIES	BIT(FRAG_HASH_SHIFT)
+#define FRAG_HASH_SEGS		((sizeof(u32) * 8) / FRAG_HASH_SHIFT)
+
+#define CLEAN_PERCPU_INTERVAL (30 * HZ)
+
+struct pkt_key {
+	__be32 saddr;
+	__be32 daddr;
+	__be32 pkt_seq;
+	u32 mark;
+};
+
+struct pkt_frag {
+	struct sk_buff *skbs;
+	unsigned long timestamp;
+	struct list_head lru_node;
+	struct pkt_key key;
+};
+
+struct stt_percpu {
+	struct flex_array *frag_hash;
+	struct list_head frag_lru;
+	unsigned int frag_mem_used;
+
+	/* Protect frags table. */
+	spinlock_t lock;
+};
+
+struct first_frag {
+	struct sk_buff *last_skb;
+	unsigned int mem_used;
+	u16 tot_len;
+	u16 rcvd_len;
+	bool ecn_ce;
+};
+
+struct frag_skb_cb {
+	u16 offset;
+
+	/* Only valid for the first skb in the chain. */
+	struct first_frag first;
+};
+
+#define FRAG_CB(skb) ((struct frag_skb_cb *)(skb)->cb)
+
+static struct stt_percpu __percpu *stt_percpu_data __read_mostly;
+static u32 frag_hash_seed __read_mostly;
+
+/* Protects sock-hash and refcounts. */
+static DEFINE_MUTEX(stt_mutex);
+
+/* per-network namespace private data for this module */
+struct stt_net {
+	struct list_head sock_list;
+};
+
+static int stt_net_id;
+static int n_tunnels;
+static DEFINE_PER_CPU(u32, pkt_seq_counter);
+
+static void clean_percpu(struct work_struct *work);
+static DECLARE_DELAYED_WORK(clean_percpu_wq, clean_percpu);
+
+static struct stt_sock *stt_find_sock(struct net *net, __be16 port)
+{
+	struct stt_net *sn = net_generic(net, stt_net_id);
+	struct stt_sock *stt_sock;
+
+	list_for_each_entry_rcu(stt_sock, &sn->sock_list, list) {
+		if (inet_sk(stt_sock->sock->sk)->inet_sport == port)
+			return stt_sock;
+	}
+	return NULL;
+}
+
+static __be32 ack_seq(void)
+{
+#if NR_CPUS <= 65536
+	u32 pkt_seq, ack;
+
+	pkt_seq = this_cpu_read(pkt_seq_counter);
+	ack = pkt_seq << ilog2(NR_CPUS) | smp_processor_id();
+	this_cpu_inc(pkt_seq_counter);
+
+	return (__force __be32)ack;
+#else
+#error "Support for greater than 64k CPUs not implemented"
+#endif
+}
+
+static int clear_gso(struct sk_buff *skb)
+{
+	struct skb_shared_info *shinfo = skb_shinfo(skb);
+	int err;
+
+	if (shinfo->gso_type == 0 && shinfo->gso_size == 0 &&
+	    shinfo->gso_segs == 0)
+		return 0;
+
+	err = skb_unclone(skb, GFP_ATOMIC);
+	if (unlikely(err))
+		return err;
+
+	shinfo = skb_shinfo(skb);
+	shinfo->gso_type = 0;
+	shinfo->gso_size = 0;
+	shinfo->gso_segs = 0;
+	return 0;
+}
+
+static struct sk_buff *stt_build_header(struct sk_buff *skb, __be64 tun_id,
+					__be16 s_port, __be16 d_port,
+					__be32 saddr, __be32 dst,
+					__be16 h_proto, u8 nw_proto,
+					int dst_mtu)
+{
+	int data_len = skb->len + sizeof(struct stthdr) + STT_ETH_PAD;
+	unsigned short encap_mss;
+	struct tcphdr *tcph;
+	struct stthdr *stth;
+
+	skb_push(skb, STT_HEADER_LEN);
+	skb_reset_transport_header(skb);
+	tcph = tcp_hdr(skb);
+	memset(tcph, 0, STT_HEADER_LEN);
+	stth = stt_hdr(skb);
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		stth->flags |= STT_CSUM_PARTIAL;
+
+		stth->l4_offset = skb->csum_start -
+					(skb_headroom(skb) +
+					skb_transport_offset(skb) +
+					STT_HEADER_LEN);
+
+		if (h_proto == htons(ETH_P_IP))
+			stth->flags |= STT_PROTO_IPV4;
+
+		if (nw_proto == IPPROTO_TCP)
+			stth->flags |= STT_PROTO_TCP;
+
+		stth->mss = htons(skb_shinfo(skb)->gso_size);
+	} else if (skb->ip_summed == CHECKSUM_UNNECESSARY) {
+		stth->flags |= STT_CSUM_VERIFIED;
+	}
+
+	stth->vlan_tci = htons(skb->vlan_tci);
+	skb->vlan_tci = 0;
+	put_unaligned(tun_id, &stth->key);
+
+	tcph->source	= s_port;
+	tcph->dest	= d_port;
+	tcph->doff	= sizeof(struct tcphdr) / 4;
+	tcph->ack	= 1;
+	tcph->psh	= 1;
+	tcph->window	= htons(USHRT_MAX);
+	tcph->seq	= htonl(data_len << STT_SEQ_LEN_SHIFT);
+	tcph->ack_seq	= ack_seq();
+	tcph->check	= ~tcp_v4_check(skb->len - skb_transport_offset(skb),
+					saddr, dst, 0);
+
+	skb->csum_start = skb_transport_header(skb) - skb->head;
+	skb->csum_offset = offsetof(struct tcphdr, check);
+	skb->ip_summed = CHECKSUM_PARTIAL;
+
+	encap_mss = dst_mtu - sizeof(struct iphdr) - sizeof(struct tcphdr);
+	if (data_len > encap_mss) {
+		/* It's pretty rare to hit this case, so just fall back to
+		 * linearizing for now.
+		 */
+		if (skb_shinfo(skb)->frag_list &&
+		    unlikely(__skb_linearize(skb)))
+			goto error;
+
+		if (unlikely(skb_unclone(skb, GFP_ATOMIC)))
+			goto error;
+
+		skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
+		skb_shinfo(skb)->gso_size = encap_mss;
+		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(data_len, encap_mss);
+	} else {
+		if (unlikely(clear_gso(skb)))
+			goto error;
+	}
+
+	return skb;
+error:
+	kfree_skb(skb);
+	return NULL;
+}
+
+static bool stt_can_offload(struct sk_buff *skb, __be16 h_proto, u8 nw_proto)
+{
+	if (skb_is_gso(skb) && skb->ip_summed != CHECKSUM_PARTIAL) {
+		int csum_offset;
+		int len;
+		__sum16 *csum;
+
+		if (nw_proto == IPPROTO_TCP)
+			csum_offset = offsetof(struct tcphdr, check);
+		else if (nw_proto == IPPROTO_UDP)
+			csum_offset = offsetof(struct udphdr, check);
+		else
+			return false;
+
+		len = skb->len - skb_transport_offset(skb);
+		csum = (__sum16 *)(skb_transport_header(skb) + csum_offset);
+
+		if (h_proto == htons(ETH_P_IP)) {
+			struct iphdr *iph = ip_hdr(skb);
+			*csum = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
+						   len, nw_proto, 0);
+		} else if (h_proto == htons(ETH_P_IPV6)) {
+			struct ipv6hdr *ip6h = ipv6_hdr(skb);
+			*csum = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
+						 len, nw_proto, 0);
+		} else {
+			return false;
+		}
+		skb->csum_start = skb_transport_header(skb) - skb->head;
+		skb->csum_offset = csum_offset;
+		skb->ip_summed = CHECKSUM_PARTIAL;
+	}
+
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		/* Assume receiver can only offload TCP/UDP over IPv4/6,
+		 * and require 802.1Q VLANs to be accelerated.
+		 */
+		if (h_proto != htons(ETH_P_IP) &&
+		    h_proto != htons(ETH_P_IPV6))
+			return false;
+		if (nw_proto != IPPROTO_TCP && nw_proto != IPPROTO_UDP)
+			return false;
+
+		/* L4 offset must fit in a 1-byte field. */
+		if (skb->csum_start - skb_headroom(skb) > 255)
+			return false;
+
+		if (skb_shinfo(skb)->gso_type & SKB_GSO_TCP_ECN)
+			return false;
+	}
+	/* Total size of encapsulated packet must fit in 16 bits. */
+	if (skb->len + STT_HEADER_LEN + sizeof(struct iphdr) > 65535)
+		return false;
+
+	return true;
+}
+
+static bool need_linearize(const struct sk_buff *skb)
+{
+	struct skb_shared_info *shinfo = skb_shinfo(skb);
+	int i;
+
+	if (unlikely(shinfo->frag_list))
+		return true;
+
+	/* Generally speaking we should linearize if there are paged frags.
+	 * However, if all of the refcounts are 1 we know nobody else can
+	 * change them from underneath us and we can skip the linearization.
+	 */
+	for (i = 0; i < shinfo->nr_frags; i++)
+		if (unlikely(page_count(skb_frag_page(&shinfo->frags[i])) > 1))
+			return true;
+
+	return false;
+}
+
+static struct sk_buff *handle_offloads(struct sk_buff *skb)
+{
+	int err;
+
+	if (skb_is_gso(skb)) {
+		struct sk_buff *nskb;
+		char cb[sizeof(skb->cb)];
+
+		memcpy(cb, skb->cb, sizeof(cb));
+
+		nskb = __skb_gso_segment(skb, 0, false);
+		if (IS_ERR(nskb)) {
+			err = PTR_ERR(nskb);
+			goto error;
+		}
+
+		consume_skb(skb);
+		skb = nskb;
+		while (nskb) {
+			memcpy(nskb->cb, cb, sizeof(cb));
+			nskb = nskb->next;
+		}
+	} else if (skb->ip_summed == CHECKSUM_PARTIAL) {
+		/* Pages aren't locked and could change at any time.
+		 * If this happens after we compute the checksum, the
+		 * checksum will be wrong.  We linearize now to avoid
+		 * this problem.
+		 */
+		if (unlikely(need_linearize(skb))) {
+			err = __skb_linearize(skb);
+			if (unlikely(err))
+				goto error;
+		}
+
+		err = skb_checksum_help(skb);
+		if (unlikely(err))
+			goto error;
+	}
+
+	skb->ip_summed = CHECKSUM_NONE;
+
+	return skb;
+error:
+	return ERR_PTR(err);
+}
+
+int stt_xmit_skb(struct sk_buff *skb, struct rtable *rt,
+		 __be32 src, __be32 dst, __u8 tos,
+		 __u8 ttl, __be16 df, __be16 src_port, __be16 dst_port,
+		 __be64 tun_id)
+{
+	struct ethhdr *eh = eth_hdr(skb);
+	struct iphdr *iph = ip_hdr(skb);
+	__be16 inner_h_proto;
+	 u8 inner_nw_proto;
+	int ret = 0, min_headroom;
+
+	inner_h_proto = eh->h_proto;
+	inner_nw_proto = iph->protocol;
+
+	min_headroom = LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len
+			+ STT_HEADER_LEN + sizeof(struct iphdr);
+
+	if (skb_headroom(skb) < min_headroom || skb_header_cloned(skb)) {
+		int head_delta = SKB_DATA_ALIGN(min_headroom -
+						skb_headroom(skb) +
+						16);
+
+		ret = pskb_expand_head(skb, max_t(int, head_delta, 0),
+				       0, GFP_ATOMIC);
+		if (unlikely(ret))
+			goto err_free_rt;
+	}
+
+	if (!stt_can_offload(skb, inner_h_proto, inner_nw_proto)) {
+		struct sk_buff *nskb;
+
+		nskb = handle_offloads(skb);
+		if (IS_ERR(nskb)) {
+			ret = PTR_ERR(nskb);
+			goto err_free_rt;
+		}
+		skb = nskb;
+	}
+
+	while (skb) {
+		struct sk_buff *next_skb = skb->next;
+
+		skb->next = NULL;
+
+		if (next_skb)
+			dst_clone(&rt->dst);
+
+		/* Push STT and TCP header. */
+		skb = stt_build_header(skb, tun_id, src_port, dst_port, src,
+				       dst, inner_h_proto, inner_nw_proto,
+				       dst_mtu(&rt->dst));
+		if (unlikely(!skb))
+			goto next;
+		/* Push IP header. */
+		ret += iptunnel_xmit(NULL, rt, skb, src, dst, IPPROTO_TCP,
+				     tos, ttl, df, false);
+
+next:
+		skb = next_skb;
+	}
+
+	return ret;
+
+err_free_rt:
+	ip_rt_put(rt);
+	kfree_skb(skb);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(stt_xmit_skb);
+
+static struct sk_buff *normalize_frag_list(struct sk_buff *head,
+					   struct sk_buff **skbp)
+{
+	struct sk_buff *skb = *skbp;
+	struct sk_buff *last;
+
+	do {
+		struct sk_buff *frags;
+
+		if (skb_shared(skb)) {
+			struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);
+
+			if (unlikely(!nskb))
+				return ERR_PTR(-ENOMEM);
+
+			nskb->next = skb->next;
+			consume_skb(skb);
+			skb = nskb;
+			*skbp = skb;
+		}
+
+		if (head) {
+			head->len -= skb->len;
+			head->data_len -= skb->len;
+			head->truesize -= skb->truesize;
+		}
+
+		frags = skb_shinfo(skb)->frag_list;
+		if (frags) {
+			int err;
+
+			err = skb_unclone(skb, GFP_ATOMIC);
+			if (unlikely(err))
+				return ERR_PTR(err);
+
+			last = normalize_frag_list(skb, &frags);
+			if (IS_ERR(last))
+				return last;
+
+			skb_shinfo(skb)->frag_list = NULL;
+			last->next = skb->next;
+			skb->next = frags;
+		} else {
+			last = skb;
+		}
+
+		skbp = &skb->next;
+	} while ((skb = skb->next));
+
+	return last;
+}
+
+/* Takes a linked list of skbs, which potentially contain frag_list
+ * (whose members in turn potentially contain frag_lists, etc.) and
+ * converts them into a single linear linked list.
+ */
+static int straighten_frag_list(struct sk_buff **skbp)
+{
+	struct sk_buff *err_skb;
+
+	err_skb = normalize_frag_list(NULL, skbp);
+	if (IS_ERR(err_skb))
+		return PTR_ERR(err_skb);
+
+	return 0;
+}
+
+static void copy_skb_metadata(struct sk_buff *to, struct sk_buff *from)
+{
+	to->tstamp = from->tstamp;
+	to->priority = from->priority;
+	to->mark = from->mark;
+	to->vlan_tci = from->vlan_tci;
+	skb_copy_secmark(to, from);
+}
+
+static void update_seg_headers(struct sk_buff *skb, bool head,
+			       unsigned int l4_offset, unsigned int hdr_len,
+			       bool ipv4, u32 tcp_seq)
+{
+	u16 old_len, new_len;
+	__be32 delta;
+	struct tcphdr *tcph;
+	int gso_size;
+
+	if (ipv4) {
+		struct iphdr *iph = (struct iphdr *)(skb->data + ETH_HLEN);
+
+		old_len = ntohs(iph->tot_len);
+		new_len = skb->len - ETH_HLEN;
+		iph->tot_len = htons(new_len);
+
+		ip_send_check(iph);
+	} else {
+		struct ipv6hdr *ip6h = (struct ipv6hdr *)(skb->data + ETH_HLEN);
+
+		old_len = ntohs(ip6h->payload_len);
+		new_len = skb->len - ETH_HLEN - sizeof(struct ipv6hdr);
+		ip6h->payload_len = htons(new_len);
+	}
+
+	tcph = (struct tcphdr *)(skb->data + l4_offset);
+	if (!head) {
+		tcph->seq = htonl(tcp_seq);
+		tcph->cwr = 0;
+	}
+
+	if (skb->next) {
+		tcph->fin = 0;
+		tcph->psh = 0;
+	}
+
+	delta = htonl(~old_len + new_len);
+	tcph->check = ~csum_fold((__force __wsum)((__force u32)tcph->check +
+				 (__force u32)delta));
+
+	gso_size = skb_shinfo(skb)->gso_size;
+	if (gso_size && skb->len - hdr_len <= gso_size)
+		BUG_ON(clear_gso(skb));
+}
+
+static int __linearize(struct sk_buff *head, bool *ipv4)
+{
+	struct stthdr *stth = stt_hdr(head);
+
+	/* If no offloading is in use then we don't have enough information
+	 * to process the headers.
+	 */
+	if (!(stth->flags & STT_CSUM_PARTIAL))
+		goto linearize;
+
+	/* Handling UDP packets requires IP fragmentation, which means that
+	 * the L4 checksum can no longer be calculated by hardware (since the
+	 * fragments are in different packets.  If we have to compute the
+	 * checksum it's faster just to linearize and large UDP packets are
+	 * pretty uncommon anyways, so it's not worth dealing with for now.
+	 */
+	if (!(stth->flags & STT_PROTO_TCP))
+		goto linearize;
+
+	if ((stth->flags & STT_PROTO_IPV4)) {
+		struct iphdr *iph = (struct iphdr *)(head->data + ETH_HLEN);
+
+		/* It's difficult to get the IP IDs exactly right here due to
+		 * varying segment sizes and potentially multiple layers of
+		 * segmentation.  IP ID isn't important when DF is set and DF
+		 * is generally set for TCP packets, so just linearize if it's
+		 * not.
+		 */
+		if (!(iph->frag_off & htons(IP_DF)))
+			goto linearize;
+
+		*ipv4 = true;
+	} else {
+		struct ipv6hdr *ip6h = (struct ipv6hdr *)(head->data + ETH_HLEN);
+
+		/* Jumbograms require more processing to update and we'll
+		 * probably never see them, so just linearize.
+		 */
+		if (ip6h->payload_len == 0)
+			goto linearize;
+
+		*ipv4 = false;
+	}
+	return false;
+
+linearize:
+	return true;
+}
+
+static int update_seg(struct sk_buff *head, struct sk_buff *frag,
+		      bool ipv4, int l4_offset, int hdr_len, u32 seq)
+{
+	u16 csum_start = head->csum_start - skb_headroom(head);
+
+	if (skb_cloned(frag) || skb_headroom(frag) < hdr_len) {
+		int extra_head = hdr_len - skb_headroom(frag);
+
+		extra_head = extra_head > 0 ? extra_head : 0;
+
+		if (unlikely(pskb_expand_head(frag, extra_head, 0,
+					      GFP_ATOMIC)))
+			return -ENOMEM;
+	}
+
+	memcpy(__skb_push(frag, hdr_len), head->data, hdr_len);
+
+	frag->csum_start = skb_headroom(frag) + csum_start;
+	frag->csum_offset = head->csum_offset;
+	frag->ip_summed = head->ip_summed;
+
+	skb_shinfo(frag)->gso_size = skb_shinfo(head)->gso_size;
+	skb_shinfo(frag)->gso_type = skb_shinfo(head)->gso_type;
+	skb_shinfo(frag)->gso_segs = 0;
+
+	copy_skb_metadata(frag, head);
+
+	update_seg_headers(frag, false, l4_offset, hdr_len, ipv4, seq);
+	return 0;
+}
+
+static int __build_segments(struct sk_buff **headp)
+{
+	struct sk_buff *head = *headp;
+	struct sk_buff *nskb = NULL;
+	struct sk_buff *rskb, *skb;
+	struct tcphdr *tcph;
+	int seg_len = 0;
+	int l4_offset;
+	int hdr_len;
+	int tcp_len;
+	bool ipv4;
+	u32 seq;
+
+	/* GRO can produce skbs with only the headers, which we've
+	 * already pulled off.  We can just dump them.
+	 */
+	while (head->len == 0) {
+		nskb = head->next;
+		copy_skb_metadata(nskb, head);
+		consume_skb(head);
+		head = nskb;
+	}
+	*headp = head;
+
+	if (__linearize(head, &ipv4))
+		return skb_list_linearize(head, GFP_ATOMIC);
+
+	l4_offset = stt_hdr(head)->l4_offset;
+	tcph = (struct tcphdr *)(head->data + l4_offset);
+	tcp_len = tcph->doff * 4;
+	hdr_len = l4_offset + tcp_len;
+
+	if (unlikely((tcp_len < sizeof(struct tcphdr)) ||
+		     (head->len < hdr_len)))
+		return -EINVAL;
+
+	if (unlikely(!pskb_may_pull(head, hdr_len)))
+		return -ENOMEM;
+
+	seq = ntohl(tcph->seq);
+	rskb = head;
+	for (skb = head->next; ; skb = nskb) {
+		bool headstolen;
+		int delta;
+
+		if (!skb)
+			goto update_seg; /* Update current segment. */
+
+		if (unlikely(skb_unclone(rskb, GFP_ATOMIC)))
+			return -ENOMEM;
+
+		nskb = skb->next;
+		if (!skb_try_coalesce(rskb, skb, &headstolen, &delta))
+			goto update_seg;
+
+		rskb->next = skb->next;
+		kfree_skb_partial(skb, headstolen);
+		continue;
+update_seg:
+		if (rskb == head) {
+			/* update head segment at the end. */
+			seg_len = head->len - hdr_len;
+		} else {
+			int err;
+
+			seq += seg_len;
+			seg_len = rskb->len;
+
+			err = update_seg(head, rskb, ipv4, l4_offset,
+					 hdr_len, seq);
+			if (err)
+				return err;
+		}
+		if (!skb)
+			break;
+		rskb->truesize = SKB_TRUESIZE(skb_end_offset(rskb)) +
+				 rskb->data_len;
+		rskb = skb;
+	}
+	update_seg_headers(head, true, l4_offset, hdr_len, ipv4, 0);
+	return 0;
+}
+
+static int build_segments(struct sk_buff **headp)
+{
+	int err;
+
+	err = straighten_frag_list(headp);
+	if (unlikely(err))
+		return err;
+
+	if ((*headp)->next) {
+		err = __build_segments(headp);
+		if (unlikely(err))
+			return err;
+	}
+	return 0;
+}
+
+static void free_frag(struct stt_percpu *stt_percpu,
+		      struct pkt_frag *frag)
+{
+	stt_percpu->frag_mem_used -= FRAG_CB(frag->skbs)->first.mem_used;
+	kfree_skb_list(frag->skbs);
+	list_del(&frag->lru_node);
+	frag->skbs = NULL;
+}
+
+static void evict_frags(struct stt_percpu *stt_percpu)
+{
+	while (!list_empty(&stt_percpu->frag_lru) &&
+	       stt_percpu->frag_mem_used > REASM_LO_THRESH) {
+		struct pkt_frag *frag = list_first_entry(&stt_percpu->frag_lru,
+							 struct pkt_frag,
+							 lru_node);
+		free_frag(stt_percpu, frag);
+	}
+}
+
+static bool pkt_key_match(struct net *net,
+			  const struct pkt_frag *a, const struct pkt_key *b)
+{
+	return a->key.saddr == b->saddr && a->key.daddr == b->daddr &&
+	       a->key.pkt_seq == b->pkt_seq && a->key.mark == b->mark &&
+	       net_eq(dev_net(a->skbs->dev), net);
+}
+
+static u32 pkt_key_hash(const struct net *net, const struct pkt_key *key)
+{
+	u32 initval = frag_hash_seed ^ (u32)(unsigned long)net ^ key->mark;
+
+	return jhash_3words((__force u32)key->saddr, (__force u32)key->daddr,
+			    (__force u32)key->pkt_seq, initval);
+}
+
+static struct pkt_frag *lookup_frag(struct net *net,
+				    struct stt_percpu *stt_percpu,
+				    const struct pkt_key *key, u32 hash)
+{
+	struct pkt_frag *frag, *victim_frag = NULL;
+	int i;
+
+	for (i = 0; i < FRAG_HASH_SEGS; i++) {
+		frag = flex_array_get(stt_percpu->frag_hash,
+				      hash & (FRAG_HASH_ENTRIES - 1));
+
+		if (frag->skbs &&
+		    time_before(jiffies, frag->timestamp + FRAG_EXP_TIME) &&
+		    pkt_key_match(net, frag, key))
+			return frag;
+
+		if (!victim_frag ||
+		    (victim_frag->skbs &&
+		     (!frag->skbs ||
+		      time_before(frag->timestamp, victim_frag->timestamp))))
+			victim_frag = frag;
+
+		hash >>= FRAG_HASH_SHIFT;
+	}
+
+	if (victim_frag->skbs)
+		free_frag(stt_percpu, victim_frag);
+
+	return victim_frag;
+}
+
+static struct sk_buff *reassemble(struct sk_buff *skb)
+{
+	struct iphdr *iph = ip_hdr(skb);
+	struct tcphdr *tcph = tcp_hdr(skb);
+	u32 seq = ntohl(tcph->seq);
+	int tot_len;
+	struct pkt_key key;
+	struct stt_percpu *stt_percpu;
+	u32 hash;
+	struct pkt_frag *frag;
+	struct sk_buff *last_skb;
+
+	tot_len = seq >> STT_SEQ_LEN_SHIFT;
+	FRAG_CB(skb)->offset = seq & STT_SEQ_OFFSET_MASK;
+
+	if (unlikely(skb->len == 0))
+		goto out_free;
+
+	if (unlikely(FRAG_CB(skb)->offset + skb->len > tot_len))
+		goto out_free;
+
+	if (tot_len == skb->len)
+		goto out;
+
+	key.saddr = iph->saddr;
+	key.daddr = iph->daddr;
+	key.pkt_seq = tcph->ack_seq;
+	key.mark = skb->mark;
+	hash = pkt_key_hash(dev_net(skb->dev), &key);
+
+	stt_percpu = per_cpu_ptr(stt_percpu_data, smp_processor_id());
+
+	spin_lock(&stt_percpu->lock);
+
+	if (unlikely(stt_percpu->frag_mem_used + skb->truesize > REASM_HI_THRESH))
+		evict_frags(stt_percpu);
+
+	frag = lookup_frag(dev_net(skb->dev), stt_percpu, &key, hash);
+	if (!frag->skbs) {
+		frag->skbs = skb;
+		frag->key = key;
+		frag->timestamp = jiffies;
+		FRAG_CB(skb)->first.last_skb = skb;
+		FRAG_CB(skb)->first.mem_used = skb->truesize;
+		FRAG_CB(skb)->first.tot_len = tot_len;
+		FRAG_CB(skb)->first.rcvd_len = skb->len;
+		FRAG_CB(skb)->first.ecn_ce = INET_ECN_is_ce(iph->tos);
+		list_add_tail(&frag->lru_node, &stt_percpu->frag_lru);
+		stt_percpu->frag_mem_used += skb->truesize;
+
+		skb = NULL;
+		goto unlock;
+	}
+
+	/* Optimize for the common case where fragments are received in-order
+	 * and not overlapping.
+	 */
+	last_skb = FRAG_CB(frag->skbs)->first.last_skb;
+	if (likely(FRAG_CB(last_skb)->offset + last_skb->len ==
+		   FRAG_CB(skb)->offset)) {
+		last_skb->next = skb;
+		FRAG_CB(frag->skbs)->first.last_skb = skb;
+	} else {
+		struct sk_buff *prev = NULL, *next;
+
+		for (next = frag->skbs; next; next = next->next) {
+			if (FRAG_CB(next)->offset >= FRAG_CB(skb)->offset)
+				break;
+			prev = next;
+		}
+
+		/* Overlapping fragments aren't allowed.  We shouldn't start
+		 * before the end of the previous fragment.
+		 */
+		if (prev &&
+		    FRAG_CB(prev)->offset + prev->len > FRAG_CB(skb)->offset)
+			goto unlock_free;
+
+		/* We also shouldn't end after the beginning of the next
+		 * fragment.
+		 */
+		if (next &&
+		    FRAG_CB(skb)->offset + skb->len > FRAG_CB(next)->offset)
+			goto unlock_free;
+
+		if (prev) {
+			prev->next = skb;
+		} else {
+			FRAG_CB(skb)->first = FRAG_CB(frag->skbs)->first;
+			frag->skbs = skb;
+		}
+
+		if (next)
+			skb->next = next;
+		else
+			FRAG_CB(frag->skbs)->first.last_skb = skb;
+	}
+
+	FRAG_CB(frag->skbs)->first.ecn_ce |= INET_ECN_is_ce(iph->tos);
+	FRAG_CB(frag->skbs)->first.rcvd_len += skb->len;
+	FRAG_CB(frag->skbs)->first.mem_used += skb->truesize;
+	stt_percpu->frag_mem_used += skb->truesize;
+
+	if (FRAG_CB(frag->skbs)->first.tot_len ==
+	    FRAG_CB(frag->skbs)->first.rcvd_len) {
+		struct sk_buff *frag_head = frag->skbs;
+
+		frag_head->tstamp = skb->tstamp;
+
+		list_del(&frag->lru_node);
+		stt_percpu->frag_mem_used -= FRAG_CB(frag_head)->first.mem_used;
+		frag->skbs = NULL;
+		skb = frag_head;
+	} else {
+		list_move_tail(&frag->lru_node, &stt_percpu->frag_lru);
+		skb = NULL;
+	}
+
+	goto unlock;
+
+unlock_free:
+	kfree_skb(skb);
+	skb = NULL;
+unlock:
+	spin_unlock(&stt_percpu->lock);
+	return skb;
+out_free:
+	kfree_skb(skb);
+	skb = NULL;
+out:
+	return skb;
+}
+
+static bool validate_checksum(struct sk_buff *skb)
+{
+	struct iphdr *iph = ip_hdr(skb);
+
+	if (skb_csum_unnecessary(skb))
+		return true;
+
+	if (skb->ip_summed == CHECKSUM_COMPLETE &&
+	    !tcp_v4_check(skb->len, iph->saddr, iph->daddr, skb->csum))
+		return true;
+
+	skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, skb->len,
+				       IPPROTO_TCP, 0);
+
+	return __tcp_checksum_complete(skb) == 0;
+}
+
+static bool set_offloads(struct sk_buff *skb)
+{
+	struct stthdr *stth = stt_hdr(skb);
+	u8 proto_type;
+	u16 csum_offset;
+	unsigned short gso_type;
+	int l3_header_size;
+	int l4_header_size;
+
+	skb->vlan_tci = ntohs(stth->vlan_tci);
+
+	if (!(stth->flags & STT_CSUM_PARTIAL)) {
+		if (stth->flags & STT_CSUM_VERIFIED)
+			skb->ip_summed = CHECKSUM_UNNECESSARY;
+		else
+			skb->ip_summed = CHECKSUM_NONE;
+
+		return clear_gso(skb) == 0;
+	}
+
+	proto_type = stth->flags & STT_PROTO_TYPES;
+
+	if (proto_type == (STT_PROTO_IPV4 | STT_PROTO_TCP)) {
+		/* TCP/IPv4 */
+		csum_offset = offsetof(struct tcphdr, check);
+		gso_type = SKB_GSO_TCPV4;
+		l3_header_size = sizeof(struct iphdr);
+		l4_header_size = sizeof(struct tcphdr);
+	} else if (proto_type == STT_PROTO_TCP) {
+		/* TCP/IPv6 */
+		csum_offset = offsetof(struct tcphdr, check);
+		gso_type = SKB_GSO_TCPV6;
+		l3_header_size = sizeof(struct ipv6hdr);
+		l4_header_size = sizeof(struct tcphdr);
+	} else if (proto_type == STT_PROTO_IPV4) {
+		/* UDP/IPv4 */
+		csum_offset = offsetof(struct udphdr, check);
+		gso_type = SKB_GSO_UDP;
+		l3_header_size = sizeof(struct iphdr);
+		l4_header_size = sizeof(struct udphdr);
+	} else {
+		/* UDP/IPv6 */
+		csum_offset = offsetof(struct udphdr, check);
+		gso_type = SKB_GSO_UDP;
+		l3_header_size = sizeof(struct ipv6hdr);
+		l4_header_size = sizeof(struct udphdr);
+	}
+
+	if (unlikely(stth->l4_offset < ETH_HLEN + l3_header_size))
+		return false;
+
+	if (unlikely(!pskb_may_pull(skb, stth->l4_offset + l4_header_size)))
+		return false;
+	stth = stt_hdr(skb);
+
+	skb->csum_start = skb_headroom(skb) + stth->l4_offset;
+	skb->csum_offset = csum_offset;
+	skb->ip_summed = CHECKSUM_PARTIAL;
+
+	if (stth->mss) {
+		if (unlikely(skb_unclone(skb, GFP_ATOMIC)))
+			return false;
+
+		skb_shinfo(skb)->gso_type = gso_type | SKB_GSO_DODGY;
+		skb_shinfo(skb)->gso_size = ntohs(stth->mss);
+		skb_shinfo(skb)->gso_segs = 0;
+	} else {
+		if (unlikely(clear_gso(skb)))
+			return false;
+	}
+
+	return true;
+}
+
+static void stt_rcv(struct stt_sock *stt_sock, struct sk_buff *skb)
+{
+	int err;
+
+	if (unlikely(!validate_checksum(skb)))
+		goto drop;
+
+	skb = reassemble(skb);
+	if (!skb)
+		return;
+
+	if (unlikely(stt_hdr(skb)->version != 0))
+		goto drop;
+
+	err = iptunnel_pull_header(skb,
+				   sizeof(struct stthdr) + STT_ETH_PAD,
+				   htons(ETH_P_TEB));
+	if (unlikely(err))
+		goto drop;
+
+	if (unlikely(!set_offloads(skb)))
+		goto drop;
+
+	if (unlikely(build_segments(&skb)))
+		goto drop;
+
+	stt_sock->rcv(stt_sock, skb);
+	return;
+drop:
+	/* Consume bad packet */
+	kfree_skb_list(skb);
+}
+
+static void tcp_sock_release(struct socket *sock)
+{
+	kernel_sock_shutdown(sock, SHUT_RDWR);
+	sk_release_kernel(sock->sk);
+}
+
+static int tcp_sock_create4(struct net *net, __be16 port,
+			    struct socket **sockp)
+{
+	int err;
+	struct socket *sock = NULL;
+	struct sockaddr_in tcp_addr;
+
+	err = sock_create_kern(AF_INET, SOCK_STREAM, 0, &sock);
+	if (err < 0)
+		goto error;
+
+	sk_change_net(sock->sk, net);
+
+	memset(&tcp_addr, 0, sizeof(tcp_addr));
+	tcp_addr.sin_family = AF_INET;
+	tcp_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+	tcp_addr.sin_port = port;
+	err = kernel_bind(sock, (struct sockaddr *)&tcp_addr,
+			  sizeof(tcp_addr));
+	if (err < 0)
+		goto error;
+
+	*sockp = sock;
+	return 0;
+
+error:
+	if (sock)
+		tcp_sock_release(sock);
+	*sockp = NULL;
+	return err;
+}
+
+static void schedule_clean_percpu(void)
+{
+	schedule_delayed_work(&clean_percpu_wq, CLEAN_PERCPU_INTERVAL);
+}
+
+static void clean_percpu(struct work_struct *work)
+{
+	int i;
+
+	for_each_possible_cpu(i) {
+		struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+		int j;
+
+		for (j = 0; j < FRAG_HASH_ENTRIES; j++) {
+			struct pkt_frag *frag;
+
+			frag = flex_array_get(stt_percpu->frag_hash, j);
+			if (!frag->skbs ||
+			    time_before(jiffies, frag->timestamp + FRAG_EXP_TIME))
+				continue;
+
+			spin_lock_bh(&stt_percpu->lock);
+
+			if (frag->skbs &&
+			    time_after(jiffies, frag->timestamp + FRAG_EXP_TIME))
+				free_frag(stt_percpu, frag);
+
+			spin_unlock_bh(&stt_percpu->lock);
+		}
+	}
+	schedule_clean_percpu();
+}
+
+static unsigned int nf_ip_hook(const struct nf_hook_ops *ops,
+			       struct sk_buff *skb,
+			       const struct net_device *in,
+			       const struct net_device *out,
+			       int (*okfn)(struct sk_buff *))
+{
+	struct stt_sock *stt_sock;
+	int ip_hdr_len;
+
+	if (ip_hdr(skb)->protocol != IPPROTO_TCP)
+		return NF_ACCEPT;
+
+	ip_hdr_len = ip_hdrlen(skb);
+	if (unlikely(!pskb_may_pull(skb, ip_hdr_len + sizeof(struct tcphdr))))
+		return NF_ACCEPT;
+
+	skb_set_transport_header(skb, ip_hdr_len);
+
+	stt_sock = stt_find_sock(dev_net(skb->dev), tcp_hdr(skb)->dest);
+	if (unlikely(!stt_sock))
+		return NF_ACCEPT;
+
+	if (unlikely(!pskb_pull(skb, ip_hdr_len + tcp_hdrlen(skb)))) {
+		kfree_skb(skb);
+		return NF_STOLEN;
+	}
+
+	stt_rcv(stt_sock, skb);
+	return NF_STOLEN;
+}
+
+static struct nf_hook_ops nf_hook_ops __read_mostly = {
+	.hook           = nf_ip_hook,
+	.owner          = THIS_MODULE,
+	.pf             = NFPROTO_IPV4,
+	.hooknum        = NF_INET_LOCAL_IN,
+	.priority       = INT_MAX,
+};
+
+static int stt_start(void)
+{
+	int err;
+	int i;
+
+	if (n_tunnels) {
+		n_tunnels++;
+		return 0;
+	}
+
+	get_random_bytes(&frag_hash_seed, sizeof(u32));
+
+	stt_percpu_data = alloc_percpu(struct stt_percpu);
+	if (!stt_percpu_data) {
+		err = -ENOMEM;
+		goto error;
+	}
+
+	for_each_possible_cpu(i) {
+		struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+		struct flex_array *frag_hash;
+
+		spin_lock_init(&stt_percpu->lock);
+		INIT_LIST_HEAD(&stt_percpu->frag_lru);
+		get_random_bytes(&per_cpu(pkt_seq_counter, i), sizeof(u32));
+
+		frag_hash = flex_array_alloc(sizeof(struct pkt_frag),
+					     FRAG_HASH_ENTRIES,
+					     GFP_KERNEL | __GFP_ZERO);
+		if (!frag_hash) {
+			err = -ENOMEM;
+			goto free_percpu;
+		}
+		stt_percpu->frag_hash = frag_hash;
+
+		err = flex_array_prealloc(stt_percpu->frag_hash, 0,
+					  FRAG_HASH_ENTRIES,
+					  GFP_KERNEL | __GFP_ZERO);
+		if (err)
+			goto free_percpu;
+	}
+	err = nf_register_hook(&nf_hook_ops);
+	if (err)
+		goto free_percpu;
+
+	schedule_clean_percpu();
+	n_tunnels++;
+	return 0;
+
+free_percpu:
+	for_each_possible_cpu(i) {
+		struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+
+		if (stt_percpu->frag_hash)
+			flex_array_free(stt_percpu->frag_hash);
+	}
+
+	free_percpu(stt_percpu_data);
+
+error:
+	return err;
+}
+
+static void stt_cleanup(void)
+{
+	int i;
+
+	n_tunnels--;
+	if (n_tunnels)
+		return;
+
+	cancel_delayed_work_sync(&clean_percpu_wq);
+	nf_unregister_hook(&nf_hook_ops);
+
+	for_each_possible_cpu(i) {
+		struct stt_percpu *stt_percpu = per_cpu_ptr(stt_percpu_data, i);
+		int j;
+
+		for (j = 0; j < FRAG_HASH_ENTRIES; j++) {
+			struct pkt_frag *frag;
+
+			frag = flex_array_get(stt_percpu->frag_hash, j);
+			kfree_skb_list(frag->skbs);
+		}
+
+		flex_array_free(stt_percpu->frag_hash);
+	}
+
+	free_percpu(stt_percpu_data);
+}
+
+static struct stt_sock *stt_socket_create(struct net *net, __be16 port,
+					  stt_rcv_t *rcv, void *data)
+{
+	struct stt_net *sn = net_generic(net, stt_net_id);
+	struct stt_sock *stt_sock;
+	struct socket *sock;
+	int err;
+
+	stt_sock = kzalloc(sizeof(*stt_sock), GFP_KERNEL);
+	if (!stt_sock)
+		return ERR_PTR(-ENOMEM);
+
+	err = tcp_sock_create4(net, port, &sock);
+	if (err) {
+		kfree(stt_sock);
+		return ERR_PTR(err);
+	}
+
+	stt_sock->sock = sock;
+	stt_sock->rcv = rcv;
+	stt_sock->rcv_data = data;
+	stt_sock->refcnt = 1;
+	list_add_rcu(&stt_sock->list, &sn->sock_list);
+
+	return stt_sock;
+}
+
+static void __stt_sock_release(struct stt_sock *stt_sock)
+{
+	list_del_rcu(&stt_sock->list);
+	tcp_sock_release(stt_sock->sock);
+	kfree_rcu(stt_sock, rcu);
+}
+
+struct stt_sock *stt_sock_add(struct net *net, __be16 port,
+			      stt_rcv_t *rcv, void *data)
+{
+	struct stt_sock *stt_sock;
+
+	mutex_lock(&stt_mutex);
+	rcu_read_lock();
+	stt_sock = stt_find_sock(net, port);
+	rcu_read_unlock();
+	if (stt_sock) {
+		if (stt_sock->rcv == rcv &&
+		    stt_sock->rcv_data == data)
+			stt_sock->refcnt++;
+		else
+			stt_sock = ERR_PTR(-EBUSY);
+	} else {
+		stt_sock = stt_socket_create(net, port, rcv, data);
+		if (!IS_ERR(stt_sock)) {
+			int err;
+
+			err = stt_start();
+			if (err) {
+				__stt_sock_release(stt_sock);
+				stt_sock = ERR_PTR(err);
+			}
+		}
+	}
+	mutex_unlock(&stt_mutex);
+
+	return stt_sock;
+}
+EXPORT_SYMBOL_GPL(stt_sock_add);
+
+void stt_sock_release(struct net *net, __be16 port)
+{
+	struct stt_sock *stt_sock;
+
+	mutex_lock(&stt_mutex);
+	rcu_read_lock();
+	stt_sock = stt_find_sock(net, port);
+	rcu_read_unlock();
+
+	if (stt_sock) {
+		stt_sock->refcnt--;
+		if (!stt_sock->refcnt) {
+			__stt_sock_release(stt_sock);
+			stt_cleanup();
+		}
+	}
+	mutex_unlock(&stt_mutex);
+}
+EXPORT_SYMBOL_GPL(stt_sock_release);
+
+static __net_init int stt_init_net(struct net *net)
+{
+	struct stt_net *sn = net_generic(net, stt_net_id);
+
+	INIT_LIST_HEAD(&sn->sock_list);
+	return 0;
+}
+
+static struct pernet_operations stt_net_ops = {
+	.init = stt_init_net,
+	.id   = &stt_net_id,
+	.size = sizeof(struct stt_net),
+};
+
+static int __init stt_init_module(void)
+{
+	return register_pernet_subsys(&stt_net_ops);
+}
+module_init(stt_init_module);
+
+static void __exit stt_cleanup_module(void)
+{
+	unregister_pernet_subsys(&stt_net_ops);
+}
+module_exit(stt_cleanup_module);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for STT encapsulated traffic");
+MODULE_ALIAS_RTNL_LINK("stt");
-- 
1.9.1

^ permalink raw reply related

* Re: [PATCH] ipv4: Don't increase PMTU with Datagram Too Big message.
From: David Miller @ 2015-01-29 23:29 UTC (permalink / raw)
  To: lw; +Cc: netdev
In-Reply-To: <1422518943-30637-1-git-send-email-lw@cn.fujitsu.com>

From: Li Wei <lw@cn.fujitsu.com>
Date: Thu, 29 Jan 2015 16:09:03 +0800

> RFC 1191 said, "a host MUST not increase its estimate of the Path
> MTU in response to the contents of a Datagram Too Big message."
> 
> Signed-off-by: Li Wei <lw@cn.fujitsu.com>

Applied, thank you.

^ permalink raw reply

* [PATCH net-next v2 1/6] skbuff: Add skb_list_linearize()
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar

similar to skb_linearize(), this API takes skb list as arg and
linearize it into one big skb. STT driver patch will use this.

Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
---
Fixed according to comments from Eric.
---
 include/linux/skbuff.h |  2 ++
 net/core/skbuff.c      | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 85ab7d7..c9194c1 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -2532,6 +2532,8 @@ static inline int skb_linearize(struct sk_buff *skb)
 	return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
 }
 
+int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask);
+
 /**
  * skb_has_shared_frag - can any frag be overwritten
  * @skb: buffer to test
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 56db472..d6358a7 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -2329,6 +2329,40 @@ void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
 }
 EXPORT_SYMBOL(skb_copy_and_csum_dev);
 
+int skb_list_linearize(struct sk_buff *head, gfp_t gfp_mask)
+{
+	struct sk_buff *skb;
+	int tlen = 0;
+	int err;
+
+	err = skb_linearize(head);
+	if (err)
+		return err;
+
+	skb = head->next;
+	while (skb) {
+		tlen += skb->len;
+		skb = skb->next;
+	}
+	err = pskb_expand_head(head, 0, tlen, gfp_mask);
+	if (err)
+		return err;
+
+	skb = head->next;
+	while (skb) {
+		err = skb_copy_bits(skb, 0, skb_tail_pointer(head), skb->len);
+		if (err)
+			return err;
+		head->tail += skb->len;
+		skb = skb->next;
+	}
+	kfree_skb_list(head->next);
+	head->next = NULL;
+	head->len += tlen;
+	return 0;
+}
+EXPORT_SYMBOL(skb_list_linearize);
+
 /**
  *	skb_dequeue - remove from the head of the queue
  *	@list: list to dequeue from
-- 
1.9.1

^ permalink raw reply related

* [PATCH net-next v2 0/6] net: Add STT support.
From: Pravin B Shelar @ 2015-01-29 23:29 UTC (permalink / raw)
  To: davem; +Cc: netdev, Pravin B Shelar

Following patch series adds support for Stateless Transport
Tunneling protocol.
STT uses TCP segmentation offload available in most of NIC. On
packet xmit STT driver appends STT header along with TCP header
to the packet. For GSO packet GSO parameters are set according
to tunnel configuration and packet is handed over to networking
stack. This allows use of segmentation offload available in NICs

The protocol is documented at
http://www.ietf.org/archive/id/draft-davie-stt-06.txt

I will send out OVS userspace patch on ovs-dev mailing list.

Following are test results. All tests are done on net-next with
STT and VXLAN kernel device without OVS.

Single Netperf session:
=======================
VXLAN:
    CPU utilization
     - Send local: 1.26
     - Recv remote: 8.62
    Throughput: 4.9 Gbit/sec
STT:
    CPU utilization
     - Send local: 1.01
     - Recv remote: 1.8
    Throughput: 9.45 Gbit/sec

Five Netperf sessions:
======================
VXLAN:
    CPU utilization
     - Send local: 9.7
     - Recv remote: 70 (varies from 60 to 80)
    Throughput: 9.05 Gbit/sec
STT:
    CPU utilization
     - Send local: 5.85
     - Recv remote: 14
    Throughput: 9.47 Gbit/sec

v1-v2:
- Added STT device.
- Fixed first patch according to comments from Eric.

Pravin B Shelar (6):
  skbuff: Add skb_list_linearize()
  net: Add STT tunneling protocol.
  openvswitch: Add support for STT tunneling.
  ip_tunnel: Introduce tunnel_info struct.
  ip_tunnel: Extend tunnel_info for STT.
  net: Add STT device.

 include/linux/skbuff.h           |    2 +
 include/net/ip_tunnels.h         |   63 +-
 include/net/stt.h                |   59 ++
 include/uapi/linux/if_tunnel.h   |   17 +
 include/uapi/linux/openvswitch.h |    1 +
 net/core/skbuff.c                |   34 +
 net/ipv4/Kconfig                 |   20 +
 net/ipv4/Makefile                |    2 +
 net/ipv4/gre_demux.c             |    4 +-
 net/ipv4/ip_gre.c                |  183 +++--
 net/ipv4/ip_stt.c                |  410 +++++++++++
 net/ipv4/ip_tunnel.c             |  312 +++++----
 net/ipv4/ip_vti.c                |   75 +-
 net/ipv4/ipip.c                  |   88 +--
 net/ipv4/stt.c                   | 1398 ++++++++++++++++++++++++++++++++++++++
 net/ipv6/sit.c                   |  152 ++---
 net/openvswitch/Kconfig          |   10 +
 net/openvswitch/Makefile         |    1 +
 net/openvswitch/vport-stt.c      |  216 ++++++
 19 files changed, 2674 insertions(+), 373 deletions(-)
 create mode 100644 include/net/stt.h
 create mode 100644 net/ipv4/ip_stt.c
 create mode 100644 net/ipv4/stt.c
 create mode 100644 net/openvswitch/vport-stt.c

-- 
1.9.1

^ permalink raw reply

* Re: [PATCH 1/4] NFC: nci: Add FWDL support
From: Samuel Ortiz @ 2015-01-29 23:28 UTC (permalink / raw)
  To: clement.perrochaud
  Cc: linux-nfc, Clément Perrochaud, sunil.jogi, jerome.pele,
	Charles.Gorand-Effinnov, lauro.venancio, aloisio.almeida, robh+dt,
	pawel.moll, mark.rutland, ijc+devicetree, galak, davem,
	grant.likely, lefrique, christophe.ricard, cuissard, bzhao,
	hirent, akarwar, linux-wireless, devicetree, linux-kernel, netdev
In-Reply-To: <1421940460-14049-2-git-send-email-clement.perrochaud@effinnov.com>

Hi Clement,

On Thu, Jan 22, 2015 at 04:27:37PM +0100, clement.perrochaud@effinnov.com wrote:
> From: Clément Perrochaud <clement.perrochaud@nxp.com>

- It really is not obvious that FWDL actually means firmware download
- A small commit message explaining why you need this (Mostly for the
  NXP chipset for now) would be nice.

Cheers,
Samuel.

^ permalink raw reply

* Re: [PATCH] stmmac: DMA threshold mode or SF mode can be different among multiple device instance
From: David Miller @ 2015-01-29 23:28 UTC (permalink / raw)
  To: sonic.adi; +Cc: peppe.cavallaro, netdev, adi-buildroot-devel, sonic.zhang
In-Reply-To: <1422521699-14096-1-git-send-email-sonic.adi@gmail.com>

From: Sonic Zhang <sonic.adi@gmail.com>
Date: Thu, 29 Jan 2015 16:54:59 +0800

>  		/* Try to bump up the dma threshold on this failure */
> -		if (unlikely(tc != SF_DMA_MODE) && (tc <= 256)) {
> +		if (unlikely(priv->xstats.threshold != SF_DMA_MODE) &&
> +			(tc <= 256)) {

This is not indented properly.  The second line of the conditional
should start precisely at the first column after the openning
parenthesis of the if() statement.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox