diff --git a/lib/librte_pipeline/Makefile b/lib/librte_pipeline/Makefile
index e8c43c7..72e4c7c 100644
--- a/lib/librte_pipeline/Makefile
+++ b/lib/librte_pipeline/Makefile
@@ -8,10 +8,11 @@ include $(RTE_SDK)/mk/rte.vars.mk
 #
 LIB = librte_pipeline.a
 
+CFLAGS += -DALLOW_EXPERIMENTAL_API
 CFLAGS += -O3
 CFLAGS += $(WERROR_FLAGS)
 LDLIBS += -lrte_eal -lrte_mempool -lrte_mbuf -lrte_table
-LDLIBS += -lrte_port
+LDLIBS += -lrte_port -lrte_meter
 
 EXPORT_MAP := rte_pipeline_version.map
 
diff --git a/lib/librte_pipeline/meson.build b/lib/librte_pipeline/meson.build
index 4dda560..71da295 100644
--- a/lib/librte_pipeline/meson.build
+++ b/lib/librte_pipeline/meson.build
@@ -5,4 +5,4 @@ version = 3
 allow_experimental_apis = true
 sources = files('rte_pipeline.c', 'rte_table_action.c')
 headers = files('rte_pipeline.h', 'rte_table_action.h')
-deps += ['port', 'table']
+deps += ['port', 'table', 'meter']
diff --git a/lib/librte_pipeline/rte_pipeline_version.map b/lib/librte_pipeline/rte_pipeline_version.map
index 13337de..c7106dc 100644
--- a/lib/librte_pipeline/rte_pipeline_version.map
+++ b/lib/librte_pipeline/rte_pipeline_version.map
@@ -51,7 +51,11 @@ EXPERIMENTAL {
 
 	rte_table_action_apply;
 	rte_table_action_create;
+	rte_table_action_dscp_table_update;
 	rte_table_action_free;
+	rte_table_action_meter_profile_add;
+	rte_table_action_meter_profile_delete;
+	rte_table_action_meter_read;
 	rte_table_action_profile_action_register;
 	rte_table_action_profile_create;
 	rte_table_action_profile_free;
diff --git a/lib/librte_pipeline/rte_table_action.c b/lib/librte_pipeline/rte_table_action.c
index ce484e4..b22bf80 100644
--- a/lib/librte_pipeline/rte_table_action.c
+++ b/lib/librte_pipeline/rte_table_action.c
@@ -5,13 +5,21 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include <rte_malloc.h>
-
 #include <rte_common.h>
+#include <rte_byteorder.h>
+#include <rte_cycles.h>
 #include <rte_malloc.h>
+#include <rte_ip.h>
+
 
 #include "rte_table_action.h"
 
+#define rte_htons rte_cpu_to_be_16
+#define rte_htonl rte_cpu_to_be_32
+
+#define rte_ntohs rte_be_to_cpu_16
+#define rte_ntohl rte_be_to_cpu_32
+
 /**
  * RTE_TABLE_ACTION_FWD
  */
@@ -33,6 +41,262 @@ fwd_apply(struct fwd_data *data,
 }
 
 /**
+ * RTE_TABLE_ACTION_MTR
+ */
+static int
+mtr_cfg_check(struct rte_table_action_mtr_config *mtr)
+{
+	if ((mtr->alg == RTE_TABLE_ACTION_METER_SRTCM) ||
+		((mtr->n_tc != 1) && (mtr->n_tc != 4)) ||
+		(mtr->n_bytes_enabled != 0))
+		return -ENOTSUP;
+	return 0;
+}
+
+#define MBUF_SCHED_QUEUE_TC_COLOR(queue, tc, color)        \
+	((uint16_t)((((uint64_t)(queue)) & 0x3) |          \
+	((((uint64_t)(tc)) & 0x3) << 2) |                  \
+	((((uint64_t)(color)) & 0x3) << 4)))
+
+#define MBUF_SCHED_COLOR(sched, color)                     \
+	(((sched) & (~0x30LLU)) | ((color) << 4))
+
+struct mtr_trtcm_data {
+	struct rte_meter_trtcm trtcm;
+	uint64_t stats[e_RTE_METER_COLORS];
+} __attribute__((__packed__));
+
+#define MTR_TRTCM_DATA_METER_PROFILE_ID_GET(data)          \
+	(((data)->stats[e_RTE_METER_GREEN] & 0xF8LLU) >> 3)
+
+static void
+mtr_trtcm_data_meter_profile_id_set(struct mtr_trtcm_data *data,
+	uint32_t profile_id)
+{
+	data->stats[e_RTE_METER_GREEN] &= ~0xF8LLU;
+	data->stats[e_RTE_METER_GREEN] |= (profile_id % 32) << 3;
+}
+
+#define MTR_TRTCM_DATA_POLICER_ACTION_DROP_GET(data, color)\
+	(((data)->stats[(color)] & 4LLU) >> 2)
+
+#define MTR_TRTCM_DATA_POLICER_ACTION_COLOR_GET(data, color)\
+	((enum rte_meter_color)((data)->stats[(color)] & 3LLU))
+
+static void
+mtr_trtcm_data_policer_action_set(struct mtr_trtcm_data *data,
+	enum rte_meter_color color,
+	enum rte_table_action_policer action)
+{
+	if (action == RTE_TABLE_ACTION_POLICER_DROP) {
+		data->stats[color] |= 4LLU;
+	} else {
+		data->stats[color] &= ~7LLU;
+		data->stats[color] |= color & 3LLU;
+	}
+}
+
+static uint64_t
+mtr_trtcm_data_stats_get(struct mtr_trtcm_data *data,
+	enum rte_meter_color color) {
+	return data->stats[color] >> 8;
+}
+
+static void
+mtr_trtcm_data_stats_reset(struct mtr_trtcm_data *data,
+	enum rte_meter_color color) {
+	data->stats[color] &= 0xFFLU;
+}
+
+#define MTR_TRTCM_DATA_STATS_INC(data, color)              \
+	((data)->stats[(color)] += (1LLU << 8))
+
+static size_t
+mtr_data_size(struct rte_table_action_mtr_config *mtr)
+{
+	return mtr->n_tc * sizeof(struct mtr_trtcm_data);
+}
+
+struct dscp_table_entry_data {
+	enum rte_meter_color color;
+	uint16_t tc;
+	uint16_t queue_tc_color;
+};
+
+struct dscp_table_data {
+	struct dscp_table_entry_data entry[64];
+};
+
+struct meter_profile_data {
+	struct rte_meter_trtcm_profile profile;
+	uint32_t profile_id;
+	int valid;
+};
+
+static struct meter_profile_data *
+meter_profile_data_find(struct meter_profile_data *mp,
+	uint32_t mp_size,
+	uint32_t profile_id)
+{
+	uint32_t i;
+
+	for (i = 0; i < mp_size; i++) {
+		struct meter_profile_data *mp_data = &mp[i];
+
+		if (mp_data->valid && (mp_data->profile_id == profile_id))
+			return mp_data;
+	}
+
+	return NULL;
+}
+
+static struct meter_profile_data *
+meter_profile_data_find_unused(struct meter_profile_data *mp,
+	uint32_t mp_size)
+{
+	uint32_t i;
+
+	for (i = 0; i < mp_size; i++) {
+		struct meter_profile_data *mp_data = &mp[i];
+
+		if (!mp_data->valid)
+			return mp_data;
+	}
+
+	return NULL;
+}
+
+static int
+mtr_apply_check(struct rte_table_action_mtr_params *p,
+	struct rte_table_action_mtr_config *cfg,
+	struct meter_profile_data *mp,
+	uint32_t mp_size)
+{
+	uint32_t i;
+
+	if (p->tc_mask > RTE_LEN2MASK(cfg->n_tc, uint32_t))
+		return -EINVAL;
+
+	for (i = 0; i < RTE_TABLE_ACTION_TC_MAX; i++) {
+		struct rte_table_action_mtr_tc_params *p_tc = &p->mtr[i];
+		struct meter_profile_data *mp_data;
+
+		if ((p->tc_mask & (1LLU << i)) == 0)
+			continue;
+
+		mp_data = meter_profile_data_find(mp,
+			mp_size,
+			p_tc->meter_profile_id);
+		if (!mp_data)
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+mtr_apply(struct mtr_trtcm_data *data,
+	struct rte_table_action_mtr_params *p,
+	struct rte_table_action_mtr_config *cfg,
+	struct meter_profile_data *mp,
+	uint32_t mp_size)
+{
+	uint32_t i;
+	int status;
+
+	/* Check input arguments */
+	status = mtr_apply_check(p, cfg, mp, mp_size);
+	if (status)
+		return status;
+
+	/* Apply */
+	for (i = 0; i < RTE_TABLE_ACTION_TC_MAX; i++) {
+		struct rte_table_action_mtr_tc_params *p_tc = &p->mtr[i];
+		struct mtr_trtcm_data *data_tc = &data[i];
+		struct meter_profile_data *mp_data;
+
+		if ((p->tc_mask & (1LLU << i)) == 0)
+			continue;
+
+		/* Find profile */
+		mp_data = meter_profile_data_find(mp,
+			mp_size,
+			p_tc->meter_profile_id);
+		if (!mp_data)
+			return -EINVAL;
+
+		memset(data_tc, 0, sizeof(*data_tc));
+
+		/* Meter object */
+		status = rte_meter_trtcm_config(&data_tc->trtcm,
+			&mp_data->profile);
+		if (status)
+			return status;
+
+		/* Meter profile */
+		mtr_trtcm_data_meter_profile_id_set(data_tc,
+			mp_data - mp);
+
+		/* Policer actions */
+		mtr_trtcm_data_policer_action_set(data_tc,
+			e_RTE_METER_GREEN,
+			p_tc->policer[e_RTE_METER_GREEN]);
+
+		mtr_trtcm_data_policer_action_set(data_tc,
+			e_RTE_METER_YELLOW,
+			p_tc->policer[e_RTE_METER_YELLOW]);
+
+		mtr_trtcm_data_policer_action_set(data_tc,
+			e_RTE_METER_RED,
+			p_tc->policer[e_RTE_METER_RED]);
+	}
+
+	return 0;
+}
+
+static __rte_always_inline uint64_t
+pkt_work_mtr(struct rte_mbuf *mbuf,
+	struct mtr_trtcm_data *data,
+	struct dscp_table_data *dscp_table,
+	struct meter_profile_data *mp,
+	uint64_t time,
+	uint32_t dscp,
+	uint16_t total_length)
+{
+	uint64_t drop_mask, sched;
+	uint64_t *sched_ptr = (uint64_t *) &mbuf->hash.sched;
+	struct dscp_table_entry_data *dscp_entry = &dscp_table->entry[dscp];
+	enum rte_meter_color color_in, color_meter, color_policer;
+	uint32_t tc, mp_id;
+
+	tc = dscp_entry->tc;
+	color_in = dscp_entry->color;
+	data += tc;
+	mp_id = MTR_TRTCM_DATA_METER_PROFILE_ID_GET(data);
+	sched = *sched_ptr;
+
+	/* Meter */
+	color_meter = rte_meter_trtcm_color_aware_check(
+		&data->trtcm,
+		&mp[mp_id].profile,
+		time,
+		total_length,
+		color_in);
+
+	/* Stats */
+	MTR_TRTCM_DATA_STATS_INC(data, color_meter);
+
+	/* Police */
+	drop_mask = MTR_TRTCM_DATA_POLICER_ACTION_DROP_GET(data, color_meter);
+	color_policer =
+		MTR_TRTCM_DATA_POLICER_ACTION_COLOR_GET(data, color_meter);
+	*sched_ptr = MBUF_SCHED_COLOR(sched, color_policer);
+
+	return drop_mask;
+}
+
+
+/**
  * Action profile
  */
 static int
@@ -40,6 +304,7 @@ action_valid(enum rte_table_action_type action)
 {
 	switch (action) {
 	case RTE_TABLE_ACTION_FWD:
+	case RTE_TABLE_ACTION_MTR:
 		return 1;
 	default:
 		return 0;
@@ -52,22 +317,28 @@ action_valid(enum rte_table_action_type action)
 struct ap_config {
 	uint64_t action_mask;
 	struct rte_table_action_common_config common;
+	struct rte_table_action_mtr_config mtr;
 };
 
 static size_t
 action_cfg_size(enum rte_table_action_type action)
 {
 	switch (action) {
+	case RTE_TABLE_ACTION_MTR:
+		return sizeof(struct rte_table_action_mtr_config);
 	default:
 		return 0;
 	}
 }
 
 static void*
-action_cfg_get(struct ap_config *ap_config __rte_unused,
+action_cfg_get(struct ap_config *ap_config,
 	enum rte_table_action_type type)
 {
 	switch (type) {
+	case RTE_TABLE_ACTION_MTR:
+		return &ap_config->mtr;
+
 	default:
 		return NULL;
 	}
@@ -93,12 +364,15 @@ struct ap_data {
 
 static size_t
 action_data_size(enum rte_table_action_type action,
-	struct ap_config *ap_config __rte_unused)
+	struct ap_config *ap_config)
 {
 	switch (action) {
 	case RTE_TABLE_ACTION_FWD:
 		return sizeof(struct fwd_data);
 
+	case RTE_TABLE_ACTION_MTR:
+		return mtr_data_size(&ap_config->mtr);
+
 	default:
 		return 0;
 	}
@@ -157,6 +431,8 @@ rte_table_action_profile_action_register(struct rte_table_action_profile *profil
 	enum rte_table_action_type type,
 	void *action_config)
 {
+	int status;
+
 	/* Check input arguments */
 	if ((profile == NULL) ||
 		profile->frozen ||
@@ -166,6 +442,19 @@ rte_table_action_profile_action_register(struct rte_table_action_profile *profil
 		(action_cfg_size(type) && (action_config == NULL)))
 		return -EINVAL;
 
+	switch (type) {
+	case RTE_TABLE_ACTION_MTR:
+		status = mtr_cfg_check(action_config);
+		break;
+
+	default:
+		status = 0;
+		break;
+	}
+
+	if (status)
+		return status;
+
 	/* Action enable */
 	action_cfg_set(&profile->cfg, type, action_config);
 
@@ -195,9 +484,16 @@ rte_table_action_profile_free(struct rte_table_action_profile *profile)
 	return 0;
 }
 
+/**
+ * Action
+ */
+#define METER_PROFILES_MAX                                 32
+
 struct rte_table_action {
 	struct ap_config cfg;
 	struct ap_data data;
+	struct dscp_table_data dscp_table;
+	struct meter_profile_data mp[METER_PROFILES_MAX];
 };
 
 struct rte_table_action *
@@ -261,31 +557,338 @@ rte_table_action_apply(struct rte_table_action *action,
 		return fwd_apply(action_data,
 			action_params);
 
+	case RTE_TABLE_ACTION_MTR:
+		return mtr_apply(action_data,
+			action_params,
+			&action->cfg.mtr,
+			action->mp,
+			RTE_DIM(action->mp));
+
 	default:
 		return -EINVAL;
 	}
 }
 
-static __rte_always_inline uint64_t
-pkt_work(struct rte_mbuf *mbuf __rte_unused,
-	struct rte_pipeline_table_entry *table_entry __rte_unused,
-	uint64_t time __rte_unused,
-	struct rte_table_action *action __rte_unused,
-	struct ap_config *cfg __rte_unused)
+int
+rte_table_action_dscp_table_update(struct rte_table_action *action,
+	uint64_t dscp_mask,
+	struct rte_table_action_dscp_table *table)
 {
+	uint32_t i;
+
+	/* Check input arguments */
+	if ((action == NULL) ||
+		(action->cfg.action_mask & (1LLU << RTE_TABLE_ACTION_MTR)) ||
+		(dscp_mask == 0) ||
+		(table == NULL))
+		return -EINVAL;
+
+	for (i = 0; i < RTE_DIM(table->entry); i++) {
+		struct dscp_table_entry_data *data =
+			&action->dscp_table.entry[i];
+		struct rte_table_action_dscp_table_entry *entry =
+			&table->entry[i];
+		uint16_t queue_tc_color =
+			MBUF_SCHED_QUEUE_TC_COLOR(entry->tc_queue_id,
+				entry->tc_id,
+				entry->color);
+
+		if ((dscp_mask & (1LLU << i)) == 0)
+			continue;
+
+		data->color = entry->color;
+		data->tc = entry->tc_id;
+		data->queue_tc_color = queue_tc_color;
+	}
+
 	return 0;
 }
 
-static __rte_always_inline uint64_t
-pkt4_work(struct rte_mbuf **mbufs __rte_unused,
-	struct rte_pipeline_table_entry **table_entries __rte_unused,
-	uint64_t time __rte_unused,
-	struct rte_table_action *action __rte_unused,
-	struct ap_config *cfg __rte_unused)
+int
+rte_table_action_meter_profile_add(struct rte_table_action *action,
+	uint32_t meter_profile_id,
+	struct rte_table_action_meter_profile *profile)
+{
+	struct meter_profile_data *mp_data;
+	uint32_t status;
+
+	/* Check input arguments */
+	if ((action == NULL) ||
+		((action->cfg.action_mask & (1LLU << RTE_TABLE_ACTION_MTR)) == 0) ||
+		(profile == NULL))
+		return -EINVAL;
+
+	if (profile->alg != RTE_TABLE_ACTION_METER_TRTCM)
+		return -ENOTSUP;
+
+	mp_data = meter_profile_data_find(action->mp,
+		RTE_DIM(action->mp),
+		meter_profile_id);
+	if (mp_data)
+		return -EEXIST;
+
+	mp_data = meter_profile_data_find_unused(action->mp,
+		RTE_DIM(action->mp));
+	if (!mp_data)
+		return -ENOSPC;
+
+	/* Install new profile */
+	status = rte_meter_trtcm_profile_config(&mp_data->profile,
+		&profile->trtcm);
+	if (status)
+		return status;
+
+	mp_data->profile_id = meter_profile_id;
+	mp_data->valid = 1;
+
+	return 0;
+}
+
+int
+rte_table_action_meter_profile_delete(struct rte_table_action *action,
+	uint32_t meter_profile_id)
 {
+	struct meter_profile_data *mp_data;
+
+	/* Check input arguments */
+	if ((action == NULL) ||
+		((action->cfg.action_mask & (1LLU << RTE_TABLE_ACTION_MTR)) == 0))
+		return -EINVAL;
+
+	mp_data = meter_profile_data_find(action->mp,
+		RTE_DIM(action->mp),
+		meter_profile_id);
+	if (!mp_data)
+		return 0;
+
+	/* Uninstall profile */
+	mp_data->valid = 0;
+
+	return 0;
+}
+
+int
+rte_table_action_meter_read(struct rte_table_action *action,
+	void *data,
+	uint32_t tc_mask,
+	struct rte_table_action_mtr_counters *stats,
+	int clear)
+{
+	struct mtr_trtcm_data *mtr_data;
+	uint32_t i;
+
+	/* Check input arguments */
+	if ((action == NULL) ||
+		((action->cfg.action_mask & (1LLU << RTE_TABLE_ACTION_MTR)) == 0) ||
+		(data == NULL) ||
+		(tc_mask > RTE_LEN2MASK(action->cfg.mtr.n_tc, uint32_t)))
+		return -EINVAL;
+
+	mtr_data = action_data_get(data, action, RTE_TABLE_ACTION_MTR);
+
+	/* Read */
+	if (stats) {
+		for (i = 0; i < RTE_TABLE_ACTION_TC_MAX; i++) {
+			struct rte_table_action_mtr_counters_tc *dst =
+				&stats->stats[i];
+			struct mtr_trtcm_data *src = &mtr_data[i];
+
+			if ((tc_mask & (1 << i)) == 0)
+				continue;
+
+			dst->n_packets[e_RTE_METER_GREEN] =
+				mtr_trtcm_data_stats_get(src, e_RTE_METER_GREEN);
+
+			dst->n_packets[e_RTE_METER_YELLOW] =
+				mtr_trtcm_data_stats_get(src, e_RTE_METER_YELLOW);
+
+			dst->n_packets[e_RTE_METER_RED] =
+				mtr_trtcm_data_stats_get(src, e_RTE_METER_RED);
+
+			dst->n_packets_valid = 1;
+			dst->n_bytes_valid = 0;
+		}
+
+		stats->tc_mask = tc_mask;
+	}
+
+	/* Clear */
+	if (clear)
+		for (i = 0; i < RTE_TABLE_ACTION_TC_MAX; i++) {
+			struct mtr_trtcm_data *src = &mtr_data[i];
+
+			if ((tc_mask & (1 << i)) == 0)
+				continue;
+
+			mtr_trtcm_data_stats_reset(src, e_RTE_METER_GREEN);
+			mtr_trtcm_data_stats_reset(src, e_RTE_METER_YELLOW);
+			mtr_trtcm_data_stats_reset(src, e_RTE_METER_RED);
+		}
+
+
 	return 0;
 }
 
+static __rte_always_inline uint64_t
+pkt_work(struct rte_mbuf *mbuf,
+	struct rte_pipeline_table_entry *table_entry,
+	uint64_t time,
+	struct rte_table_action *action,
+	struct ap_config *cfg)
+{
+	uint64_t drop_mask = 0;
+
+	uint32_t ip_offset = action->cfg.common.ip_offset;
+	void *ip = RTE_MBUF_METADATA_UINT32_PTR(mbuf, ip_offset);
+
+	uint32_t dscp;
+	uint16_t total_length;
+
+	if (cfg->common.ip_version) {
+		struct ipv4_hdr *hdr = ip;
+
+		dscp = hdr->type_of_service >> 2;
+		total_length = rte_ntohs(hdr->total_length);
+	} else {
+		struct ipv6_hdr *hdr = ip;
+
+		dscp = (rte_ntohl(hdr->vtc_flow) & 0x0F600000) >> 18;
+		total_length =
+			rte_ntohs(hdr->payload_len) + sizeof(struct ipv6_hdr);
+	}
+
+	if (cfg->action_mask & (1LLU << RTE_TABLE_ACTION_MTR)) {
+		void *data =
+			action_data_get(table_entry, action, RTE_TABLE_ACTION_MTR);
+
+		drop_mask |= pkt_work_mtr(mbuf,
+			data,
+			&action->dscp_table,
+			action->mp,
+			time,
+			dscp,
+			total_length);
+	}
+
+	return drop_mask;
+}
+
+static __rte_always_inline uint64_t
+pkt4_work(struct rte_mbuf **mbufs,
+	struct rte_pipeline_table_entry **table_entries,
+	uint64_t time,
+	struct rte_table_action *action,
+	struct ap_config *cfg)
+{
+	uint64_t drop_mask0 = 0;
+	uint64_t drop_mask1 = 0;
+	uint64_t drop_mask2 = 0;
+	uint64_t drop_mask3 = 0;
+
+	struct rte_mbuf *mbuf0 = mbufs[0];
+	struct rte_mbuf *mbuf1 = mbufs[1];
+	struct rte_mbuf *mbuf2 = mbufs[2];
+	struct rte_mbuf *mbuf3 = mbufs[3];
+
+	struct rte_pipeline_table_entry *table_entry0 = table_entries[0];
+	struct rte_pipeline_table_entry *table_entry1 = table_entries[1];
+	struct rte_pipeline_table_entry *table_entry2 = table_entries[2];
+	struct rte_pipeline_table_entry *table_entry3 = table_entries[3];
+
+	uint32_t ip_offset = action->cfg.common.ip_offset;
+	void *ip0 = RTE_MBUF_METADATA_UINT32_PTR(mbuf0, ip_offset);
+	void *ip1 = RTE_MBUF_METADATA_UINT32_PTR(mbuf1, ip_offset);
+	void *ip2 = RTE_MBUF_METADATA_UINT32_PTR(mbuf2, ip_offset);
+	void *ip3 = RTE_MBUF_METADATA_UINT32_PTR(mbuf3, ip_offset);
+
+	uint32_t dscp0, dscp1, dscp2, dscp3;
+	uint16_t total_length0, total_length1, total_length2, total_length3;
+
+	if (cfg->common.ip_version) {
+		struct ipv4_hdr *hdr0 = ip0;
+		struct ipv4_hdr *hdr1 = ip1;
+		struct ipv4_hdr *hdr2 = ip2;
+		struct ipv4_hdr *hdr3 = ip3;
+
+		dscp0 = hdr0->type_of_service >> 2;
+		dscp1 = hdr1->type_of_service >> 2;
+		dscp2 = hdr2->type_of_service >> 2;
+		dscp3 = hdr3->type_of_service >> 2;
+
+		total_length0 = rte_ntohs(hdr0->total_length);
+		total_length1 = rte_ntohs(hdr1->total_length);
+		total_length2 = rte_ntohs(hdr2->total_length);
+		total_length3 = rte_ntohs(hdr3->total_length);
+	} else {
+		struct ipv6_hdr *hdr0 = ip0;
+		struct ipv6_hdr *hdr1 = ip1;
+		struct ipv6_hdr *hdr2 = ip2;
+		struct ipv6_hdr *hdr3 = ip3;
+
+		dscp0 = (rte_ntohl(hdr0->vtc_flow) & 0x0F600000) >> 18;
+		dscp1 = (rte_ntohl(hdr1->vtc_flow) & 0x0F600000) >> 18;
+		dscp2 = (rte_ntohl(hdr2->vtc_flow) & 0x0F600000) >> 18;
+		dscp3 = (rte_ntohl(hdr3->vtc_flow) & 0x0F600000) >> 18;
+
+		total_length0 =
+			rte_ntohs(hdr0->payload_len) + sizeof(struct ipv6_hdr);
+		total_length1 =
+			rte_ntohs(hdr1->payload_len) + sizeof(struct ipv6_hdr);
+		total_length2 =
+			rte_ntohs(hdr2->payload_len) + sizeof(struct ipv6_hdr);
+		total_length3 =
+			rte_ntohs(hdr3->payload_len) + sizeof(struct ipv6_hdr);
+	}
+
+	if (cfg->action_mask & (1LLU << RTE_TABLE_ACTION_MTR)) {
+		void *data0 =
+			action_data_get(table_entry0, action, RTE_TABLE_ACTION_MTR);
+		void *data1 =
+			action_data_get(table_entry1, action, RTE_TABLE_ACTION_MTR);
+		void *data2 =
+			action_data_get(table_entry2, action, RTE_TABLE_ACTION_MTR);
+		void *data3 =
+			action_data_get(table_entry3, action, RTE_TABLE_ACTION_MTR);
+
+		drop_mask0 |= pkt_work_mtr(mbuf0,
+			data0,
+			&action->dscp_table,
+			action->mp,
+			time,
+			dscp0,
+			total_length0);
+
+		drop_mask1 |= pkt_work_mtr(mbuf1,
+			data1,
+			&action->dscp_table,
+			action->mp,
+			time,
+			dscp1,
+			total_length1);
+
+		drop_mask2 |= pkt_work_mtr(mbuf2,
+			data2,
+			&action->dscp_table,
+			action->mp,
+			time,
+			dscp2,
+			total_length2);
+
+		drop_mask3 |= pkt_work_mtr(mbuf3,
+			data3,
+			&action->dscp_table,
+			action->mp,
+			time,
+			dscp3,
+			total_length3);
+	}
+
+	return drop_mask0 |
+		(drop_mask1 << 1) |
+		(drop_mask2 << 2) |
+		(drop_mask3 << 3);
+}
+
 static __rte_always_inline int
 ah(struct rte_pipeline *p,
 	struct rte_mbuf **pkts,
@@ -297,6 +900,9 @@ ah(struct rte_pipeline *p,
 	uint64_t pkts_drop_mask = 0;
 	uint64_t time = 0;
 
+	if (cfg->action_mask & (1LLU << RTE_TABLE_ACTION_MTR))
+		time = rte_rdtsc();
+
 	if ((pkts_mask & (pkts_mask + 1)) == 0) {
 		uint64_t n_pkts = __builtin_popcountll(pkts_mask);
 		uint32_t i;
diff --git a/lib/librte_pipeline/rte_table_action.h b/lib/librte_pipeline/rte_table_action.h
index 03b77ca..c2f4a55 100644
--- a/lib/librte_pipeline/rte_table_action.h
+++ b/lib/librte_pipeline/rte_table_action.h
@@ -59,6 +59,7 @@ extern "C" {
 #include <stdint.h>
 
 #include <rte_compat.h>
+#include <rte_meter.h>
 
 #include "rte_pipeline.h"
 
@@ -66,6 +67,9 @@ extern "C" {
 enum rte_table_action_type {
 	/** Forward to next pipeline table, output port or drop. */
 	RTE_TABLE_ACTION_FWD = 0,
+
+	/**  Traffic Metering and Policing. */
+	RTE_TABLE_ACTION_MTR,
 };
 
 /** Common action configuration (per table action profile). */
@@ -94,6 +98,164 @@ struct rte_table_action_fwd_params {
 };
 
 /**
+ * RTE_TABLE_ACTION_MTR
+ */
+/** Max number of traffic classes (TCs). */
+#define RTE_TABLE_ACTION_TC_MAX                                  4
+
+/** Max number of queues per traffic class. */
+#define RTE_TABLE_ACTION_TC_QUEUE_MAX                            4
+
+/** Differentiated Services Code Point (DSCP) translation table entry. */
+struct rte_table_action_dscp_table_entry {
+	/** Traffic class. Used by the meter or the traffic management actions.
+	 * Has to be strictly smaller than *RTE_TABLE_ACTION_TC_MAX*. Traffic
+	 * class 0 is the highest priority.
+	 */
+	uint32_t tc_id;
+
+	/** Traffic class queue. Used by the traffic management action. Has to
+	 * be strictly smaller than *RTE_TABLE_ACTION_TC_QUEUE_MAX*.
+	 */
+	uint32_t tc_queue_id;
+
+	/** Packet color. Used by the meter action as the packet input color
+	 * for the color aware mode of the traffic metering algorithm.
+	 */
+	enum rte_meter_color color;
+};
+
+/** DSCP translation table. */
+struct rte_table_action_dscp_table {
+	/** Array of DSCP table entries */
+	struct rte_table_action_dscp_table_entry entry[64];
+};
+
+/** Supported traffic metering algorithms. */
+enum rte_table_action_meter_algorithm {
+	/** Single Rate Three Color Marker (srTCM) - IETF RFC 2697. */
+	RTE_TABLE_ACTION_METER_SRTCM,
+
+	/** Two Rate Three Color Marker (trTCM) - IETF RFC 2698. */
+	RTE_TABLE_ACTION_METER_TRTCM,
+};
+
+/** Traffic metering profile (configuration template). */
+struct rte_table_action_meter_profile {
+	/** Traffic metering algorithm. */
+	enum rte_table_action_meter_algorithm alg;
+
+	RTE_STD_C11
+	union {
+		/** Only valid when *alg* is set to srTCM - IETF RFC 2697. */
+		struct rte_meter_srtcm_params srtcm;
+
+		/** Only valid when *alg* is set to trTCM - IETF RFC 2698. */
+		struct rte_meter_trtcm_params trtcm;
+	};
+};
+
+/** Policer actions. */
+enum rte_table_action_policer {
+	/** Recolor the packet as green. */
+	RTE_TABLE_ACTION_POLICER_COLOR_GREEN = 0,
+
+	/** Recolor the packet as yellow. */
+	RTE_TABLE_ACTION_POLICER_COLOR_YELLOW,
+
+	/** Recolor the packet as red. */
+	RTE_TABLE_ACTION_POLICER_COLOR_RED,
+
+	/** Drop the packet. */
+	RTE_TABLE_ACTION_POLICER_DROP,
+
+	/** Number of policer actions. */
+	RTE_TABLE_ACTION_POLICER_MAX
+};
+
+/** Meter action configuration per traffic class. */
+struct rte_table_action_mtr_tc_params {
+	/** Meter profile ID. */
+	uint32_t meter_profile_id;
+
+	/** Policer actions. */
+	enum rte_table_action_policer policer[e_RTE_METER_COLORS];
+};
+
+/** Meter action statistics counters per traffic class. */
+struct rte_table_action_mtr_counters_tc {
+	/** Number of packets per color at the output of the traffic metering
+	 * and before the policer actions are executed. Only valid when
+	 * *n_packets_valid* is non-zero.
+	 */
+	uint64_t n_packets[e_RTE_METER_COLORS];
+
+	/** Number of packet bytes per color at the output of the traffic
+	 * metering and before the policer actions are executed. Only valid when
+	 * *n_bytes_valid* is non-zero.
+	 */
+	uint64_t n_bytes[e_RTE_METER_COLORS];
+
+	/** When non-zero, the *n_packets* field is valid. */
+	int n_packets_valid;
+
+	/** When non-zero, the *n_bytes* field is valid. */
+	int n_bytes_valid;
+};
+
+/** Meter action configuration (per table action profile). */
+struct rte_table_action_mtr_config {
+	/** Meter algorithm. */
+	enum rte_table_action_meter_algorithm alg;
+
+	/** Number of traffic classes. Each traffic class has its own traffic
+	 * meter and policer instances. Needs to be equal to either 1 or to
+	 * *RTE_TABLE_ACTION_TC_MAX*.
+	 */
+	uint32_t n_tc;
+
+	/** When non-zero, the *n_packets* meter stats counter is enabled,
+	 * otherwise it is disabled.
+	 *
+	 * @see struct rte_table_action_mtr_counters_tc
+	 */
+	int n_packets_enabled;
+
+	/** When non-zero, the *n_bytes* meter stats counter is enabled,
+	 * otherwise it is disabled.
+	 *
+	 * @see struct rte_table_action_mtr_counters_tc
+	 */
+	int n_bytes_enabled;
+};
+
+/** Meter action parameters (per table rule). */
+struct rte_table_action_mtr_params {
+	/** Traffic meter and policer parameters for each of the *tc_mask*
+	 * traffic classes.
+	 */
+	struct rte_table_action_mtr_tc_params mtr[RTE_TABLE_ACTION_TC_MAX];
+
+	/** Bit mask defining which traffic class parameters are valid in *mtr*.
+	 * If bit N is set in *tc_mask*, then parameters for traffic class N are
+	 * valid in *mtr*.
+	 */
+	uint32_t tc_mask;
+};
+
+/** Meter action statistics counters (per table rule). */
+struct rte_table_action_mtr_counters {
+	/** Stats counters for each of the *tc_mask* traffic classes. */
+	struct rte_table_action_mtr_counters_tc stats[RTE_TABLE_ACTION_TC_MAX];
+
+	/** Bit mask defining which traffic class parameters are valid in *mtr*.
+	 * If bit N is set in *tc_mask*, then parameters for traffic class N are
+	 * valid in *mtr*.
+	 */
+	uint32_t tc_mask;
+};
+
+/**
  * Table action profile.
  */
 struct rte_table_action_profile;
@@ -231,6 +393,92 @@ rte_table_action_apply(struct rte_table_action *action,
 	enum rte_table_action_type type,
 	void *action_params);
 
+/**
+ * Table action DSCP table update.
+ *
+ * @param[in] action
+ *   Handle to table action object (needs to be valid).
+ * @param[in] dscp_mask
+ *   64-bit mask defining the DSCP table entries to be updated. If bit N is set
+ *   in this bit mask, then DSCP table entry N is to be updated, otherwise not.
+ * @param[in] table
+ *   DSCP table.
+ * @return
+ *   Zero on success, non-zero error code otherwise.
+ */
+int __rte_experimental
+rte_table_action_dscp_table_update(struct rte_table_action *action,
+	uint64_t dscp_mask,
+	struct rte_table_action_dscp_table *table);
+
+/**
+ * Table action meter profile add.
+ *
+ * @param[in] action
+ *   Handle to table action object (needs to be valid).
+ * @param[in] meter_profile_id
+ *   Meter profile ID to be used for the *profile* once it is successfully added
+ *   to the *action* object (needs to be unused by the set of meter profiles
+ *   currently registered for the *action* object).
+ * @param[in] profile
+ *   Meter profile to be added.
+ * @return
+ *   Zero on success, non-zero error code otherwise.
+ */
+int __rte_experimental
+rte_table_action_meter_profile_add(struct rte_table_action *action,
+	uint32_t meter_profile_id,
+	struct rte_table_action_meter_profile *profile);
+
+/**
+ * Table action meter profile delete.
+ *
+ * @param[in] action
+ *   Handle to table action object (needs to be valid).
+ * @param[in] meter_profile_id
+ *   Meter profile ID of the meter profile to be deleted from the *action*
+ *   object (needs to be valid for the *action* object).
+ * @return
+ *   Zero on success, non-zero error code otherwise.
+ */
+int __rte_experimental
+rte_table_action_meter_profile_delete(struct rte_table_action *action,
+	uint32_t meter_profile_id);
+
+/**
+ * Table action meter read.
+ *
+ * @param[in] action
+ *   Handle to table action object (needs to be valid).
+ * @param[in] data
+ *   Data byte array (typically table rule data) with meter action previously
+ *   applied on it.
+ * @param[in] tc_mask
+ *   Bit mask defining which traffic classes should have the meter stats
+ *   counters read from *data* and stored into *stats*. If bit N is set in this
+ *   bit mask, then traffic class N is part of this operation, otherwise it is
+ *   not. If bit N is set in this bit mask, then traffic class N must be one of
+ *   the traffic classes that are enabled for the meter action in the table
+ *   action profile used by the *action* object.
+ * @param[inout] stats
+ *   When non-NULL, it points to the area where the meter stats counters read
+ *   from *data* are saved. Only the meter stats counters for the *tc_mask*
+ *   traffic classes are read and stored to *stats*.
+ * @param[in] clear
+ *   When non-zero, the meter stats counters are cleared (i.e. set to zero),
+ *   otherwise the counters are not modified. When the read operation is enabled
+ *   (*stats* is non-NULL), the clear operation is performed after the read
+ *   operation is completed.
+ * @return
+ *   Zero on success, non-zero error code otherwise.
+ */
+int __rte_experimental
+rte_table_action_meter_read(struct rte_table_action *action,
+	void *data,
+	uint32_t tc_mask,
+	struct rte_table_action_mtr_counters *stats,
+	int clear);
+
 #ifdef __cplusplus
 }
 #endif
