diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 41da032..9b25d08 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -390,8 +390,26 @@ struct tlv_ops {
 	bool	(*func)(unsigned int class, struct sk_buff *skb, int offset);
 };
 
+struct tlv_rx_params {
+	unsigned char class : 3;
+};
+
+struct tlv_tx_params {
+	unsigned char admin_perm : 2;
+	unsigned char user_perm : 2;
+	unsigned char class : 3;
+	unsigned char align_mult : 4;
+	unsigned char align_off : 4;
+	unsigned char data_len_mult : 4;
+	unsigned char data_len_off : 4;
+	unsigned char min_data_len;
+	unsigned char max_data_len;
+	unsigned char preferred_order;
+};
+
 struct tlv_params {
-	unsigned char rx_class : 3;
+	struct tlv_rx_params r;
+	struct tlv_tx_params t;
 };
 
 struct tlv_proc {
@@ -417,6 +435,12 @@ struct tlv_param_table {
 
 extern struct tlv_param_table ipv6_tlv_param_table;
 
+/* Preferred TLV ordering (placed by increasing order) */
+#define TLV_PREF_ORDER_HAO		10
+#define TLV_PREF_ORDER_ROUTERALERT	20
+#define TLV_PREF_ORDER_JUMBO		30
+#define TLV_PREF_ORDER_CALIPSO		40
+
 int tlv_set_proc(struct tlv_param_table *tlv_param_table,
 		 unsigned char type, const struct tlv_proc *proc);
 int tlv_unset_proc(struct tlv_param_table *tlv_param_table, unsigned char type);
diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
index 8b9ac7f..6a99ee1 100644
--- a/include/uapi/linux/in6.h
+++ b/include/uapi/linux/in6.h
@@ -307,4 +307,12 @@ struct in6_flowlabel_req {
 #define IPV6_TLV_CLASS_ANY_DSTOPT      (IPV6_TLV_CLASS_FLAG_RTRDSTOPT | \
 					IPV6_TLV_CLASS_FLAG_DSTOPT)
 
+/* TLV permissions values */
+enum {
+	IPV6_TLV_PERM_NONE,
+	IPV6_TLV_PERM_WITH_CHECK,
+	IPV6_TLV_PERM_NO_CHECK,
+	IPV6_TLV_PERM_MAX = IPV6_TLV_PERM_NO_CHECK
+};
+
 #endif /* _UAPI_LINUX_IN6_H */
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index b28c108..520c2eb 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -166,7 +166,7 @@ static bool ip6_parse_tlv(unsigned int class, struct sk_buff *skb,
 				goto bad;
 
 			curr = tlv_get_proc(&ipv6_tlv_param_table, nh[off]);
-			if ((curr->params.rx_class & class) && curr->ops.func) {
+			if ((curr->params.r.class & class) && curr->ops.func) {
 				/* Handler will apply additional checks to
 				 * the TLV
 				 */
diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c
index def52b5..53e92fd 100644
--- a/net/ipv6/exthdrs_core.c
+++ b/net/ipv6/exthdrs_core.c
@@ -431,7 +431,18 @@ static void tlv_param_table_release(struct rcu_head *rcu)
 
 /* Default (unset) values for TLV parameters */
 static const struct tlv_proc tlv_default_proc = {
-	.params.rx_class = 0,
+	.params.r.class = 0,
+
+	.params.t.preferred_order = 0,
+	.params.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+	.params.t.user_perm = IPV6_TLV_PERM_NONE,
+	.params.t.class = 0,
+	.params.t.align_mult = (4 - 1), /* Default alignment: 4n + 2 */
+	.params.t.align_off = 2,
+	.params.t.min_data_len = 0,
+	.params.t.max_data_len = 255,
+	.params.t.data_len_mult = (1 - 1), /* No default length align */
+	.params.t.data_len_off = 0,
 };
 
 static size_t tlv_param_table_size(unsigned char count)
@@ -650,10 +661,13 @@ int exthdrs_init(struct tlv_param_table *tlv_param_table,
 		 const struct tlv_proc_init *tlv_init_params,
 		 int num_init_params)
 {
+	unsigned long check_map[BITS_TO_LONGS(256)];
 	struct tlv_param_table_data *tpt;
 	size_t tsize;
 	int i;
 
+	memset(check_map, 0, sizeof(check_map));
+
 	tsize = tlv_param_table_size(num_init_params + 1);
 
 	tpt = kvmalloc(tsize, GFP_KERNEL);
@@ -667,6 +681,7 @@ int exthdrs_init(struct tlv_param_table *tlv_param_table,
 
 	for (i = 0; i < num_init_params; i++) {
 		const struct tlv_proc_init *tpi = &tlv_init_params[i];
+		unsigned int order = tpi->proc.params.t.preferred_order;
 		struct tlv_proc *tp = &tpt->procs[i + 1];
 
 		if (WARN_ON(tpi->type < 2)) {
@@ -680,6 +695,11 @@ int exthdrs_init(struct tlv_param_table *tlv_param_table,
 			return -EINVAL;
 		}
 
+		if (order) {
+			WARN_ON(test_bit(order, check_map));
+			set_bit(order, check_map);
+		}
+
 		*tp = tpi->proc;
 		tpt->entries[tpi->type] = i + 1;
 	}
diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c
index 02d12c5..042ca14 100644
--- a/net/ipv6/exthdrs_options.c
+++ b/net/ipv6/exthdrs_options.c
@@ -181,26 +181,75 @@ static const struct tlv_proc_init tlv_init_params[] __initconst = {
 		.type = IPV6_TLV_HAO,
 
 		.proc.ops.func = ipv6_dest_hao,
-		.proc.params.rx_class = IPV6_TLV_CLASS_FLAG_DSTOPT,
+
+		.proc.params.r.class = IPV6_TLV_CLASS_FLAG_DSTOPT,
+
+		.proc.params.t.preferred_order = TLV_PREF_ORDER_HAO,
+		.proc.params.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.proc.params.t.user_perm = IPV6_TLV_PERM_NONE,
+		.proc.params.t.class = IPV6_TLV_CLASS_FLAG_DSTOPT,
+		.proc.params.t.align_mult = (8 - 1), /* Align to 8n + 6 */
+		.proc.params.t.align_off = 6,
+		.proc.params.t.min_data_len = 16,
+		.proc.params.t.max_data_len = 16,
+		.proc.params.t.data_len_mult = (1 - 1), /* Fixed length */
+		.proc.params.t.data_len_off = 0,
 	},
 #endif
 	{
 		.type = IPV6_TLV_ROUTERALERT,
 
 		.proc.ops.func = ipv6_hop_ra,
-		.proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+
+		.proc.params.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+
+		.proc.params.t.preferred_order = TLV_PREF_ORDER_ROUTERALERT,
+		.proc.params.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.proc.params.t.user_perm = IPV6_TLV_PERM_NONE,
+		.proc.params.t.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+		.proc.params.t.align_mult = (2 - 1), /* Align to 2n */
+		.proc.params.t.align_off = 0,
+		.proc.params.t.min_data_len = 2,
+		.proc.params.t.max_data_len = 2,
+		.proc.params.t.data_len_mult = (1 - 1), /* Fixed length */
+		.proc.params.t.data_len_off = 0,
+
 	},
 	{
 		.type = IPV6_TLV_JUMBO,
 
 		.proc.ops.func	= ipv6_hop_jumbo,
-		.proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+
+		.proc.params.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+
+		.proc.params.t.preferred_order = TLV_PREF_ORDER_JUMBO,
+		.proc.params.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.proc.params.t.user_perm = IPV6_TLV_PERM_NONE,
+		.proc.params.t.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+		.proc.params.t.align_mult = (4 - 1), /* Align to 4n + 2 */
+		.proc.params.t.align_off = 2,
+		.proc.params.t.min_data_len = 4,
+		.proc.params.t.max_data_len = 4,
+		.proc.params.t.data_len_mult = (1 - 1), /* Fixed length */
+		.proc.params.t.data_len_off = 0,
 	},
 	{
 		.type = IPV6_TLV_CALIPSO,
 
 		.proc.ops.func = ipv6_hop_calipso,
-		.proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+		.proc.params.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+
+		.proc.params.t.preferred_order = TLV_PREF_ORDER_CALIPSO,
+		.proc.params.t.admin_perm = IPV6_TLV_PERM_NO_CHECK,
+		.proc.params.t.user_perm = IPV6_TLV_PERM_NONE,
+		.proc.params.t.class = IPV6_TLV_CLASS_FLAG_HOPOPT,
+		.proc.params.t.align_mult = (4 - 1), /* Align to 4n + 2 */
+		.proc.params.t.align_off = 2,
+		.proc.params.t.min_data_len = 8,
+		.proc.params.t.max_data_len = 252,
+		.proc.params.t.data_len_mult = (4 - 1),
+					/* Length is multiple of 4 */
+		.proc.params.t.data_len_off = 0,
 	},
 };
 
