diff --git a/platform/linux-generic/include/odp_classification_datamodel.h b/platform/linux-generic/include/odp_classification_datamodel.h
index a40541986..25c488497 100644
--- a/platform/linux-generic/include/odp_classification_datamodel.h
+++ b/platform/linux-generic/include/odp_classification_datamodel.h
@@ -22,8 +22,6 @@ extern "C" {
 #include <odp/api/spinlock.h>
 #include <odp/api/classification.h>
 #include <odp_pool_internal.h>
-#include <odp_packet_internal.h>
-#include <odp_packet_io_internal.h>
 #include <odp_queue_if.h>
 #include <protocols/ip.h>
 
diff --git a/platform/linux-generic/include/odp_ipsec_internal.h b/platform/linux-generic/include/odp_ipsec_internal.h
index 0a7f96256..81ecec08e 100644
--- a/platform/linux-generic/include/odp_ipsec_internal.h
+++ b/platform/linux-generic/include/odp_ipsec_internal.h
@@ -23,6 +23,7 @@ extern "C" {
 #include <odp/api/byteorder.h>
 #include <odp/api/ipsec.h>
 #include <odp/api/ticketlock.h>
+#include <odp_classification_datamodel.h>
 
 /** @ingroup odp_ipsec
  *  @{
@@ -141,6 +142,7 @@ struct ipsec_sa_s {
 			odp_ipsec_lookup_mode_t lookup_mode;
 			odp_u32be_t	lookup_dst_ip;
 			odp_atomic_u64_t antireplay;
+			cos_t		*cos;
 		} in;
 
 		struct {
@@ -229,6 +231,11 @@ int _odp_ipsec_sa_replay_update(ipsec_sa_t *ipsec_sa, uint32_t seq,
 int _odp_ipsec_try_inline(odp_packet_t pkt);
 
 /**
+ * Returns ODP IPsec configuration
+ */
+const odp_ipsec_config_t *_odp_ipsec_config_get(void);
+
+/**
  * @}
  */
 
diff --git a/platform/linux-generic/odp_ipsec.c b/platform/linux-generic/odp_ipsec.c
index ef6a60249..6b5f5abf2 100644
--- a/platform/linux-generic/odp_ipsec.c
+++ b/platform/linux-generic/odp_ipsec.c
@@ -13,6 +13,7 @@
 #include <odp_debug_internal.h>
 #include <odp_packet_internal.h>
 #include <odp_ipsec_internal.h>
+#include <odp_classification_internal.h>
 
 #include <protocols/eth.h>
 #include <protocols/ip.h>
@@ -30,6 +31,7 @@ int odp_ipsec_capability(odp_ipsec_capability_t *capa)
 	int rc;
 	odp_crypto_capability_t crypto_capa;
 	odp_queue_capability_t queue_capa;
+	odp_cls_capability_t cls_capa;
 
 	memset(capa, 0, sizeof(odp_ipsec_capability_t));
 
@@ -39,6 +41,7 @@ int odp_ipsec_capability(odp_ipsec_capability_t *capa)
 	capa->op_mode_inline_out = ODP_SUPPORT_PREFERRED;
 
 	capa->proto_ah = ODP_SUPPORT_YES;
+	capa->pipeline_cls = ODP_SUPPORT_YES;
 
 	capa->max_num_sa = ODP_CONFIG_IPSEC_SAS;
 
@@ -57,6 +60,12 @@ int odp_ipsec_capability(odp_ipsec_capability_t *capa)
 
 	capa->max_queues = queue_capa.max_queues;
 
+	rc = odp_cls_capability(&cls_capa);
+	if (rc < 0)
+		return rc;
+
+	capa->max_cls_cos = cls_capa.max_cos;
+
 	return 0;
 }
 
@@ -95,6 +104,11 @@ int odp_ipsec_config(const odp_ipsec_config_t *config)
 	return 0;
 }
 
+const odp_ipsec_config_t *_odp_ipsec_config_get(void)
+{
+	return &ipsec_config;
+}
+
 static odp_ipsec_packet_result_t *ipsec_pkt_result(odp_packet_t packet)
 {
 	ODP_ASSERT(ODP_EVENT_PACKET_IPSEC ==
@@ -1055,7 +1069,16 @@ int odp_ipsec_in_enq(const odp_packet_t pkt_in[], int num_in,
 		result->status = status;
 		if (NULL != ipsec_sa) {
 			result->sa = ipsec_sa->ipsec_sa_hdl;
-			queue = ipsec_sa->queue;
+			if (ipsec_sa->in.cos && !status.error.all) {
+				odp_packet_hdr_t *pkt_hdr = odp_packet_hdr(pkt);
+				const uint8_t *base = odp_packet_data(pkt);
+
+				queue = cls_pkt_get_queue(pkt_hdr,
+							  ipsec_sa->in.cos,
+							  base);
+			} else {
+				queue = ipsec_sa->queue;
+			}
 		} else {
 			result->sa = ODP_IPSEC_SA_INVALID;
 			queue = ipsec_config.inbound.default_queue;
@@ -1138,6 +1161,7 @@ int _odp_ipsec_try_inline(odp_packet_t pkt)
 	ipsec_sa_t *ipsec_sa;
 	odp_ipsec_packet_result_t *result;
 	odp_packet_hdr_t *pkt_hdr;
+	odp_queue_t queue;
 
 	memset(&status, 0, sizeof(status));
 
@@ -1155,10 +1179,19 @@ int _odp_ipsec_try_inline(odp_packet_t pkt)
 	memset(result, 0, sizeof(*result));
 	result->status = status;
 	result->sa = ipsec_sa->ipsec_sa_hdl;
+	result->flag.inline_mode = 1;
 
 	pkt_hdr = odp_packet_hdr(pkt);
 	pkt_hdr->p.input_flags.dst_queue = 1;
-	pkt_hdr->dst_queue = queue_fn->from_ext(ipsec_sa->queue);
+	if (ipsec_sa->in.cos && !status.error.all) {
+		odp_packet_hdr_t *pkt_hdr = odp_packet_hdr(pkt);
+		const uint8_t *base = odp_packet_data(pkt);
+
+		queue = cls_pkt_get_queue(pkt_hdr, ipsec_sa->in.cos, base);
+	} else {
+		queue = ipsec_sa->queue;
+	}
+	pkt_hdr->dst_queue = queue_fn->from_ext(queue);
 
 	/* Last thing */
 	_odp_ipsec_sa_unuse(ipsec_sa);
diff --git a/platform/linux-generic/odp_ipsec_sad.c b/platform/linux-generic/odp_ipsec_sad.c
index 28092ac31..0bcc20b86 100644
--- a/platform/linux-generic/odp_ipsec_sad.c
+++ b/platform/linux-generic/odp_ipsec_sad.c
@@ -13,6 +13,7 @@
 
 #include <odp_debug_internal.h>
 #include <odp_ipsec_internal.h>
+#include <odp_classification_internal.h>
 
 #include <string.h>
 
@@ -216,9 +217,22 @@ odp_ipsec_sa_t odp_ipsec_sa_create(const odp_ipsec_sa_param_t *param)
 			       sizeof(ipsec_sa->in.lookup_dst_ip));
 
 		if (param->inbound.antireplay_ws > IPSEC_ANTIREPLAY_WS)
-			return ODP_IPSEC_SA_INVALID;
+			goto error;
 		ipsec_sa->antireplay = (param->inbound.antireplay_ws != 0);
 		odp_atomic_init_u64(&ipsec_sa->in.antireplay, 0);
+
+		if (ODP_IPSEC_PIPELINE_CLS == param->inbound.pipeline) {
+			if (ODP_IPSEC_OP_MODE_SYNC ==
+			    _odp_ipsec_config_get()->inbound_mode)
+				goto error;
+
+			ipsec_sa->in.cos =
+				_odp_cos_get_entry(param->inbound.dest_cos);
+			if (NULL == ipsec_sa->in.cos)
+				goto error;
+		} else {
+			ipsec_sa->in.cos = NULL;
+		}
 	} else {
 		odp_atomic_store_u32(&ipsec_sa->out.seq, 1);
 	}
