diff --git a/examples/ip_pipeline/pipeline/pipeline_routing.c b/examples/ip_pipeline/pipeline/pipeline_routing.c
index beec982..8ba1e25 100644
--- a/examples/ip_pipeline/pipeline/pipeline_routing.c
+++ b/examples/ip_pipeline/pipeline/pipeline_routing.c
@@ -43,7 +43,7 @@
 
 struct app_pipeline_routing_route {
 	struct pipeline_routing_route_key key;
-	struct app_pipeline_routing_route_params params;
+	struct pipeline_routing_route_data data;
 	void *entry_ptr;
 
 	TAILQ_ENTRY(app_pipeline_routing_route) node;
@@ -196,12 +196,44 @@ print_route(const struct app_pipeline_routing_route *route)
 			key->ip & 0xFF,
 
 			key->depth,
-			route->params.port_id,
+			route->data.port_id);
+
+		if (route->data.flags & PIPELINE_ROUTING_ROUTE_ARP)
+			printf(
+				", Next Hop IP = %" PRIu32 ".%" PRIu32
+				".%" PRIu32 ".%" PRIu32,
+				(route->data.ethernet.ip >> 24) & 0xFF,
+				(route->data.ethernet.ip >> 16) & 0xFF,
+				(route->data.ethernet.ip >> 8) & 0xFF,
+				route->data.ethernet.ip & 0xFF);
+		else
+			printf(
+				", Next Hop HWaddress = %02" PRIx32
+				":%02" PRIx32 ":%02" PRIx32
+				":%02" PRIx32 ":%02" PRIx32
+				":%02" PRIx32,
+				route->data.ethernet.macaddr.addr_bytes[0],
+				route->data.ethernet.macaddr.addr_bytes[1],
+				route->data.ethernet.macaddr.addr_bytes[2],
+				route->data.ethernet.macaddr.addr_bytes[3],
+				route->data.ethernet.macaddr.addr_bytes[4],
+				route->data.ethernet.macaddr.addr_bytes[5]);
+
+		if (route->data.flags & PIPELINE_ROUTING_ROUTE_QINQ)
+			printf(", QinQ SVLAN = %" PRIu32 " CVLAN = %" PRIu32,
+				route->data.l2.qinq.svlan,
+				route->data.l2.qinq.cvlan);
+
+		if (route->data.flags & PIPELINE_ROUTING_ROUTE_MPLS) {
+			uint32_t i;
+
+			printf(", MPLS labels");
+			for (i = 0; i < route->data.l2.mpls.n_labels; i++)
+				printf(" %" PRIu32,
+					route->data.l2.mpls.labels[i]);
+		}
 
-			(route->params.ip >> 24) & 0xFF,
-			(route->params.ip >> 16) & 0xFF,
-			(route->params.ip >> 8) & 0xFF,
-			route->params.ip & 0xFF);
+		printf(")\n");
 	}
 }
 
@@ -212,6 +244,8 @@ print_arp_entry(const struct app_pipeline_routing_arp_entry *entry)
 		".%" PRIu32 ".%" PRIu32 ") => "
 		"HWaddress = %02" PRIx32 ":%02" PRIx32 ":%02" PRIx32
 		":%02" PRIx32 ":%02" PRIx32 ":%02" PRIx32 "\n",
+
+
 		entry->key.key.ipv4.port_id,
 		(entry->key.key.ipv4.ip >> 24) & 0xFF,
 		(entry->key.key.ipv4.ip >> 16) & 0xFF,
@@ -253,7 +287,7 @@ int
 app_pipeline_routing_add_route(struct app_params *app,
 	uint32_t pipeline_id,
 	struct pipeline_routing_route_key *key,
-	struct app_pipeline_routing_route_params *route_params)
+	struct pipeline_routing_route_data *data)
 {
 	struct pipeline_routing *p;
 
@@ -267,7 +301,7 @@ app_pipeline_routing_add_route(struct app_params *app,
 	/* Check input arguments */
 	if ((app == NULL) ||
 		(key == NULL) ||
-		(route_params == NULL))
+		(data == NULL))
 		return -1;
 
 	p = app_pipeline_data_fe(app, pipeline_id);
@@ -287,8 +321,8 @@ app_pipeline_routing_add_route(struct app_params *app,
 		netmask = (~0) << (32 - depth);
 		key->key.ipv4.ip &= netmask;
 
-		/* route params */
-		if (route_params->port_id >= p->n_ports_out)
+		/* data */
+		if (data->port_id >= p->n_ports_out)
 			return -1;
 	}
 	break;
@@ -318,9 +352,7 @@ app_pipeline_routing_add_route(struct app_params *app,
 	req->type = PIPELINE_MSG_REQ_CUSTOM;
 	req->subtype = PIPELINE_ROUTING_MSG_REQ_ROUTE_ADD;
 	memcpy(&req->key, key, sizeof(*key));
-	req->flags = route_params->flags;
-	req->port_id = route_params->port_id;
-	req->ip = route_params->ip;
+	memcpy(&req->data, data, sizeof(*data));
 
 	rsp = app_msg_send_recv(app, pipeline_id, req, MSG_TIMEOUT_DEFAULT);
 	if (rsp == NULL) {
@@ -341,7 +373,7 @@ app_pipeline_routing_add_route(struct app_params *app,
 	}
 
 	memcpy(&entry->key, key, sizeof(*key));
-	memcpy(&entry->params, route_params, sizeof(*route_params));
+	memcpy(&entry->data, data, sizeof(*data));
 	entry->entry_ptr = rsp->entry_ptr;
 
 	/* Commit entry */
@@ -820,31 +852,174 @@ app_pipeline_routing_delete_default_arp_entry(struct app_params *app,
 	return 0;
 }
 
+static int
+parse_labels(char *string, uint32_t *labels, uint32_t *n_labels)
+{
+	uint32_t n_max_labels = *n_labels, count = 0;
+
+	/* Check for void list of labels */
+	if (strcmp(string, "<void>") == 0) {
+		*n_labels = 0;
+		return 0;
+	}
+
+	/* At least one label should be present */
+	for ( ; (*string != '\0'); ) {
+		char *next;
+		int value;
+
+		if (count >= n_max_labels)
+			return -1;
+
+		if (count > 0) {
+			if (string[0] != ':')
+				return -1;
+
+			string++;
+		}
+
+		value = strtol(string, &next, 10);
+		if (next == string)
+			return -1;
+		string = next;
+
+		labels[count++] = (uint32_t) value;
+	}
+
+	*n_labels = count;
+	return 0;
+}
+
+/*
+ * route add (mpls = no, qinq = no, arp = no)
+ */
+
+struct cmd_route_add1_result {
+	cmdline_fixed_string_t p_string;
+	uint32_t p;
+	cmdline_fixed_string_t route_string;
+	cmdline_fixed_string_t add_string;
+	cmdline_ipaddr_t ip;
+	uint32_t depth;
+	cmdline_fixed_string_t port_string;
+	uint32_t port;
+	cmdline_fixed_string_t ether_string;
+	struct ether_addr macaddr;
+};
+
+static void
+cmd_route_add1_parsed(
+	void *parsed_result,
+	__rte_unused struct cmdline *cl,
+	void *data)
+{
+	struct cmd_route_add1_result *params = parsed_result;
+	struct app_params *app = data;
+	struct pipeline_routing_route_key key;
+	struct pipeline_routing_route_data route_data;
+	int status;
+
+	/* Create route */
+	key.type = PIPELINE_ROUTING_ROUTE_IPV4;
+	key.key.ipv4.ip = rte_bswap32((uint32_t) params->ip.addr.ipv4.s_addr);
+	key.key.ipv4.depth = params->depth;
+
+	route_data.flags = 0;
+	route_data.port_id = params->port;
+	route_data.ethernet.macaddr = params->macaddr;
+
+	status = app_pipeline_routing_add_route(app,
+		params->p,
+		&key,
+		&route_data);
+
+	if (status != 0) {
+		printf("Command failed\n");
+		return;
+	}
+}
+
+static cmdline_parse_token_string_t cmd_route_add1_p_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add1_result, p_string,
+	"p");
+
+static cmdline_parse_token_num_t cmd_route_add1_p =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add1_result, p, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add1_route_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add1_result, route_string,
+	"route");
+
+static cmdline_parse_token_string_t cmd_route_add1_add_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add1_result, add_string,
+	"add");
+
+static cmdline_parse_token_ipaddr_t cmd_route_add1_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add1_result, ip);
+
+static cmdline_parse_token_num_t cmd_route_add1_depth =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add1_result, depth, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add1_port_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add1_result, port_string,
+	"port");
+
+static cmdline_parse_token_num_t cmd_route_add1_port =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add1_result, port, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add1_ether_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add1_result, ether_string,
+	"ether");
+
+static cmdline_parse_token_etheraddr_t cmd_route_add1_macaddr =
+	TOKEN_ETHERADDR_INITIALIZER(struct cmd_route_add1_result, macaddr);
+
+static cmdline_parse_inst_t cmd_route_add1 = {
+	.f = cmd_route_add1_parsed,
+	.data = NULL,
+	.help_str = "Route add (mpls = no, qinq = no, arp = no)",
+	.tokens = {
+		(void *)&cmd_route_add1_p_string,
+		(void *)&cmd_route_add1_p,
+		(void *)&cmd_route_add1_route_string,
+		(void *)&cmd_route_add1_add_string,
+		(void *)&cmd_route_add1_ip,
+		(void *)&cmd_route_add1_depth,
+		(void *)&cmd_route_add1_port_string,
+		(void *)&cmd_route_add1_port,
+		(void *)&cmd_route_add1_ether_string,
+		(void *)&cmd_route_add1_macaddr,
+		NULL,
+	},
+};
+
 /*
- * route add
+ * route add (mpls = no, qinq = no, arp = yes)
  */
 
-struct cmd_route_add_result {
+struct cmd_route_add2_result {
 	cmdline_fixed_string_t p_string;
 	uint32_t p;
 	cmdline_fixed_string_t route_string;
 	cmdline_fixed_string_t add_string;
 	cmdline_ipaddr_t ip;
 	uint32_t depth;
+	cmdline_fixed_string_t port_string;
 	uint32_t port;
+	cmdline_fixed_string_t ether_string;
 	cmdline_ipaddr_t nh_ip;
 };
 
 static void
-cmd_route_add_parsed(
+cmd_route_add2_parsed(
 	void *parsed_result,
 	__rte_unused struct cmdline *cl,
 	void *data)
 {
-	struct cmd_route_add_result *params = parsed_result;
+	struct cmd_route_add2_result *params = parsed_result;
 	struct app_params *app = data;
 	struct pipeline_routing_route_key key;
-	struct app_pipeline_routing_route_params rt_params;
+	struct pipeline_routing_route_data route_data;
 	int status;
 
 	/* Create route */
@@ -852,14 +1027,15 @@ cmd_route_add_parsed(
 	key.key.ipv4.ip = rte_bswap32((uint32_t) params->ip.addr.ipv4.s_addr);
 	key.key.ipv4.depth = params->depth;
 
-	rt_params.flags = 0; /* remote route */
-	rt_params.port_id = params->port;
-	rt_params.ip = rte_bswap32((uint32_t) params->nh_ip.addr.ipv4.s_addr);
+	route_data.flags = PIPELINE_ROUTING_ROUTE_ARP;
+	route_data.port_id = params->port;
+	route_data.ethernet.ip =
+		rte_bswap32((uint32_t) params->nh_ip.addr.ipv4.s_addr);
 
 	status = app_pipeline_routing_add_route(app,
 		params->p,
 		&key,
-		&rt_params);
+		&route_data);
 
 	if (status != 0) {
 		printf("Command failed\n");
@@ -867,46 +1043,558 @@ cmd_route_add_parsed(
 	}
 }
 
-static cmdline_parse_token_string_t cmd_route_add_p_string =
-	TOKEN_STRING_INITIALIZER(struct cmd_route_add_result, p_string,
+static cmdline_parse_token_string_t cmd_route_add2_p_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add2_result, p_string,
 	"p");
 
-static cmdline_parse_token_num_t cmd_route_add_p =
-	TOKEN_NUM_INITIALIZER(struct cmd_route_add_result, p, UINT32);
+static cmdline_parse_token_num_t cmd_route_add2_p =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add2_result, p, UINT32);
 
-static cmdline_parse_token_string_t cmd_route_add_route_string =
-	TOKEN_STRING_INITIALIZER(struct cmd_route_add_result, route_string,
+static cmdline_parse_token_string_t cmd_route_add2_route_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add2_result, route_string,
 	"route");
 
-static cmdline_parse_token_string_t cmd_route_add_add_string =
-	TOKEN_STRING_INITIALIZER(struct cmd_route_add_result, add_string,
+static cmdline_parse_token_string_t cmd_route_add2_add_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add2_result, add_string,
 	"add");
 
-static cmdline_parse_token_ipaddr_t cmd_route_add_ip =
-	TOKEN_IPV4_INITIALIZER(struct cmd_route_add_result, ip);
+static cmdline_parse_token_ipaddr_t cmd_route_add2_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add2_result, ip);
+
+static cmdline_parse_token_num_t cmd_route_add2_depth =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add2_result, depth, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add2_port_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add2_result, port_string,
+	"port");
+
+static cmdline_parse_token_num_t cmd_route_add2_port =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add2_result, port, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add2_ether_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add2_result, ether_string,
+	"ether");
+
+static cmdline_parse_token_ipaddr_t cmd_route_add2_nh_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add2_result, nh_ip);
+
+static cmdline_parse_inst_t cmd_route_add2 = {
+	.f = cmd_route_add2_parsed,
+	.data = NULL,
+	.help_str = "Route add (mpls = no, qinq = no, arp = yes)",
+	.tokens = {
+		(void *)&cmd_route_add2_p_string,
+		(void *)&cmd_route_add2_p,
+		(void *)&cmd_route_add2_route_string,
+		(void *)&cmd_route_add2_add_string,
+		(void *)&cmd_route_add2_ip,
+		(void *)&cmd_route_add2_depth,
+		(void *)&cmd_route_add2_port_string,
+		(void *)&cmd_route_add2_port,
+		(void *)&cmd_route_add2_ether_string,
+		(void *)&cmd_route_add2_nh_ip,
+		NULL,
+	},
+};
+
+/*
+ * route add (mpls = no, qinq = yes, arp = no)
+ */
+
+struct cmd_route_add3_result {
+	cmdline_fixed_string_t p_string;
+	uint32_t p;
+	cmdline_fixed_string_t route_string;
+	cmdline_fixed_string_t add_string;
+	cmdline_ipaddr_t ip;
+	uint32_t depth;
+	cmdline_fixed_string_t port_string;
+	uint32_t port;
+	cmdline_fixed_string_t ether_string;
+	struct ether_addr macaddr;
+	cmdline_fixed_string_t qinq_string;
+	uint32_t svlan;
+	uint32_t cvlan;
+};
+
+static void
+cmd_route_add3_parsed(
+	void *parsed_result,
+	__rte_unused struct cmdline *cl,
+	void *data)
+{
+	struct cmd_route_add3_result *params = parsed_result;
+	struct app_params *app = data;
+	struct pipeline_routing_route_key key;
+	struct pipeline_routing_route_data route_data;
+	int status;
+
+	/* Create route */
+	key.type = PIPELINE_ROUTING_ROUTE_IPV4;
+	key.key.ipv4.ip = rte_bswap32((uint32_t) params->ip.addr.ipv4.s_addr);
+	key.key.ipv4.depth = params->depth;
+
+	route_data.flags = PIPELINE_ROUTING_ROUTE_QINQ;
+	route_data.port_id = params->port;
+	route_data.ethernet.macaddr = params->macaddr;
+	route_data.l2.qinq.svlan = params->svlan;
+	route_data.l2.qinq.cvlan = params->cvlan;
+
+	status = app_pipeline_routing_add_route(app,
+		params->p,
+		&key,
+		&route_data);
+
+	if (status != 0) {
+		printf("Command failed\n");
+		return;
+	}
+}
+
+static cmdline_parse_token_string_t cmd_route_add3_p_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add3_result, p_string,
+	"p");
+
+static cmdline_parse_token_num_t cmd_route_add3_p =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add3_result, p, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add3_route_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add3_result, route_string,
+	"route");
+
+static cmdline_parse_token_string_t cmd_route_add3_add_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add3_result, add_string,
+	"add");
+
+static cmdline_parse_token_ipaddr_t cmd_route_add3_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add3_result, ip);
+
+static cmdline_parse_token_num_t cmd_route_add3_depth =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add3_result, depth, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add3_port_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add3_result, port_string,
+	"port");
+
+static cmdline_parse_token_num_t cmd_route_add3_port =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add3_result, port, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add3_ether_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add3_result, ether_string,
+	"ether");
+
+static cmdline_parse_token_etheraddr_t cmd_route_add3_macaddr =
+	TOKEN_ETHERADDR_INITIALIZER(struct cmd_route_add3_result, macaddr);
+
+static cmdline_parse_token_string_t cmd_route_add3_qinq_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add3_result, qinq_string,
+	"qinq");
+
+static cmdline_parse_token_num_t cmd_route_add3_svlan =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add3_result, svlan, UINT32);
+
+static cmdline_parse_token_num_t cmd_route_add3_cvlan =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add3_result, cvlan, UINT32);
+
+static cmdline_parse_inst_t cmd_route_add3 = {
+	.f = cmd_route_add3_parsed,
+	.data = NULL,
+	.help_str = "Route add (qinq = yes, arp = no)",
+	.tokens = {
+		(void *)&cmd_route_add3_p_string,
+		(void *)&cmd_route_add3_p,
+		(void *)&cmd_route_add3_route_string,
+		(void *)&cmd_route_add3_add_string,
+		(void *)&cmd_route_add3_ip,
+		(void *)&cmd_route_add3_depth,
+		(void *)&cmd_route_add3_port_string,
+		(void *)&cmd_route_add3_port,
+		(void *)&cmd_route_add3_ether_string,
+		(void *)&cmd_route_add3_macaddr,
+		(void *)&cmd_route_add3_qinq_string,
+		(void *)&cmd_route_add3_svlan,
+		(void *)&cmd_route_add3_cvlan,
+		NULL,
+	},
+};
+
+/*
+ * route add (mpls = no, qinq = yes, arp = yes)
+ */
+
+struct cmd_route_add4_result {
+	cmdline_fixed_string_t p_string;
+	uint32_t p;
+	cmdline_fixed_string_t route_string;
+	cmdline_fixed_string_t add_string;
+	cmdline_ipaddr_t ip;
+	uint32_t depth;
+	cmdline_fixed_string_t port_string;
+	uint32_t port;
+	cmdline_fixed_string_t ether_string;
+	cmdline_ipaddr_t nh_ip;
+	cmdline_fixed_string_t qinq_string;
+	uint32_t svlan;
+	uint32_t cvlan;
+};
+
+static void
+cmd_route_add4_parsed(
+	void *parsed_result,
+	__rte_unused struct cmdline *cl,
+	void *data)
+{
+	struct cmd_route_add4_result *params = parsed_result;
+	struct app_params *app = data;
+	struct pipeline_routing_route_key key;
+	struct pipeline_routing_route_data route_data;
+	int status;
+
+	/* Create route */
+	key.type = PIPELINE_ROUTING_ROUTE_IPV4;
+	key.key.ipv4.ip = rte_bswap32((uint32_t) params->ip.addr.ipv4.s_addr);
+	key.key.ipv4.depth = params->depth;
+
+	route_data.flags = PIPELINE_ROUTING_ROUTE_QINQ |
+		PIPELINE_ROUTING_ROUTE_ARP;
+	route_data.port_id = params->port;
+	route_data.ethernet.ip =
+		rte_bswap32((uint32_t) params->nh_ip.addr.ipv4.s_addr);
+	route_data.l2.qinq.svlan = params->svlan;
+	route_data.l2.qinq.cvlan = params->cvlan;
+
+	status = app_pipeline_routing_add_route(app,
+		params->p,
+		&key,
+		&route_data);
+
+	if (status != 0) {
+		printf("Command failed\n");
+		return;
+	}
+}
+
+static cmdline_parse_token_string_t cmd_route_add4_p_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add4_result, p_string,
+	"p");
+
+static cmdline_parse_token_num_t cmd_route_add4_p =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add4_result, p, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add4_route_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add4_result, route_string,
+	"route");
+
+static cmdline_parse_token_string_t cmd_route_add4_add_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add4_result, add_string,
+	"add");
+
+static cmdline_parse_token_ipaddr_t cmd_route_add4_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add4_result, ip);
+
+static cmdline_parse_token_num_t cmd_route_add4_depth =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add4_result, depth, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add4_port_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add4_result, port_string,
+	"port");
+
+static cmdline_parse_token_num_t cmd_route_add4_port =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add4_result, port, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add4_ether_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add4_result, ether_string,
+	"ether");
+
+static cmdline_parse_token_ipaddr_t cmd_route_add4_nh_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add4_result, nh_ip);
+
+static cmdline_parse_token_string_t cmd_route_add4_qinq_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add4_result, qinq_string,
+	"qinq");
+
+static cmdline_parse_token_num_t cmd_route_add4_svlan =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add4_result, svlan, UINT32);
+
+static cmdline_parse_token_num_t cmd_route_add4_cvlan =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add4_result, cvlan, UINT32);
+
+static cmdline_parse_inst_t cmd_route_add4 = {
+	.f = cmd_route_add4_parsed,
+	.data = NULL,
+	.help_str = "Route add (qinq = yes, arp = yes)",
+	.tokens = {
+		(void *)&cmd_route_add4_p_string,
+		(void *)&cmd_route_add4_p,
+		(void *)&cmd_route_add4_route_string,
+		(void *)&cmd_route_add4_add_string,
+		(void *)&cmd_route_add4_ip,
+		(void *)&cmd_route_add4_depth,
+		(void *)&cmd_route_add4_port_string,
+		(void *)&cmd_route_add4_port,
+		(void *)&cmd_route_add4_ether_string,
+		(void *)&cmd_route_add4_nh_ip,
+		(void *)&cmd_route_add4_qinq_string,
+		(void *)&cmd_route_add4_svlan,
+		(void *)&cmd_route_add4_cvlan,
+		NULL,
+	},
+};
+
+/*
+ * route add (mpls = yes, qinq = no, arp = no)
+ */
+
+struct cmd_route_add5_result {
+	cmdline_fixed_string_t p_string;
+	uint32_t p;
+	cmdline_fixed_string_t route_string;
+	cmdline_fixed_string_t add_string;
+	cmdline_ipaddr_t ip;
+	uint32_t depth;
+	cmdline_fixed_string_t port_string;
+	uint32_t port;
+	cmdline_fixed_string_t ether_string;
+	struct ether_addr macaddr;
+	cmdline_fixed_string_t mpls_string;
+	cmdline_fixed_string_t mpls_labels;
+};
+
+static void
+cmd_route_add5_parsed(
+	void *parsed_result,
+	__rte_unused struct cmdline *cl,
+	void *data)
+{
+	struct cmd_route_add5_result *params = parsed_result;
+	struct app_params *app = data;
+	struct pipeline_routing_route_key key;
+	struct pipeline_routing_route_data route_data;
+	uint32_t mpls_labels[PIPELINE_ROUTING_MPLS_LABELS_MAX];
+	uint32_t n_labels = RTE_DIM(mpls_labels);
+	uint32_t i;
+	int status;
+
+	/* Parse MPLS labels */
+	status = parse_labels(params->mpls_labels, mpls_labels, &n_labels);
+	if (status) {
+		printf("MPLS labels parse error\n");
+		return;
+	}
+
+	/* Create route */
+	key.type = PIPELINE_ROUTING_ROUTE_IPV4;
+	key.key.ipv4.ip = rte_bswap32((uint32_t) params->ip.addr.ipv4.s_addr);
+	key.key.ipv4.depth = params->depth;
+
+	route_data.flags = PIPELINE_ROUTING_ROUTE_MPLS;
+	route_data.port_id = params->port;
+	route_data.ethernet.macaddr = params->macaddr;
+	for (i = 0; i < n_labels; i++)
+		route_data.l2.mpls.labels[i] = mpls_labels[i];
+	route_data.l2.mpls.n_labels = n_labels;
+
+	status = app_pipeline_routing_add_route(app,
+		params->p,
+		&key,
+		&route_data);
+
+	if (status != 0) {
+		printf("Command failed\n");
+		return;
+	}
+}
+
+static cmdline_parse_token_string_t cmd_route_add5_p_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add5_result, p_string,
+	"p");
+
+static cmdline_parse_token_num_t cmd_route_add5_p =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add5_result, p, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add5_route_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add5_result, route_string,
+	"route");
+
+static cmdline_parse_token_string_t cmd_route_add5_add_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add5_result, add_string,
+	"add");
+
+static cmdline_parse_token_ipaddr_t cmd_route_add5_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add5_result, ip);
+
+static cmdline_parse_token_num_t cmd_route_add5_depth =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add5_result, depth, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add5_port_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add5_result, port_string,
+	"port");
+
+static cmdline_parse_token_num_t cmd_route_add5_port =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add5_result, port, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add5_ether_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add5_result, ether_string,
+	"ether");
+
+static cmdline_parse_token_etheraddr_t cmd_route_add5_macaddr =
+	TOKEN_ETHERADDR_INITIALIZER(struct cmd_route_add5_result, macaddr);
+
+static cmdline_parse_token_string_t cmd_route_add5_mpls_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add5_result, mpls_string,
+	"mpls");
+
+static cmdline_parse_token_string_t cmd_route_add5_mpls_labels =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add5_result, mpls_labels,
+	NULL);
+
+static cmdline_parse_inst_t cmd_route_add5 = {
+	.f = cmd_route_add5_parsed,
+	.data = NULL,
+	.help_str = "Route add (mpls = yes, arp = no)",
+	.tokens = {
+		(void *)&cmd_route_add5_p_string,
+		(void *)&cmd_route_add5_p,
+		(void *)&cmd_route_add5_route_string,
+		(void *)&cmd_route_add5_add_string,
+		(void *)&cmd_route_add5_ip,
+		(void *)&cmd_route_add5_depth,
+		(void *)&cmd_route_add5_port_string,
+		(void *)&cmd_route_add5_port,
+		(void *)&cmd_route_add5_ether_string,
+		(void *)&cmd_route_add5_macaddr,
+		(void *)&cmd_route_add5_mpls_string,
+		(void *)&cmd_route_add5_mpls_labels,
+		NULL,
+	},
+};
+
+/*
+ * route add (mpls = yes, qinq = no, arp = yes)
+ */
+
+struct cmd_route_add6_result {
+	cmdline_fixed_string_t p_string;
+	uint32_t p;
+	cmdline_fixed_string_t route_string;
+	cmdline_fixed_string_t add_string;
+	cmdline_ipaddr_t ip;
+	uint32_t depth;
+	cmdline_fixed_string_t port_string;
+	uint32_t port;
+	cmdline_fixed_string_t ether_string;
+	cmdline_ipaddr_t nh_ip;
+	cmdline_fixed_string_t mpls_string;
+	cmdline_fixed_string_t mpls_labels;
+};
+
+static void
+cmd_route_add6_parsed(
+	void *parsed_result,
+	__rte_unused struct cmdline *cl,
+	void *data)
+{
+	struct cmd_route_add6_result *params = parsed_result;
+	struct app_params *app = data;
+	struct pipeline_routing_route_key key;
+	struct pipeline_routing_route_data route_data;
+	uint32_t mpls_labels[PIPELINE_ROUTING_MPLS_LABELS_MAX];
+	uint32_t n_labels = RTE_DIM(mpls_labels);
+	uint32_t i;
+	int status;
+
+	/* Parse MPLS labels */
+	status = parse_labels(params->mpls_labels, mpls_labels, &n_labels);
+	if (status) {
+		printf("MPLS labels parse error\n");
+		return;
+	}
+
+	/* Create route */
+	key.type = PIPELINE_ROUTING_ROUTE_IPV4;
+	key.key.ipv4.ip = rte_bswap32((uint32_t) params->ip.addr.ipv4.s_addr);
+	key.key.ipv4.depth = params->depth;
+
+	route_data.flags = PIPELINE_ROUTING_ROUTE_MPLS |
+		PIPELINE_ROUTING_ROUTE_ARP;
+	route_data.port_id = params->port;
+	route_data.ethernet.ip =
+		rte_bswap32((uint32_t) params->nh_ip.addr.ipv4.s_addr);
+	for (i = 0; i < n_labels; i++)
+		route_data.l2.mpls.labels[i] = mpls_labels[i];
+	route_data.l2.mpls.n_labels = n_labels;
+
+	status = app_pipeline_routing_add_route(app,
+		params->p,
+		&key,
+		&route_data);
+
+	if (status != 0) {
+		printf("Command failed\n");
+		return;
+	}
+}
+
+static cmdline_parse_token_string_t cmd_route_add6_p_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add6_result, p_string,
+	"p");
+
+static cmdline_parse_token_num_t cmd_route_add6_p =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add6_result, p, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add6_route_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add6_result, route_string,
+	"route");
+
+static cmdline_parse_token_string_t cmd_route_add6_add_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add6_result, add_string,
+	"add");
+
+static cmdline_parse_token_ipaddr_t cmd_route_add6_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add6_result, ip);
+
+static cmdline_parse_token_num_t cmd_route_add6_depth =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add6_result, depth, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add6_port_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add6_result, port_string,
+	"port");
+
+static cmdline_parse_token_num_t cmd_route_add6_port =
+	TOKEN_NUM_INITIALIZER(struct cmd_route_add6_result, port, UINT32);
+
+static cmdline_parse_token_string_t cmd_route_add6_ether_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add6_result, ether_string,
+	"ether");
 
-static cmdline_parse_token_num_t cmd_route_add_depth =
-	TOKEN_NUM_INITIALIZER(struct cmd_route_add_result, depth, UINT32);
+static cmdline_parse_token_ipaddr_t cmd_route_add6_nh_ip =
+	TOKEN_IPV4_INITIALIZER(struct cmd_route_add6_result, nh_ip);
 
-static cmdline_parse_token_num_t cmd_route_add_port =
-	TOKEN_NUM_INITIALIZER(struct cmd_route_add_result, port, UINT32);
+static cmdline_parse_token_string_t cmd_route_add6_mpls_string =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add6_result, mpls_string,
+	"mpls");
 
-static cmdline_parse_token_ipaddr_t cmd_route_add_nh_ip =
-	TOKEN_IPV4_INITIALIZER(struct cmd_route_add_result, nh_ip);
+static cmdline_parse_token_string_t cmd_route_add6_mpls_labels =
+	TOKEN_STRING_INITIALIZER(struct cmd_route_add6_result, mpls_labels,
+	NULL);
 
-static cmdline_parse_inst_t cmd_route_add = {
-	.f = cmd_route_add_parsed,
+static cmdline_parse_inst_t cmd_route_add6 = {
+	.f = cmd_route_add6_parsed,
 	.data = NULL,
-	.help_str = "Route add",
+	.help_str = "Route add (mpls = yes, arp = yes)",
 	.tokens = {
-		(void *)&cmd_route_add_p_string,
-		(void *)&cmd_route_add_p,
-		(void *)&cmd_route_add_route_string,
-		(void *)&cmd_route_add_add_string,
-		(void *)&cmd_route_add_ip,
-		(void *)&cmd_route_add_depth,
-		(void *)&cmd_route_add_port,
-		(void *)&cmd_route_add_nh_ip,
+		(void *)&cmd_route_add6_p_string,
+		(void *)&cmd_route_add6_p,
+		(void *)&cmd_route_add6_route_string,
+		(void *)&cmd_route_add6_add_string,
+		(void *)&cmd_route_add6_ip,
+		(void *)&cmd_route_add6_depth,
+		(void *)&cmd_route_add6_port_string,
+		(void *)&cmd_route_add6_port,
+		(void *)&cmd_route_add6_ether_string,
+		(void *)&cmd_route_add6_nh_ip,
+		(void *)&cmd_route_add6_mpls_string,
+		(void *)&cmd_route_add6_mpls_labels,
 		NULL,
 	},
 };
@@ -1519,7 +2207,12 @@ static cmdline_parse_inst_t cmd_arp_ls = {
 };
 
 static cmdline_parse_ctx_t pipeline_cmds[] = {
-	(cmdline_parse_inst_t *)&cmd_route_add,
+	(cmdline_parse_inst_t *)&cmd_route_add1,
+	(cmdline_parse_inst_t *)&cmd_route_add2,
+	(cmdline_parse_inst_t *)&cmd_route_add3,
+	(cmdline_parse_inst_t *)&cmd_route_add4,
+	(cmdline_parse_inst_t *)&cmd_route_add5,
+	(cmdline_parse_inst_t *)&cmd_route_add6,
 	(cmdline_parse_inst_t *)&cmd_route_del,
 	(cmdline_parse_inst_t *)&cmd_route_add_default,
 	(cmdline_parse_inst_t *)&cmd_route_del_default,
diff --git a/examples/ip_pipeline/pipeline/pipeline_routing.h b/examples/ip_pipeline/pipeline/pipeline_routing.h
index e1016f9..fa41642 100644
--- a/examples/ip_pipeline/pipeline/pipeline_routing.h
+++ b/examples/ip_pipeline/pipeline/pipeline_routing.h
@@ -41,17 +41,11 @@
  * Route
  */
 
-struct app_pipeline_routing_route_params {
-	uint32_t flags;
-	uint32_t port_id; /* Output port ID */
-	uint32_t ip; /* IP address for the next hop (only for remote routes) */
-};
-
 int
 app_pipeline_routing_add_route(struct app_params *app,
 	uint32_t pipeline_id,
 	struct pipeline_routing_route_key *key,
-	struct app_pipeline_routing_route_params *route_params);
+	struct pipeline_routing_route_data *data);
 
 int
 app_pipeline_routing_delete_route(struct app_params *app,
diff --git a/examples/ip_pipeline/pipeline/pipeline_routing_be.c b/examples/ip_pipeline/pipeline/pipeline_routing_be.c
index 1e817dd..14e6ce8 100644
--- a/examples/ip_pipeline/pipeline/pipeline_routing_be.c
+++ b/examples/ip_pipeline/pipeline/pipeline_routing_be.c
@@ -49,16 +49,29 @@
 #include "pipeline_actions_common.h"
 #include "hash_func.h"
 
+#define MPLS_LABEL(label, exp, s, ttl)					\
+	(((((uint64_t) (label)) & 0xFFFFFLLU) << 12) |		\
+	((((uint64_t) (exp)) & 0x7LLU) << 9) |				\
+	((((uint64_t) (s)) & 0x1LLU) << 8) |				\
+	(((uint64_t) (ttl)) & 0xFFLU))
+
+#define RTE_SCHED_PORT_HIERARCHY(subport, pipe,		\
+	traffic_class, queue, color)					\
+	((((uint32_t) (queue)) & 0x3) |					\
+	((((uint32_t) (traffic_class)) & 0x3) << 2) |		\
+	((((uint32_t) (pipe)) & 0xFFFFF) << 4) |			\
+	((((uint32_t) (subport)) & 0x3F) << 24) |			\
+	((((uint32_t) (color)) & 0x3) << 30))
+
 struct pipeline_routing {
 	struct pipeline p;
+	struct pipeline_routing_params params;
 	pipeline_msg_req_handler custom_handlers[PIPELINE_ROUTING_MSG_REQS];
-
-	uint32_t n_routes;
-	uint32_t n_arp_entries;
-	uint32_t ip_da_offset;
-	uint32_t arp_key_offset;
 } __rte_cache_aligned;
 
+/*
+ * Message handlers
+ */
 static void *
 pipeline_routing_msg_req_custom_handler(struct pipeline *p, void *msg);
 
@@ -138,35 +151,152 @@ struct routing_table_entry {
 	uint32_t flags;
 	uint32_t port_id; /* Output port ID */
 	uint32_t ip; /* Next hop IP address (only valid for remote routes) */
+
+	/* ether_l2 */
+	uint16_t data_offset;
+	uint16_t ether_l2_length;
+	uint64_t slab[4];
+	uint16_t slab_offset[4];
 };
 
-static inline void
+static inline __attribute__((always_inline)) void
 pkt_work_routing(
 	struct rte_mbuf *pkt,
 	struct rte_pipeline_table_entry *table_entry,
-	void *arg)
+	void *arg,
+	int arp,
+	int qinq,
+	int qinq_sched,
+	int mpls,
+	int mpls_color_mark)
 {
+	struct pipeline_routing *p_rt = arg;
+
 	struct routing_table_entry *entry =
 		(struct routing_table_entry *) table_entry;
-	struct pipeline_routing *p_rt = arg;
+
+	struct ipv4_hdr *ip = (struct ipv4_hdr *)
+		RTE_MBUF_METADATA_UINT8_PTR(pkt, p_rt->params.ip_hdr_offset);
+
+	enum rte_meter_color pkt_color = (enum rte_meter_color)
+		RTE_MBUF_METADATA_UINT32(pkt, p_rt->params.color_offset);
 
 	struct pipeline_routing_arp_key_ipv4 *arp_key =
 		(struct pipeline_routing_arp_key_ipv4 *)
-		RTE_MBUF_METADATA_UINT8_PTR(pkt, p_rt->arp_key_offset);
-	uint32_t ip = RTE_MBUF_METADATA_UINT32(pkt, p_rt->ip_da_offset);
+		RTE_MBUF_METADATA_UINT8_PTR(pkt, p_rt->params.arp_key_offset);
+
+	uint64_t *slab0_ptr, *slab1_ptr, *slab2_ptr, *slab3_ptr;
+	uint32_t ip_da, nh_ip, port_id, sched;
+	uint16_t total_length, data_offset, ether_l2_length;
+
+	/* Read */
+	total_length = rte_bswap16(ip->total_length);
+	ip_da = ip->dst_addr;
+	data_offset = entry->data_offset;
+	ether_l2_length = entry->ether_l2_length;
+	slab0_ptr = RTE_MBUF_METADATA_UINT64_PTR(pkt, entry->slab_offset[0]);
+	slab1_ptr = RTE_MBUF_METADATA_UINT64_PTR(pkt, entry->slab_offset[1]);
+	slab2_ptr = RTE_MBUF_METADATA_UINT64_PTR(pkt, entry->slab_offset[2]);
+	slab3_ptr = RTE_MBUF_METADATA_UINT64_PTR(pkt, entry->slab_offset[3]);
+
+	if (arp) {
+		port_id = entry->port_id;
+		nh_ip = entry->ip;
+		if (entry->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
+			nh_ip = ip_da;
+	}
+
+	/* Compute */
+	total_length += ether_l2_length;
+
+	if (qinq && qinq_sched) {
+		uint32_t dscp = ip->type_of_service >> 2;
+		uint32_t svlan, cvlan, tc, tc_q;
+
+		if (qinq_sched == 1) {
+			uint64_t slab_qinq = rte_bswap64(entry->slab[0]);
+
+			svlan = (slab_qinq >> 48) & 0xFFF;
+			cvlan = (slab_qinq >> 16) & 0xFFF;
+			tc = (dscp >> 2) & 0x3;
+			tc_q = dscp & 0x3;
+		} else {
+			uint32_t ip_src = rte_bswap32(ip->src_addr);
+
+			svlan = 0;
+			cvlan = (ip_src >> 16) & 0xFFF;
+			tc = (ip_src >> 2) & 0x3;
+			tc_q = ip_src & 0x3;
+		}
+
+		sched = RTE_SCHED_PORT_HIERARCHY(svlan,
+			cvlan,
+			tc,
+			tc_q,
+			e_RTE_METER_GREEN);
+	}
+
+	/* Write */
+	pkt->data_off = data_offset;
+	pkt->data_len = total_length;
+	pkt->pkt_len = total_length;
+
+	if ((qinq == 0) && (mpls == 0)) {
+		*slab0_ptr = entry->slab[0];
+
+		if (arp == 0)
+			*slab1_ptr = entry->slab[1];
+	}
+
+	if (qinq) {
+		*slab0_ptr = entry->slab[0];
+		*slab1_ptr = entry->slab[1];
+
+		if (arp == 0)
+			*slab2_ptr = entry->slab[2];
+
+		if (qinq_sched)
+			pkt->hash.sched = sched;
+	}
+
+	if (mpls) {
+		if (mpls_color_mark) {
+			uint64_t mpls_exp = rte_bswap64(
+				(MPLS_LABEL(0, pkt_color, 0, 0) << 32) |
+				MPLS_LABEL(0, pkt_color, 0, 0));
+
+			*slab0_ptr = entry->slab[0] | mpls_exp;
+			*slab1_ptr = entry->slab[1] | mpls_exp;
+			*slab2_ptr = entry->slab[2];
+		} else {
+			*slab0_ptr = entry->slab[0];
+			*slab1_ptr = entry->slab[1];
+			*slab2_ptr = entry->slab[2];
+		}
+
+		if (arp == 0)
+			*slab3_ptr = entry->slab[3];
+	}
 
-	arp_key->port_id = entry->port_id;
-	arp_key->ip = entry->ip;
-	if (entry->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
-		arp_key->ip = ip;
+	if (arp) {
+		arp_key->port_id = port_id;
+		arp_key->ip = nh_ip;
+	}
 }
 
-static inline void
+static inline __attribute__((always_inline)) void
 pkt4_work_routing(
 	struct rte_mbuf **pkts,
 	struct rte_pipeline_table_entry **table_entries,
-	void *arg)
+	void *arg,
+	int arp,
+	int qinq,
+	int qinq_sched,
+	int mpls,
+	int mpls_color_mark)
 {
+	struct pipeline_routing *p_rt = arg;
+
 	struct routing_table_entry *entry0 =
 		(struct routing_table_entry *) table_entries[0];
 	struct routing_table_entry *entry1 =
@@ -175,51 +305,692 @@ pkt4_work_routing(
 		(struct routing_table_entry *) table_entries[2];
 	struct routing_table_entry *entry3 =
 		(struct routing_table_entry *) table_entries[3];
-	struct pipeline_routing *p_rt = arg;
+
+	struct ipv4_hdr *ip0 = (struct ipv4_hdr *)
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[0],
+			p_rt->params.ip_hdr_offset);
+	struct ipv4_hdr *ip1 = (struct ipv4_hdr *)
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[1],
+			p_rt->params.ip_hdr_offset);
+	struct ipv4_hdr *ip2 = (struct ipv4_hdr *)
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[2],
+			p_rt->params.ip_hdr_offset);
+	struct ipv4_hdr *ip3 = (struct ipv4_hdr *)
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[3],
+			p_rt->params.ip_hdr_offset);
+
+	enum rte_meter_color pkt0_color = (enum rte_meter_color)
+		RTE_MBUF_METADATA_UINT32(pkts[0], p_rt->params.color_offset);
+	enum rte_meter_color pkt1_color = (enum rte_meter_color)
+		RTE_MBUF_METADATA_UINT32(pkts[1], p_rt->params.color_offset);
+	enum rte_meter_color pkt2_color = (enum rte_meter_color)
+		RTE_MBUF_METADATA_UINT32(pkts[2], p_rt->params.color_offset);
+	enum rte_meter_color pkt3_color = (enum rte_meter_color)
+		RTE_MBUF_METADATA_UINT32(pkts[3], p_rt->params.color_offset);
 
 	struct pipeline_routing_arp_key_ipv4 *arp_key0 =
 		(struct pipeline_routing_arp_key_ipv4 *)
-		RTE_MBUF_METADATA_UINT8_PTR(pkts[0], p_rt->arp_key_offset);
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[0],
+			p_rt->params.arp_key_offset);
 	struct pipeline_routing_arp_key_ipv4 *arp_key1 =
 		(struct pipeline_routing_arp_key_ipv4 *)
-		RTE_MBUF_METADATA_UINT8_PTR(pkts[1], p_rt->arp_key_offset);
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[1],
+			p_rt->params.arp_key_offset);
 	struct pipeline_routing_arp_key_ipv4 *arp_key2 =
 		(struct pipeline_routing_arp_key_ipv4 *)
-		RTE_MBUF_METADATA_UINT8_PTR(pkts[2], p_rt->arp_key_offset);
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[2],
+			p_rt->params.arp_key_offset);
 	struct pipeline_routing_arp_key_ipv4 *arp_key3 =
 		(struct pipeline_routing_arp_key_ipv4 *)
-		RTE_MBUF_METADATA_UINT8_PTR(pkts[3], p_rt->arp_key_offset);
+		RTE_MBUF_METADATA_UINT8_PTR(pkts[3],
+			p_rt->params.arp_key_offset);
+
+	uint64_t *slab0_ptr0, *slab1_ptr0, *slab2_ptr0, *slab3_ptr0;
+	uint64_t *slab0_ptr1, *slab1_ptr1, *slab2_ptr1, *slab3_ptr1;
+	uint64_t *slab0_ptr2, *slab1_ptr2, *slab2_ptr2, *slab3_ptr2;
+	uint64_t *slab0_ptr3, *slab1_ptr3, *slab2_ptr3, *slab3_ptr3;
+
+	uint32_t ip_da0, nh_ip0, port_id0, sched0;
+	uint32_t ip_da1, nh_ip1, port_id1, sched1;
+	uint32_t ip_da2, nh_ip2, port_id2, sched2;
+	uint32_t ip_da3, nh_ip3, port_id3, sched3;
+
+	uint16_t total_length0, data_offset0, ether_l2_length0;
+	uint16_t total_length1, data_offset1, ether_l2_length1;
+	uint16_t total_length2, data_offset2, ether_l2_length2;
+	uint16_t total_length3, data_offset3, ether_l2_length3;
+
+	/* Read */
+	total_length0 = rte_bswap16(ip0->total_length);
+	total_length1 = rte_bswap16(ip1->total_length);
+	total_length2 = rte_bswap16(ip2->total_length);
+	total_length3 = rte_bswap16(ip3->total_length);
+
+	ip_da0 = ip0->dst_addr;
+	ip_da1 = ip1->dst_addr;
+	ip_da2 = ip2->dst_addr;
+	ip_da3 = ip3->dst_addr;
+
+	data_offset0 = entry0->data_offset;
+	data_offset1 = entry1->data_offset;
+	data_offset2 = entry2->data_offset;
+	data_offset3 = entry3->data_offset;
+
+	ether_l2_length0 = entry0->ether_l2_length;
+	ether_l2_length1 = entry1->ether_l2_length;
+	ether_l2_length2 = entry2->ether_l2_length;
+	ether_l2_length3 = entry3->ether_l2_length;
+
+	slab0_ptr0 = RTE_MBUF_METADATA_UINT64_PTR(pkts[0],
+		entry0->slab_offset[0]);
+	slab1_ptr0 = RTE_MBUF_METADATA_UINT64_PTR(pkts[0],
+		entry0->slab_offset[1]);
+	slab2_ptr0 = RTE_MBUF_METADATA_UINT64_PTR(pkts[0],
+		entry0->slab_offset[2]);
+	slab3_ptr0 = RTE_MBUF_METADATA_UINT64_PTR(pkts[0],
+		entry0->slab_offset[3]);
+
+	slab0_ptr1 = RTE_MBUF_METADATA_UINT64_PTR(pkts[1],
+		entry1->slab_offset[0]);
+	slab1_ptr1 = RTE_MBUF_METADATA_UINT64_PTR(pkts[1],
+		entry1->slab_offset[1]);
+	slab2_ptr1 = RTE_MBUF_METADATA_UINT64_PTR(pkts[1],
+		entry1->slab_offset[2]);
+	slab3_ptr1 = RTE_MBUF_METADATA_UINT64_PTR(pkts[1],
+		entry1->slab_offset[3]);
+
+	slab0_ptr2 = RTE_MBUF_METADATA_UINT64_PTR(pkts[2],
+		entry2->slab_offset[0]);
+	slab1_ptr2 = RTE_MBUF_METADATA_UINT64_PTR(pkts[2],
+		entry2->slab_offset[1]);
+	slab2_ptr2 = RTE_MBUF_METADATA_UINT64_PTR(pkts[2],
+		entry2->slab_offset[2]);
+	slab3_ptr2 = RTE_MBUF_METADATA_UINT64_PTR(pkts[2],
+		entry2->slab_offset[3]);
+
+	slab0_ptr3 = RTE_MBUF_METADATA_UINT64_PTR(pkts[3],
+		entry3->slab_offset[0]);
+	slab1_ptr3 = RTE_MBUF_METADATA_UINT64_PTR(pkts[3],
+		entry3->slab_offset[1]);
+	slab2_ptr3 = RTE_MBUF_METADATA_UINT64_PTR(pkts[3],
+		entry3->slab_offset[2]);
+	slab3_ptr3 = RTE_MBUF_METADATA_UINT64_PTR(pkts[3],
+		entry3->slab_offset[3]);
+
+	if (arp) {
+		port_id0 = entry0->port_id;
+		nh_ip0 = entry0->ip;
+		if (entry0->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
+			nh_ip0 = ip_da0;
+
+		port_id1 = entry1->port_id;
+		nh_ip1 = entry1->ip;
+		if (entry1->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
+			nh_ip1 = ip_da1;
+
+		port_id2 = entry2->port_id;
+		nh_ip2 = entry2->ip;
+		if (entry2->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
+			nh_ip2 = ip_da2;
+
+		port_id3 = entry3->port_id;
+		nh_ip3 = entry3->ip;
+		if (entry3->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
+			nh_ip3 = ip_da3;
+	}
+
+	/* Compute */
+	total_length0 += ether_l2_length0;
+	total_length1 += ether_l2_length1;
+	total_length2 += ether_l2_length2;
+	total_length3 += ether_l2_length3;
+
+	if (qinq && qinq_sched) {
+		uint32_t dscp0 = ip0->type_of_service >> 2;
+		uint32_t dscp1 = ip1->type_of_service >> 2;
+		uint32_t dscp2 = ip2->type_of_service >> 2;
+		uint32_t dscp3 = ip3->type_of_service >> 2;
+		uint32_t svlan0, cvlan0, tc0, tc_q0;
+		uint32_t svlan1, cvlan1, tc1, tc_q1;
+		uint32_t svlan2, cvlan2, tc2, tc_q2;
+		uint32_t svlan3, cvlan3, tc3, tc_q3;
+
+		if (qinq_sched == 1) {
+			uint64_t slab_qinq0 = rte_bswap64(entry0->slab[0]);
+			uint64_t slab_qinq1 = rte_bswap64(entry1->slab[0]);
+			uint64_t slab_qinq2 = rte_bswap64(entry2->slab[0]);
+			uint64_t slab_qinq3 = rte_bswap64(entry3->slab[0]);
+
+			svlan0 = (slab_qinq0 >> 48) & 0xFFF;
+			svlan1 = (slab_qinq1 >> 48) & 0xFFF;
+			svlan2 = (slab_qinq2 >> 48) & 0xFFF;
+			svlan3 = (slab_qinq3 >> 48) & 0xFFF;
+
+			cvlan0 = (slab_qinq0 >> 16) & 0xFFF;
+			cvlan1 = (slab_qinq1 >> 16) & 0xFFF;
+			cvlan2 = (slab_qinq2 >> 16) & 0xFFF;
+			cvlan3 = (slab_qinq3 >> 16) & 0xFFF;
+
+			tc0 = (dscp0 >> 2) & 0x3;
+			tc1 = (dscp1 >> 2) & 0x3;
+			tc2 = (dscp2 >> 2) & 0x3;
+			tc3 = (dscp3 >> 2) & 0x3;
+
+			tc_q0 = dscp0 & 0x3;
+			tc_q1 = dscp1 & 0x3;
+			tc_q2 = dscp2 & 0x3;
+			tc_q3 = dscp3 & 0x3;
+		} else {
+			uint32_t ip_src0 = rte_bswap32(ip0->src_addr);
+			uint32_t ip_src1 = rte_bswap32(ip1->src_addr);
+			uint32_t ip_src2 = rte_bswap32(ip2->src_addr);
+			uint32_t ip_src3 = rte_bswap32(ip3->src_addr);
+
+			svlan0 = 0;
+			svlan1 = 0;
+			svlan2 = 0;
+			svlan3 = 0;
+
+			cvlan0 = (ip_src0 >> 16) & 0xFFF;
+			cvlan1 = (ip_src1 >> 16) & 0xFFF;
+			cvlan2 = (ip_src2 >> 16) & 0xFFF;
+			cvlan3 = (ip_src3 >> 16) & 0xFFF;
+
+			tc0 = (ip_src0 >> 2) & 0x3;
+			tc1 = (ip_src1 >> 2) & 0x3;
+			tc2 = (ip_src2 >> 2) & 0x3;
+			tc3 = (ip_src3 >> 2) & 0x3;
+
+			tc_q0 = ip_src0 & 0x3;
+			tc_q1 = ip_src1 & 0x3;
+			tc_q2 = ip_src2 & 0x3;
+			tc_q3 = ip_src3 & 0x3;
+		}
+
+		sched0 = RTE_SCHED_PORT_HIERARCHY(svlan0,
+			cvlan0,
+			tc0,
+			tc_q0,
+			e_RTE_METER_GREEN);
+		sched1 = RTE_SCHED_PORT_HIERARCHY(svlan1,
+			cvlan1,
+			tc1,
+			tc_q1,
+			e_RTE_METER_GREEN);
+		sched2 = RTE_SCHED_PORT_HIERARCHY(svlan2,
+			cvlan2,
+			tc2,
+			tc_q2,
+			e_RTE_METER_GREEN);
+		sched3 = RTE_SCHED_PORT_HIERARCHY(svlan3,
+			cvlan3,
+			tc3,
+			tc_q3,
+			e_RTE_METER_GREEN);
+	}
+
+	/* Write */
+	pkts[0]->data_off = data_offset0;
+	pkts[1]->data_off = data_offset1;
+	pkts[2]->data_off = data_offset2;
+	pkts[3]->data_off = data_offset3;
+
+	pkts[0]->data_len = total_length0;
+	pkts[1]->data_len = total_length1;
+	pkts[2]->data_len = total_length2;
+	pkts[3]->data_len = total_length3;
+
+	pkts[0]->pkt_len = total_length0;
+	pkts[1]->pkt_len = total_length1;
+	pkts[2]->pkt_len = total_length2;
+	pkts[3]->pkt_len = total_length3;
+
+	if ((qinq == 0) && (mpls == 0)) {
+		*slab0_ptr0 = entry0->slab[0];
+		*slab0_ptr1 = entry1->slab[0];
+		*slab0_ptr2 = entry2->slab[0];
+		*slab0_ptr3 = entry3->slab[0];
+
+		if (arp == 0) {
+			*slab1_ptr0 = entry0->slab[1];
+			*slab1_ptr1 = entry1->slab[1];
+			*slab1_ptr2 = entry2->slab[1];
+			*slab1_ptr3 = entry3->slab[1];
+		}
+	}
+
+	if (qinq) {
+		*slab0_ptr0 = entry0->slab[0];
+		*slab0_ptr1 = entry1->slab[0];
+		*slab0_ptr2 = entry2->slab[0];
+		*slab0_ptr3 = entry3->slab[0];
+
+		*slab1_ptr0 = entry0->slab[1];
+		*slab1_ptr1 = entry1->slab[1];
+		*slab1_ptr2 = entry2->slab[1];
+		*slab1_ptr3 = entry3->slab[1];
+
+		if (arp == 0) {
+			*slab2_ptr0 = entry0->slab[2];
+			*slab2_ptr1 = entry1->slab[2];
+			*slab2_ptr2 = entry2->slab[2];
+			*slab2_ptr3 = entry3->slab[2];
+		}
+
+		if (qinq_sched) {
+			pkts[0]->hash.sched = sched0;
+			pkts[1]->hash.sched = sched1;
+			pkts[2]->hash.sched = sched2;
+			pkts[3]->hash.sched = sched3;
+		}
+	}
+
+	if (mpls) {
+		if (mpls_color_mark) {
+			uint64_t mpls_exp0 = rte_bswap64(
+				(MPLS_LABEL(0, pkt0_color, 0, 0) << 32) |
+				MPLS_LABEL(0, pkt0_color, 0, 0));
+			uint64_t mpls_exp1 = rte_bswap64(
+				(MPLS_LABEL(0, pkt1_color, 0, 0) << 32) |
+				MPLS_LABEL(0, pkt1_color, 0, 0));
+			uint64_t mpls_exp2 = rte_bswap64(
+				(MPLS_LABEL(0, pkt2_color, 0, 0) << 32) |
+				MPLS_LABEL(0, pkt2_color, 0, 0));
+			uint64_t mpls_exp3 = rte_bswap64(
+				(MPLS_LABEL(0, pkt3_color, 0, 0) << 32) |
+				MPLS_LABEL(0, pkt3_color, 0, 0));
+
+			*slab0_ptr0 = entry0->slab[0] | mpls_exp0;
+			*slab0_ptr1 = entry1->slab[0] | mpls_exp1;
+			*slab0_ptr2 = entry2->slab[0] | mpls_exp2;
+			*slab0_ptr3 = entry3->slab[0] | mpls_exp3;
+
+			*slab1_ptr0 = entry0->slab[1] | mpls_exp0;
+			*slab1_ptr1 = entry1->slab[1] | mpls_exp1;
+			*slab1_ptr2 = entry2->slab[1] | mpls_exp2;
+			*slab1_ptr3 = entry3->slab[1] | mpls_exp3;
+
+			*slab2_ptr0 = entry0->slab[2];
+			*slab2_ptr1 = entry1->slab[2];
+			*slab2_ptr2 = entry2->slab[2];
+			*slab2_ptr3 = entry3->slab[2];
+		} else {
+			*slab0_ptr0 = entry0->slab[0];
+			*slab0_ptr1 = entry1->slab[0];
+			*slab0_ptr2 = entry2->slab[0];
+			*slab0_ptr3 = entry3->slab[0];
+
+			*slab1_ptr0 = entry0->slab[1];
+			*slab1_ptr1 = entry1->slab[1];
+			*slab1_ptr2 = entry2->slab[1];
+			*slab1_ptr3 = entry3->slab[1];
+
+			*slab2_ptr0 = entry0->slab[2];
+			*slab2_ptr1 = entry1->slab[2];
+			*slab2_ptr2 = entry2->slab[2];
+			*slab2_ptr3 = entry3->slab[2];
+		}
+
+		if (arp == 0) {
+			*slab3_ptr0 = entry0->slab[3];
+			*slab3_ptr1 = entry1->slab[3];
+			*slab3_ptr2 = entry2->slab[3];
+			*slab3_ptr3 = entry3->slab[3];
+		}
+	}
+
+	if (arp) {
+		arp_key0->port_id = port_id0;
+		arp_key1->port_id = port_id1;
+		arp_key2->port_id = port_id2;
+		arp_key3->port_id = port_id3;
+
+		arp_key0->ip = nh_ip0;
+		arp_key1->ip = nh_ip1;
+		arp_key2->ip = nh_ip2;
+		arp_key3->ip = nh_ip3;
+	}
+}
+
+static inline void
+pkt_work_routing_ether_arp0(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 0, 0, 0, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_arp1(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 1, 0, 0, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_qinq_sched0_arp0(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 0, 1, 0, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_qinq_sched0_arp1(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 1, 1, 0, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_qinq_sched1_arp0(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 0, 1, 1, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_qinq_sched1_arp1(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 1, 1, 1, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_qinq_sched2_arp0(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 0, 1, 2, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_qinq_sched2_arp1(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 1, 1, 2, 0, 0);
+}
+
+static inline void
+pkt_work_routing_ether_mpls_color0_arp0(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 0, 0, 0, 1, 0);
+}
+
+static inline void
+pkt_work_routing_ether_mpls_color0_arp1(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 1, 0, 0, 1, 0);
+}
+
+static inline void
+pkt_work_routing_ether_mpls_color1_arp0(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 0, 0, 0, 1, 1);
+}
+
+static inline void
+pkt_work_routing_ether_mpls_color1_arp1(
+	struct rte_mbuf *pkt,
+	struct rte_pipeline_table_entry *table_entry,
+	void *arg)
+{
+	pkt_work_routing(pkt, table_entry, arg, 1, 0, 0, 1, 1);
+}
+
+static inline void
+pkt4_work_routing_ether_arp0(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 0, 0, 0, 0, 0);
+}
+
+static inline void
+pkt4_work_routing_ether_arp1(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 1, 0, 0, 0, 0);
+}
 
-	uint32_t ip0 = RTE_MBUF_METADATA_UINT32(pkts[0], p_rt->ip_da_offset);
-	uint32_t ip1 = RTE_MBUF_METADATA_UINT32(pkts[1], p_rt->ip_da_offset);
-	uint32_t ip2 = RTE_MBUF_METADATA_UINT32(pkts[2], p_rt->ip_da_offset);
-	uint32_t ip3 = RTE_MBUF_METADATA_UINT32(pkts[3], p_rt->ip_da_offset);
+static inline void
+pkt4_work_routing_ether_qinq_sched0_arp0(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 0, 1, 0, 0, 0);
+}
+
+static inline void
+pkt4_work_routing_ether_qinq_sched0_arp1(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 1, 1, 0, 0, 0);
+}
+
+static inline void
+pkt4_work_routing_ether_qinq_sched1_arp0(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 0, 1, 1, 0, 0);
+}
 
-	arp_key0->port_id = entry0->port_id;
-	arp_key1->port_id = entry1->port_id;
-	arp_key2->port_id = entry2->port_id;
-	arp_key3->port_id = entry3->port_id;
+static inline void
+pkt4_work_routing_ether_qinq_sched1_arp1(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 1, 1, 1, 0, 0);
+}
+
+static inline void
+pkt4_work_routing_ether_qinq_sched2_arp0(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 0, 1, 2, 0, 0);
+}
+
+static inline void
+pkt4_work_routing_ether_qinq_sched2_arp1(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 1, 1, 2, 0, 0);
+}
 
-	arp_key0->ip = entry0->ip;
-	if (entry0->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
-		arp_key0->ip = ip0;
+static inline void
+pkt4_work_routing_ether_mpls_color0_arp0(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 0, 0, 0, 1, 0);
+}
 
-	arp_key1->ip = entry1->ip;
-	if (entry1->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
-		arp_key1->ip = ip1;
+static inline void
+pkt4_work_routing_ether_mpls_color0_arp1(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 1, 0, 0, 1, 0);
+}
 
-	arp_key2->ip = entry2->ip;
-	if (entry2->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
-		arp_key2->ip = ip2;
+static inline void
+pkt4_work_routing_ether_mpls_color1_arp0(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 0, 0, 0, 1, 1);
+}
 
-	arp_key3->ip = entry3->ip;
-	if (entry3->flags & PIPELINE_ROUTING_ROUTE_LOCAL)
-		arp_key3->ip = ip3;
+static inline void
+pkt4_work_routing_ether_mpls_color1_arp1(
+	struct rte_mbuf **pkts,
+	struct rte_pipeline_table_entry **table_entries,
+	void *arg)
+{
+	pkt4_work_routing(pkts, table_entries, arg, 1, 0, 0, 1, 1);
 }
 
-PIPELINE_TABLE_AH_HIT(routing_table_ah_hit,
-	pkt_work_routing,
-	pkt4_work_routing);
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_arp0,
+	pkt_work_routing_ether_arp0,
+	pkt4_work_routing_ether_arp0);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_arp1,
+	pkt_work_routing_ether_arp1,
+	pkt4_work_routing_ether_arp1);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_qinq_sched0_arp0,
+	pkt_work_routing_ether_qinq_sched0_arp0,
+	pkt4_work_routing_ether_qinq_sched0_arp0);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_qinq_sched0_arp1,
+	pkt_work_routing_ether_qinq_sched0_arp1,
+	pkt4_work_routing_ether_qinq_sched0_arp1);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_qinq_sched1_arp0,
+	pkt_work_routing_ether_qinq_sched1_arp0,
+	pkt4_work_routing_ether_qinq_sched1_arp0);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_qinq_sched1_arp1,
+	pkt_work_routing_ether_qinq_sched1_arp1,
+	pkt4_work_routing_ether_qinq_sched1_arp1);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_qinq_sched2_arp0,
+	pkt_work_routing_ether_qinq_sched2_arp0,
+	pkt4_work_routing_ether_qinq_sched2_arp0);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_qinq_sched2_arp1,
+	pkt_work_routing_ether_qinq_sched2_arp1,
+	pkt4_work_routing_ether_qinq_sched2_arp1);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_mpls_color0_arp0,
+	pkt_work_routing_ether_mpls_color0_arp0,
+	pkt4_work_routing_ether_mpls_color0_arp0);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_mpls_color0_arp1,
+	pkt_work_routing_ether_mpls_color0_arp1,
+	pkt4_work_routing_ether_mpls_color0_arp1);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_mpls_color1_arp0,
+	pkt_work_routing_ether_mpls_color1_arp0,
+	pkt4_work_routing_ether_mpls_color1_arp0);
+
+PIPELINE_TABLE_AH_HIT(routing_table_ah_hit_ether_mpls_color1_arp1,
+	pkt_work_routing_ether_mpls_color1_arp1,
+	pkt4_work_routing_ether_mpls_color1_arp1);
+
+static rte_pipeline_table_action_handler_hit
+get_routing_table_ah_hit(struct pipeline_routing *p)
+{
+	if ((p->params.qinq == 0) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 0))
+		return routing_table_ah_hit_ether_arp0;
+
+	if ((p->params.qinq == 0) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 1))
+		return routing_table_ah_hit_ether_arp1;
+
+	if ((p->params.qinq == 1) &&
+		(p->params.qinq_sched == 0) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 0))
+		return routing_table_ah_hit_ether_qinq_sched0_arp0;
+
+	if ((p->params.qinq == 1) &&
+		(p->params.qinq_sched == 0) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 1))
+		return routing_table_ah_hit_ether_qinq_sched0_arp1;
+
+	if ((p->params.qinq == 1) &&
+		(p->params.qinq_sched == 1) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 0))
+		return routing_table_ah_hit_ether_qinq_sched1_arp0;
+
+	if ((p->params.qinq == 1) &&
+		(p->params.qinq_sched == 1) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 1))
+		return routing_table_ah_hit_ether_qinq_sched1_arp1;
+
+	if ((p->params.qinq == 1) &&
+		(p->params.qinq_sched == 2) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 0))
+		return routing_table_ah_hit_ether_qinq_sched2_arp0;
+
+	if ((p->params.qinq == 1) &&
+		(p->params.qinq_sched == 2) &&
+		(p->params.mpls == 0) &&
+		(p->params.arp == 1))
+		return routing_table_ah_hit_ether_qinq_sched2_arp1;
+
+	if ((p->params.qinq == 0) &&
+		(p->params.mpls == 1) &&
+		(p->params.mpls_color_mark == 0) &&
+		(p->params.arp == 0))
+		return routing_table_ah_hit_ether_mpls_color0_arp0;
+
+	if ((p->params.qinq == 0) &&
+		(p->params.mpls == 1) &&
+		(p->params.mpls_color_mark == 0) &&
+		(p->params.arp == 1))
+		return routing_table_ah_hit_ether_mpls_color0_arp1;
+
+	if ((p->params.qinq == 0) &&
+		(p->params.mpls == 1) &&
+		(p->params.mpls_color_mark == 1) &&
+		(p->params.arp == 0))
+		return routing_table_ah_hit_ether_mpls_color1_arp0;
+
+	if ((p->params.qinq == 0) &&
+		(p->params.mpls == 1) &&
+		(p->params.mpls_color_mark == 1) &&
+		(p->params.arp == 1))
+		return routing_table_ah_hit_ether_mpls_color1_arp1;
+
+	return NULL;
+}
 
 /*
  * ARP table
@@ -229,6 +1000,9 @@ struct arp_table_entry {
 	uint64_t macaddr;
 };
 
+/**
+ * ARP table AH
+ */
 static inline void
 pkt_work_arp(
 	struct rte_mbuf *pkt,
@@ -237,20 +1011,15 @@ pkt_work_arp(
 {
 	struct arp_table_entry *entry = (struct arp_table_entry *) table_entry;
 
-	/* Read: pkt buffer - mbuf */
-	uint8_t *raw = rte_pktmbuf_mtod(pkt, uint8_t *);
+	/* Read */
+	uint64_t macaddr_dst = entry->macaddr;
+	uint64_t *slab_ptr = (uint64_t *) ((char *) pkt->buf_addr +
+		(pkt->data_off - 2));
 
-	/* Read: table entry */
-	uint64_t mac_addr_dst = entry->macaddr;
-	uint64_t mac_addr_src = 0;
+	/* Compute */
 
-	/* Compute: Ethernet header */
-	uint64_t slab0 = mac_addr_dst | (mac_addr_src << 48);
-	uint32_t slab1 = mac_addr_src >> 16;
-
-	/* Write: pkt buffer - pkt headers */
-	*((uint64_t *) raw) = slab0;
-	*((uint32_t *) (raw + 8)) = slab1;
+	/* Write */
+	*slab_ptr = macaddr_dst;
 }
 
 static inline void
@@ -268,59 +1037,56 @@ pkt4_work_arp(
 	struct arp_table_entry *entry3 =
 		(struct arp_table_entry *) table_entries[3];
 
-	/* Read: pkt buffer - mbuf */
-	uint8_t *raw0 = rte_pktmbuf_mtod(pkts[0], uint8_t *);
-	uint8_t *raw1 = rte_pktmbuf_mtod(pkts[1], uint8_t *);
-	uint8_t *raw2 = rte_pktmbuf_mtod(pkts[2], uint8_t *);
-	uint8_t *raw3 = rte_pktmbuf_mtod(pkts[3], uint8_t *);
-
-	/* Read: table entry */
-	uint64_t mac_addr_dst0 = entry0->macaddr;
-	uint64_t mac_addr_dst1 = entry1->macaddr;
-	uint64_t mac_addr_dst2 = entry2->macaddr;
-	uint64_t mac_addr_dst3 = entry3->macaddr;
-
-	uint64_t mac_addr_src0 = 0;
-	uint64_t mac_addr_src1 = 0;
-	uint64_t mac_addr_src2 = 0;
-	uint64_t mac_addr_src3 = 0;
-
-	/* Compute: Ethernet header */
-	uint64_t pkt0_slab0 = mac_addr_dst0 | (mac_addr_src0 << 48);
-	uint64_t pkt1_slab0 = mac_addr_dst1 | (mac_addr_src1 << 48);
-	uint64_t pkt2_slab0 = mac_addr_dst2 | (mac_addr_src2 << 48);
-	uint64_t pkt3_slab0 = mac_addr_dst3 | (mac_addr_src3 << 48);
-
-	uint32_t pkt0_slab1 = mac_addr_src0 >> 16;
-	uint32_t pkt1_slab1 = mac_addr_src1 >> 16;
-	uint32_t pkt2_slab1 = mac_addr_src2 >> 16;
-	uint32_t pkt3_slab1 = mac_addr_src3 >> 16;
-
-	/* Write: pkt buffer - pkt headers */
-	*((uint64_t *) raw0) = pkt0_slab0;
-	*((uint32_t *) (raw0 + 8)) = pkt0_slab1;
-	*((uint64_t *) raw1) = pkt1_slab0;
-	*((uint32_t *) (raw1 + 8)) = pkt1_slab1;
-	*((uint64_t *) raw2) = pkt2_slab0;
-	*((uint32_t *) (raw2 + 8)) = pkt2_slab1;
-	*((uint64_t *) raw3) = pkt3_slab0;
-	*((uint32_t *) (raw3 + 8)) = pkt3_slab1;
+	/* Read */
+	uint64_t macaddr_dst0 = entry0->macaddr;
+	uint64_t macaddr_dst1 = entry1->macaddr;
+	uint64_t macaddr_dst2 = entry2->macaddr;
+	uint64_t macaddr_dst3 = entry3->macaddr;
+
+	uint64_t *slab_ptr0 = (uint64_t *) ((char *) pkts[0]->buf_addr +
+		(pkts[0]->data_off - 2));
+	uint64_t *slab_ptr1 = (uint64_t *) ((char *) pkts[1]->buf_addr +
+		(pkts[1]->data_off - 2));
+	uint64_t *slab_ptr2 = (uint64_t *) ((char *) pkts[2]->buf_addr +
+		(pkts[2]->data_off - 2));
+	uint64_t *slab_ptr3 = (uint64_t *) ((char *) pkts[3]->buf_addr +
+		(pkts[3]->data_off - 2));
+
+	/* Compute */
+
+	/* Write */
+	*slab_ptr0 = macaddr_dst0;
+	*slab_ptr1 = macaddr_dst1;
+	*slab_ptr2 = macaddr_dst2;
+	*slab_ptr3 = macaddr_dst3;
 }
 
 PIPELINE_TABLE_AH_HIT(arp_table_ah_hit,
 	pkt_work_arp,
 	pkt4_work_arp);
 
-static int
-pipeline_routing_parse_args(struct pipeline_routing *p,
+/*
+ * Argument parsing
+ */
+int
+pipeline_routing_parse_args(struct pipeline_routing_params *p,
 	struct pipeline_params *params)
 {
 	uint32_t n_routes_present = 0;
 	uint32_t n_arp_entries_present = 0;
-	uint32_t ip_da_offset_present = 0;
+	uint32_t ip_hdr_offset_present = 0;
 	uint32_t arp_key_offset_present = 0;
+	uint32_t l2_present = 0;
+	uint32_t qinq_sched_present = 0;
+	uint32_t mpls_color_mark_present = 0;
+	uint32_t color_offset_present = 0;
 	uint32_t i;
 
+	/* default values */
+	p->arp = 0;
+	p->qinq_sched = 0;
+	p->mpls_color_mark = 0;
+
 	for (i = 0; i < params->n_args; i++) {
 		char *arg_name = params->args_name[i];
 		char *arg_value = params->args_value[i];
@@ -338,12 +1104,26 @@ pipeline_routing_parse_args(struct pipeline_routing *p,
 			continue;
 		}
 
+		/* ip_hdr_offset */
+		if (strcmp(arg_name, "ip_hdr_offset") == 0) {
+			if (ip_hdr_offset_present)
+				return -1;
+			ip_hdr_offset_present = 1;
+
+			p->ip_hdr_offset = atoi(arg_value);
+			p->ip_da_offset = p->ip_hdr_offset +
+				__builtin_offsetof(struct ipv4_hdr, dst_addr);
+
+			continue;
+		}
+
 		/* n_arp_entries */
 		if (strcmp(arg_name, "n_arp_entries") == 0) {
 			if (n_arp_entries_present)
 				return -1;
 			n_arp_entries_present = 1;
 
+			p->arp = 1;
 			p->n_arp_entries = atoi(arg_value);
 			if (p->n_arp_entries == 0)
 				return -1;
@@ -351,24 +1131,84 @@ pipeline_routing_parse_args(struct pipeline_routing *p,
 			continue;
 		}
 
-		/* ip_da_offset */
-		if (strcmp(arg_name, "ip_da_offset") == 0) {
-			if (ip_da_offset_present)
+		/* arp_key_offset */
+		if (strcmp(arg_name, "arp_key_offset") == 0) {
+			if (arp_key_offset_present)
 				return -1;
-			ip_da_offset_present = 1;
+			arp_key_offset_present = 1;
 
-			p->ip_da_offset = atoi(arg_value);
+			p->arp = 1;
+			p->arp_key_offset = atoi(arg_value);
 
 			continue;
 		}
 
-		/* arp_key_offset */
-		if (strcmp(arg_name, "arp_key_offset") == 0) {
-			if (arp_key_offset_present)
+		/* l2 */
+		if (strcmp(arg_name, "l2") == 0) {
+			if (l2_present)
 				return -1;
-			arp_key_offset_present = 1;
+			l2_present = 1;
+
+			/* qinq */
+			if (strcmp(arg_value, "qinq") == 0) {
+				p->qinq = 1;
+				continue;
+			}
+
+			/* mpls */
+			if (strcmp(arg_value, "mpls") == 0) {
+				p->mpls = 1;
+				continue;
+			}
+
+			/* any other */
+			return -1;
+		}
 
-			p->arp_key_offset = atoi(arg_value);
+		/* qinq_sched */
+		if (strcmp(arg_name, "qinq_sched") == 0) {
+			if (qinq_sched_present)
+				return -1;
+
+			qinq_sched_present = 1;
+
+			if (strcmp(arg_value, "no") == 0)
+				p->qinq_sched = 0;
+			else if (strcmp(arg_value, "yes") == 0)
+				p->qinq_sched = 1;
+			else if (strcmp(arg_value, "test") == 0)
+				p->qinq_sched = 2;
+			else
+				return -1;
+
+			continue;
+		}
+
+		/* mpls_color_mark */
+		if (strcmp(arg_name, "mpls_color_mark") == 0) {
+			if (mpls_color_mark_present)
+				return -1;
+
+			mpls_color_mark_present = 1;
+
+			if (strcmp(arg_value, "no") == 0)
+				p->mpls_color_mark = 0;
+			else if (strcmp(arg_value, "yes") == 0)
+				p->mpls_color_mark = 1;
+			else
+				return -1;
+
+			continue;
+		}
+
+		/* color_offset */
+		if (strcmp(arg_name, "color_offset") == 0) {
+			if (color_offset_present)
+				return -1;
+
+			color_offset_present = 1;
+
+			p->color_offset = atoi(arg_value);
 
 			continue;
 		}
@@ -379,9 +1219,19 @@ pipeline_routing_parse_args(struct pipeline_routing *p,
 
 	/* Check that mandatory arguments are present */
 	if ((n_routes_present == 0) ||
-		(n_arp_entries_present == 0) ||
-		(ip_da_offset_present == 0) ||
-		(n_arp_entries_present && (arp_key_offset_present == 0)))
+		(ip_hdr_offset_present == 0) ||
+		(n_arp_entries_present && (arp_key_offset_present == 0)) ||
+		((n_arp_entries_present == 0) && arp_key_offset_present))
+		return -1;
+
+	/* Check relations between arguments */
+	if ((p->qinq == 0) && qinq_sched_present)
+		return -1;
+
+	if (((p->mpls == 0) && (mpls_color_mark_present ||
+		color_offset_present)) ||
+		((mpls_color_mark_present == 0) && color_offset_present) ||
+		(mpls_color_mark_present && (color_offset_present == 0)))
 		return -1;
 
 	return 0;
@@ -414,7 +1264,7 @@ pipeline_routing_init(struct pipeline_params *params,
 	PLOG(p, HIGH, "Routing");
 
 	/* Parse arguments */
-	if (pipeline_routing_parse_args(p_rt, params))
+	if (pipeline_routing_parse_args(&p_rt->params, params))
 		return NULL;
 
 	/* Pipeline */
@@ -484,15 +1334,15 @@ pipeline_routing_init(struct pipeline_params *params,
 	p->n_tables = 1;
 	{
 		struct rte_table_lpm_params table_lpm_params = {
-			.n_rules = p_rt->n_routes,
+			.n_rules = p_rt->params.n_routes,
 			.entry_unique_size = sizeof(struct routing_table_entry),
-			.offset = p_rt->ip_da_offset,
+			.offset = p_rt->params.ip_da_offset,
 		};
 
 		struct rte_pipeline_table_params table_params = {
 				.ops = &rte_table_lpm_ops,
 				.arg_create = &table_lpm_params,
-				.f_action_hit = routing_table_ah_hit,
+				.f_action_hit = get_routing_table_ah_hit(p_rt),
 				.f_action_miss = NULL,
 				.arg_ah = p_rt,
 				.action_data_size =
@@ -514,14 +1364,14 @@ pipeline_routing_init(struct pipeline_params *params,
 	}
 
 	/* ARP table configuration */
-	if (p_rt->n_arp_entries) {
+	if (p_rt->params.arp) {
 		struct rte_table_hash_key8_ext_params table_arp_params = {
-			.n_entries = p_rt->n_arp_entries,
-			.n_entries_ext = p_rt->n_arp_entries,
+			.n_entries = p_rt->params.n_arp_entries,
+			.n_entries_ext = p_rt->params.n_arp_entries,
 			.f_hash = hash_default_key8,
 			.seed = 0,
 			.signature_offset = 0, /* Unused */
-			.key_offset = p_rt->arp_key_offset,
+			.key_offset = p_rt->params.arp_key_offset,
 		};
 
 		struct rte_pipeline_table_params table_params = {
@@ -665,6 +1515,7 @@ pipeline_routing_msg_req_custom_handler(struct pipeline *p,
 void *
 pipeline_routing_msg_req_route_add_handler(struct pipeline *p, void *msg)
 {
+	struct pipeline_routing *p_rt = (struct pipeline_routing *) p;
 	struct pipeline_routing_route_add_msg_req *req = msg;
 	struct pipeline_routing_route_add_msg_rsp *rsp = msg;
 
@@ -673,26 +1524,321 @@ pipeline_routing_msg_req_route_add_handler(struct pipeline *p, void *msg)
 		.depth = req->key.key.ipv4.depth,
 	};
 
-	struct routing_table_entry entry = {
+	struct routing_table_entry entry_arp0 = {
+		.head = {
+			.action = RTE_PIPELINE_ACTION_PORT,
+			{.port_id = p->port_out_id[req->data.port_id]},
+		},
+
+		.flags = req->data.flags,
+		.port_id = req->data.port_id,
+		.ip = 0,
+		.data_offset = 0,
+		.ether_l2_length = 0,
+		.slab = {0},
+		.slab_offset = {0},
+	};
+
+	struct routing_table_entry entry_arp1 = {
 		.head = {
 			.action = RTE_PIPELINE_ACTION_TABLE,
 			{.table_id = p->table_id[1]},
 		},
 
-		.flags = req->flags,
-		.port_id = req->port_id,
-		.ip = rte_bswap32(req->ip),
+		.flags = req->data.flags,
+		.port_id = req->data.port_id,
+		.ip = rte_bswap32(req->data.ethernet.ip),
+		.data_offset = 0,
+		.ether_l2_length = 0,
+		.slab = {0},
+		.slab_offset = {0},
 	};
 
-	if (req->key.type != PIPELINE_ROUTING_ROUTE_IPV4) {
+	struct rte_pipeline_table_entry *entry = (p_rt->params.arp) ?
+		(struct rte_pipeline_table_entry *) &entry_arp1 :
+		(struct rte_pipeline_table_entry *) &entry_arp0;
+
+	if ((req->key.type != PIPELINE_ROUTING_ROUTE_IPV4) ||
+		((p_rt->params.arp == 0) &&
+		(req->data.flags & PIPELINE_ROUTING_ROUTE_ARP)) ||
+		(p_rt->params.arp &&
+		((req->data.flags & PIPELINE_ROUTING_ROUTE_ARP) == 0)) ||
+		((p_rt->params.qinq == 0) &&
+		(req->data.flags & PIPELINE_ROUTING_ROUTE_QINQ)) ||
+		(p_rt->params.qinq &&
+		((req->data.flags & PIPELINE_ROUTING_ROUTE_QINQ) == 0)) ||
+		((p_rt->params.mpls == 0) &&
+		(req->data.flags & PIPELINE_ROUTING_ROUTE_MPLS)) ||
+		(p_rt->params.mpls &&
+		((req->data.flags & PIPELINE_ROUTING_ROUTE_MPLS) == 0))) {
+
 		rsp->status = -1;
 		return rsp;
 	}
 
+	/* Ether - ARP off */
+	if ((p_rt->params.qinq == 0) &&
+		(p_rt->params.mpls == 0) &&
+		(p_rt->params.arp == 0)) {
+
+		uint64_t macaddr_src = 0x112233445566;
+		uint64_t macaddr_dst;
+		uint64_t ethertype = ETHER_TYPE_IPv4;
+
+		*((struct ether_addr *) &macaddr_dst) =
+			req->data.ethernet.macaddr;
+		macaddr_dst = rte_bswap64(macaddr_dst << 16);
+
+		entry_arp0.slab[0] =
+			rte_bswap64((macaddr_src << 16) | ethertype);
+		entry_arp0.slab_offset[0] = p_rt->params.ip_hdr_offset - 8;
+
+		entry_arp0.slab[1] = rte_bswap64(macaddr_dst);
+		entry_arp0.slab_offset[1] = p_rt->params.ip_hdr_offset - 2 * 8;
+
+		entry_arp0.data_offset = entry_arp0.slab_offset[1] + 2;
+		entry_arp0.ether_l2_length = 14;
+	}
+
+	/* Ether - ARP on */
+	if ((p_rt->params.qinq == 0) && (p_rt->params.mpls == 0) &&
+		p_rt->params.arp) {
+		uint64_t macaddr_src = 0x112233445566;
+		uint64_t ethertype = ETHER_TYPE_IPv4;
+
+		entry_arp1.slab[0] = rte_bswap64((macaddr_src << 16) |
+			ethertype);
+		entry_arp1.slab_offset[0] = p_rt->params.ip_hdr_offset - 8;
+
+		entry_arp1.data_offset = entry_arp1.slab_offset[0] - 6;
+		entry_arp1.ether_l2_length = 14;
+	}
+
+	/* Ether QinQ - ARP off */
+	if ((p_rt->params.qinq == 1) && (p_rt->params.mpls == 0) &&
+		(p_rt->params.arp == 0)) {
+		uint64_t macaddr_src = 0x112233445566;
+		uint64_t macaddr_dst;
+		uint64_t ethertype_ipv4 = ETHER_TYPE_IPv4;
+		uint64_t ethertype_vlan = 0x8100;
+		uint64_t ethertype_qinq = 0x9100;
+		uint64_t svlan = req->data.l2.qinq.svlan;
+		uint64_t cvlan = req->data.l2.qinq.cvlan;
+
+		*((struct ether_addr *) &macaddr_dst) =
+			req->data.ethernet.macaddr;
+		macaddr_dst = rte_bswap64(macaddr_dst << 16);
+
+		entry_arp0.slab[0] = rte_bswap64((svlan << 48) |
+			(ethertype_vlan << 32) |
+			(cvlan << 16) |
+			ethertype_ipv4);
+		entry_arp0.slab_offset[0] = p_rt->params.ip_hdr_offset - 8;
+
+		entry_arp0.slab[1] = rte_bswap64((macaddr_src << 16) |
+			ethertype_qinq);
+		entry_arp0.slab_offset[1] = p_rt->params.ip_hdr_offset - 2 * 8;
+
+		entry_arp0.slab[2] = rte_bswap64(macaddr_dst);
+		entry_arp0.slab_offset[2] = p_rt->params.ip_hdr_offset - 3 * 8;
+
+		entry_arp0.data_offset = entry_arp0.slab_offset[2] + 2;
+		entry_arp0.ether_l2_length = 22;
+	}
+
+	/* Ether QinQ - ARP on */
+	if ((p_rt->params.qinq == 1) && (p_rt->params.mpls == 0) &&
+		p_rt->params.arp) {
+		uint64_t macaddr_src = 0x112233445566;
+		uint64_t ethertype_ipv4 = ETHER_TYPE_IPv4;
+		uint64_t ethertype_vlan = 0x8100;
+		uint64_t ethertype_qinq = 0x9100;
+		uint64_t svlan = req->data.l2.qinq.svlan;
+		uint64_t cvlan = req->data.l2.qinq.cvlan;
+
+		entry_arp1.slab[0] = rte_bswap64((svlan << 48) |
+			(ethertype_vlan << 32) |
+			(cvlan << 16) |
+			ethertype_ipv4);
+		entry_arp1.slab_offset[0] = p_rt->params.ip_hdr_offset - 8;
+
+		entry_arp1.slab[1] = rte_bswap64((macaddr_src << 16) |
+			ethertype_qinq);
+		entry_arp1.slab_offset[1] = p_rt->params.ip_hdr_offset - 2 * 8;
+
+		entry_arp1.data_offset = entry_arp1.slab_offset[1] - 6;
+		entry_arp1.ether_l2_length = 22;
+	}
+
+	/* Ether MPLS - ARP off */
+	if ((p_rt->params.qinq == 0) &&
+		(p_rt->params.mpls == 1) &&
+		(p_rt->params.arp == 0)) {
+		uint64_t macaddr_src = 0x112233445566;
+		uint64_t macaddr_dst;
+		uint64_t ethertype_mpls = 0x8847;
+
+		uint64_t label0 = req->data.l2.mpls.labels[0];
+		uint64_t label1 = req->data.l2.mpls.labels[1];
+		uint64_t label2 = req->data.l2.mpls.labels[2];
+		uint64_t label3 = req->data.l2.mpls.labels[3];
+		uint32_t n_labels = req->data.l2.mpls.n_labels;
+
+		*((struct ether_addr *) &macaddr_dst) =
+			req->data.ethernet.macaddr;
+		macaddr_dst = rte_bswap64(macaddr_dst << 16);
+
+		switch (n_labels) {
+		case 1:
+			entry_arp0.slab[0] = 0;
+			entry_arp0.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp0.slab[1] = rte_bswap64(
+				MPLS_LABEL(label0, 0, 1, 0));
+			entry_arp0.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 8;
+			break;
+
+		case 2:
+			entry_arp0.slab[0] = 0;
+			entry_arp0.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp0.slab[1] = rte_bswap64(
+				(MPLS_LABEL(label0, 0, 0, 0) << 32) |
+				MPLS_LABEL(label1, 0, 1, 0));
+			entry_arp0.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 8;
+			break;
+
+		case 3:
+			entry_arp0.slab[0] = rte_bswap64(
+				(MPLS_LABEL(label1, 0, 0, 0) << 32) |
+				MPLS_LABEL(label2, 0, 1, 0));
+			entry_arp0.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp0.slab[1] = rte_bswap64(
+				MPLS_LABEL(label0, 0, 0, 0));
+			entry_arp0.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 2 * 8;
+			break;
+
+		case 4:
+			entry_arp0.slab[0] = rte_bswap64(
+				(MPLS_LABEL(label2, 0, 0, 0) << 32) |
+				MPLS_LABEL(label3, 0, 1, 0));
+			entry_arp0.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp0.slab[1] = rte_bswap64(
+				(MPLS_LABEL(label0, 0, 0, 0) << 32) |
+				MPLS_LABEL(label1, 0, 0, 0));
+			entry_arp0.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 2 * 8;
+			break;
+
+		default:
+			rsp->status = -1;
+			return rsp;
+		}
+
+		entry_arp0.slab[2] = rte_bswap64((macaddr_src << 16) |
+			ethertype_mpls);
+		entry_arp0.slab_offset[2] = p_rt->params.ip_hdr_offset -
+			(n_labels * 4 + 8);
+
+		entry_arp0.slab[3] = rte_bswap64(macaddr_dst);
+		entry_arp0.slab_offset[3] = p_rt->params.ip_hdr_offset -
+			(n_labels * 4 + 2 * 8);
+
+		entry_arp0.data_offset = entry_arp0.slab_offset[3] + 2;
+		entry_arp0.ether_l2_length = n_labels * 4 + 14;
+	}
+
+	/* Ether MPLS - ARP on */
+	if ((p_rt->params.qinq == 0) &&
+		(p_rt->params.mpls == 1) &&
+		p_rt->params.arp) {
+		uint64_t macaddr_src = 0x112233445566;
+		uint64_t ethertype_mpls = 0x8847;
+
+		uint64_t label0 = req->data.l2.mpls.labels[0];
+		uint64_t label1 = req->data.l2.mpls.labels[1];
+		uint64_t label2 = req->data.l2.mpls.labels[2];
+		uint64_t label3 = req->data.l2.mpls.labels[3];
+		uint32_t n_labels = req->data.l2.mpls.n_labels;
+
+		switch (n_labels) {
+		case 1:
+			entry_arp1.slab[0] = 0;
+			entry_arp1.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp1.slab[1] = rte_bswap64(
+				MPLS_LABEL(label0, 0, 1, 0));
+			entry_arp1.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 8;
+			break;
+
+		case 2:
+			entry_arp1.slab[0] = 0;
+			entry_arp1.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp1.slab[1] = rte_bswap64(
+				(MPLS_LABEL(label0, 0, 0, 0) << 32) |
+				MPLS_LABEL(label1, 0, 1, 0));
+			entry_arp1.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 8;
+			break;
+
+		case 3:
+			entry_arp1.slab[0] = rte_bswap64(
+				(MPLS_LABEL(label1, 0, 0, 0) << 32) |
+				MPLS_LABEL(label2, 0, 1, 0));
+			entry_arp1.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp1.slab[1] = rte_bswap64(
+				MPLS_LABEL(label0, 0, 0, 0));
+			entry_arp1.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 2 * 8;
+			break;
+
+		case 4:
+			entry_arp1.slab[0] = rte_bswap64(
+				(MPLS_LABEL(label2, 0, 0, 0) << 32) |
+				MPLS_LABEL(label3, 0, 1, 0));
+			entry_arp1.slab_offset[0] =
+				p_rt->params.ip_hdr_offset - 8;
+
+			entry_arp1.slab[1] = rte_bswap64(
+				(MPLS_LABEL(label0, 0, 0, 0) << 32) |
+				MPLS_LABEL(label1, 0, 0, 0));
+			entry_arp1.slab_offset[1] =
+				p_rt->params.ip_hdr_offset - 2 * 8;
+			break;
+
+		default:
+			rsp->status = -1;
+			return rsp;
+		}
+
+		entry_arp1.slab[2] = rte_bswap64((macaddr_src << 16) |
+			ethertype_mpls);
+		entry_arp1.slab_offset[2] = p_rt->params.ip_hdr_offset -
+			(n_labels * 4 + 8);
+
+		entry_arp1.data_offset = entry_arp1.slab_offset[2] - 6;
+		entry_arp1.ether_l2_length = n_labels * 4 + 14;
+	}
+
 	rsp->status = rte_pipeline_table_entry_add(p->p,
 		p->table_id[0],
 		&key,
-		(struct rte_pipeline_table_entry *) &entry,
+		entry,
 		&rsp->key_found,
 		(struct rte_pipeline_table_entry **) &rsp->entry_ptr);
 
@@ -789,6 +1935,7 @@ pipeline_routing_msg_req_arp_add_handler(struct pipeline *p, void *msg)
 	}
 
 	*((struct ether_addr *) &entry.macaddr) = req->macaddr;
+	entry.macaddr = entry.macaddr << 16;
 
 	rsp->status = rte_pipeline_table_entry_add(p->p,
 		p->table_id[1],
diff --git a/examples/ip_pipeline/pipeline/pipeline_routing_be.h b/examples/ip_pipeline/pipeline/pipeline_routing_be.h
index 45f37a1..7848dc1 100644
--- a/examples/ip_pipeline/pipeline/pipeline_routing_be.h
+++ b/examples/ip_pipeline/pipeline/pipeline_routing_be.h
@@ -39,6 +39,34 @@
 #include "pipeline_common_be.h"
 
 /*
+ * Pipeline argument parsing
+ */
+struct pipeline_routing_params {
+	/* routing */
+	uint32_t n_routes;
+
+	/* arp */
+	uint32_t n_arp_entries;
+	uint32_t arp;
+
+	/* l2 */
+	uint32_t qinq;
+	uint32_t qinq_sched;
+	uint32_t mpls;
+	uint32_t mpls_color_mark;
+
+	/* packet buffer offsets */
+	uint32_t ip_hdr_offset;
+	uint32_t ip_da_offset;
+	uint32_t arp_key_offset;
+	uint32_t color_offset;
+};
+
+int
+pipeline_routing_parse_args(struct pipeline_routing_params *p,
+	struct pipeline_params *params);
+
+/*
  * Route
  */
 enum pipeline_routing_route_key_type {
@@ -59,6 +87,36 @@ struct pipeline_routing_route_key {
 
 enum pipeline_routing_route_flags {
 	PIPELINE_ROUTING_ROUTE_LOCAL = 1 << 0, /* 0 = remote; 1 = local */
+	PIPELINE_ROUTING_ROUTE_ARP = 1 << 1, /* 0 = ARP OFF; 1 = ARP ON */
+	PIPELINE_ROUTING_ROUTE_QINQ = 1 << 2, /* 0 = QINQ OFF; 1 = QINQ ON */
+	PIPELINE_ROUTING_ROUTE_MPLS = 1 << 3, /* 0 = MPLS OFF; 1 = MPLS ON */
+};
+
+#define PIPELINE_ROUTING_MPLS_LABELS_MAX         4
+
+struct pipeline_routing_route_data {
+	uint32_t flags;
+	uint32_t port_id; /* Output port ID */
+
+	union {
+		/* Next hop IP (valid only when ARP is enabled) */
+		uint32_t ip;
+
+		/* Next hop MAC address (valid only when ARP disabled */
+		struct ether_addr macaddr;
+	} ethernet;
+
+	union {
+		struct {
+			uint16_t svlan;
+			uint16_t cvlan;
+		} qinq;
+
+		struct {
+			uint32_t labels[PIPELINE_ROUTING_MPLS_LABELS_MAX];
+			uint32_t n_labels;
+		} mpls;
+	} l2;
 };
 
 /*
@@ -106,9 +164,7 @@ struct pipeline_routing_route_add_msg_req {
 	struct pipeline_routing_route_key key;
 
 	/* data */
-	uint32_t flags;
-	uint32_t port_id; /* Output port ID */
-	uint32_t ip; /* Next hop IP address (only valid for remote routes) */
+	struct pipeline_routing_route_data data;
 };
 
 struct pipeline_routing_route_add_msg_rsp {
