diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 75ca703..f555c65 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -131,12 +131,14 @@ __LIB__libodp_la_SOURCES = \
 			   odp_packet.c \
 			   odp_packet_flags.c \
 			   odp_packet_io.c \
+			   pktio/ethtool.c \
 			   pktio/io_ops.c \
 			   pktio/pktio_common.c \
 			   pktio/loop.c \
 			   pktio/netmap.c \
 			   pktio/socket.c \
 			   pktio/socket_mmap.c \
+			   pktio/sysfs.c \
 			   pktio/tap.c \
 			   odp_pkt_queue.c \
 			   odp_pool.c \
diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
index e15c6af..d49b243 100644
--- a/platform/linux-generic/include/odp_packet_io_internal.h
+++ b/platform/linux-generic/include/odp_packet_io_internal.h
@@ -90,6 +90,9 @@ struct pktio_entry {
 		STATE_STOP
 	} state;
 	classifier_t cls;		/**< classifier linked with this pktio*/
+	odp_pktio_stats_t stats;	/**< statistic counters for pktio */
+	int use_ethtool;		/**< 1 - use ethtool,
+					     0 - sysfs for statistics */
 	char name[PKTIO_NAME_LEN];	/**< name of pktio provided to
 					   pktio_open() */
 	odp_pktio_t id;
@@ -129,6 +132,8 @@ typedef struct pktio_if_ops {
 	int (*close)(pktio_entry_t *pktio_entry);
 	int (*start)(pktio_entry_t *pktio_entry);
 	int (*stop)(pktio_entry_t *pktio_entry);
+	int (*stats)(pktio_entry_t *pktio_entry, odp_pktio_stats_t *stats);
+	int (*stats_reset)(pktio_entry_t *pktio_entry);
 	int (*recv)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
 		    unsigned len);
 	int (*send)(pktio_entry_t *pktio_entry, odp_packet_t pkt_table[],
@@ -218,6 +223,12 @@ extern const pktio_if_ops_t pcap_pktio_ops;
 extern const pktio_if_ops_t tap_pktio_ops;
 extern const pktio_if_ops_t * const pktio_if_ops[];
 
+int sysfs_stats(pktio_entry_t *pktio_entry,
+		odp_pktio_stats_t *stats);
+int sock_stats_reset(pktio_entry_t *pktio_entry);
+int sock_stats(pktio_entry_t *pktio_entry,
+	       odp_pktio_stats_t *stats);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/platform/linux-generic/include/odp_packet_socket.h b/platform/linux-generic/include/odp_packet_socket.h
index 66c258f..b2d5f46 100644
--- a/platform/linux-generic/include/odp_packet_socket.h
+++ b/platform/linux-generic/include/odp_packet_socket.h
@@ -163,4 +163,9 @@ int rss_conf_set_fd(int fd, const char *name,
  */
 void rss_conf_print(const odp_pktin_hash_proto_t *hash_proto);
 
+/**
+ * Get ethtool statistics of a packet socket
+ */
+int ethtool_stats_get_fd(int fd, const char *name, odp_pktio_stats_t *stats);
+
 #endif
diff --git a/platform/linux-generic/odp_packet_io.c b/platform/linux-generic/odp_packet_io.c
index a235798..1a58608 100644
--- a/platform/linux-generic/odp_packet_io.c
+++ b/platform/linux-generic/odp_packet_io.c
@@ -941,6 +941,59 @@ int odp_pktio_capability(odp_pktio_t pktio, odp_pktio_capability_t *capa)
 	return single_capability(capa);
 }
 
+int odp_pktio_stats(odp_pktio_t pktio,
+		    odp_pktio_stats_t *stats)
+{
+	pktio_entry_t *entry;
+	int ret = -1;
+
+	entry = get_pktio_entry(pktio);
+	if (entry == NULL) {
+		ODP_DBG("pktio entry %d does not exist\n", pktio);
+		return -1;
+	}
+
+	lock_entry(entry);
+
+	if (odp_unlikely(is_free(entry))) {
+		unlock_entry(entry);
+		ODP_DBG("already freed pktio\n");
+		return -1;
+	}
+
+	if (entry->s.ops->stats)
+		ret = entry->s.ops->stats(entry, stats);
+	unlock_entry(entry);
+
+	return ret;
+}
+
+int odp_pktio_stats_reset(odp_pktio_t pktio)
+{
+	pktio_entry_t *entry;
+	int ret = -1;
+
+	entry = get_pktio_entry(pktio);
+	if (entry == NULL) {
+		ODP_DBG("pktio entry %d does not exist\n", pktio);
+		return -1;
+	}
+
+	lock_entry(entry);
+
+	if (odp_unlikely(is_free(entry))) {
+		unlock_entry(entry);
+		ODP_DBG("already freed pktio\n");
+		return -1;
+	}
+
+	if (entry->s.ops->stats)
+		ret = entry->s.ops->stats_reset(entry);
+	unlock_entry(entry);
+
+	return ret;
+}
+
 int odp_pktio_input_queues_config(odp_pktio_t pktio,
 				  const odp_pktio_input_queue_param_t *param)
 {
diff --git a/platform/linux-generic/pktio/netmap.c b/platform/linux-generic/pktio/netmap.c
index 4bc51fd..127126d 100644
--- a/platform/linux-generic/pktio/netmap.c
+++ b/platform/linux-generic/pktio/netmap.c
@@ -432,6 +432,10 @@ static int netmap_open(odp_pktio_t id ODP_UNUSED, pktio_entry_t *pktio_entry,
 		odp_ticketlock_init(&pkt_nm->tx_desc_ring[i].s.lock);
 	}
 
+	/* netmap uses only ethtool to get statistics counters */
+	pktio_entry->s.use_ethtool = 1;
+	(void)sock_stats_reset(pktio_entry);
+
 	return 0;
 
 error:
@@ -853,6 +857,8 @@ const pktio_if_ops_t netmap_pktio_ops = {
 	.close = netmap_close,
 	.start = netmap_start,
 	.stop = netmap_stop,
+	.stats = sock_stats,
+	.stats_reset = sock_stats_reset,
 	.recv = netmap_recv,
 	.send = netmap_send,
 	.mtu_get = netmap_mtu_get,
diff --git a/platform/linux-generic/pktio/socket.c b/platform/linux-generic/pktio/socket.c
index ae5a2d6..3f5d826 100644
--- a/platform/linux-generic/pktio/socket.c
+++ b/platform/linux-generic/pktio/socket.c
@@ -459,6 +459,7 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
 	char shm_name[ODP_SHM_NAME_LEN];
 	pkt_sock_t *pkt_sock = &pktio_entry->s.pkt_sock;
 	uint8_t *addr;
+	odp_pktio_stats_t cur_stats;
 
 	/* Init pktio entry */
 	memset(pkt_sock, 0, sizeof(*pkt_sock));
@@ -517,6 +518,23 @@ static int sock_setup_pkt(pktio_entry_t *pktio_entry, const char *netdev,
 		ODP_ERR("bind(to IF): %s\n", strerror(errno));
 		goto error;
 	}
+
+	err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+				   pktio_entry->s.name,
+				   &cur_stats);
+	if (err != 0) {
+		err = sysfs_stats(pktio_entry, &cur_stats);
+		if (err != 0)
+			ODP_ABORT("statistic counters are not reachable\n");
+		pktio_entry->s.use_ethtool = 0;
+	} else {
+		pktio_entry->s.use_ethtool = 1;
+	}
+
+	err = sock_stats_reset(pktio_entry);
+	if (err != 0)
+		goto error;
+
 	return 0;
 
 error:
@@ -767,6 +785,71 @@ static int sock_promisc_mode_get(pktio_entry_t *pktio_entry)
 				   pktio_entry->s.name);
 }
 
+int sock_stats(pktio_entry_t *pktio_entry,
+	       odp_pktio_stats_t *stats)
+{
+	odp_pktio_stats_t cur_stats;
+	int ret;
+
+	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
+	if (pktio_entry->s.use_ethtool) {
+		ret =  ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+					    pktio_entry->s.name,
+					    &cur_stats);
+	} else {
+		ret = sysfs_stats(pktio_entry, &cur_stats);
+	}
+	if (ret)
+		ODP_ABORT("getting statistics error\n");
+
+	stats->in_octets = cur_stats.in_octets -
+				pktio_entry->s.stats.in_octets;
+	stats->in_ucast_pkts = cur_stats.in_ucast_pkts -
+				pktio_entry->s.stats.in_ucast_pkts;
+	stats->in_discards = cur_stats.in_discards -
+				pktio_entry->s.stats.in_discards;
+	stats->in_errors = cur_stats.in_errors -
+				pktio_entry->s.stats.in_errors;
+	stats->in_unknown_protos = cur_stats.in_unknown_protos -
+				pktio_entry->s.stats.in_unknown_protos;
+
+	stats->out_octets = cur_stats.out_octets -
+				pktio_entry->s.stats.out_octets;
+	stats->out_ucast_pkts = cur_stats.out_ucast_pkts -
+				pktio_entry->s.stats.out_ucast_pkts;
+	stats->out_discards = cur_stats.out_discards -
+				pktio_entry->s.stats.out_discards;
+	stats->out_errors = cur_stats.out_errors -
+				pktio_entry->s.stats.out_errors;
+
+	return 0;
+}
+
+int sock_stats_reset(pktio_entry_t *pktio_entry)
+{
+	int err = 0;
+	odp_pktio_stats_t cur_stats;
+
+	memset(&cur_stats, 0, sizeof(odp_pktio_stats_t));
+
+	if (pktio_entry->s.use_ethtool) {
+		err = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+					   pktio_entry->s.name,
+					   &cur_stats);
+	} else {
+		err = sysfs_stats(pktio_entry, &cur_stats);
+	}
+
+	if (err != 0) {
+		ODP_ERR("stats error\n");
+	} else {
+		memcpy(&pktio_entry->s.stats, &cur_stats,
+		       sizeof(odp_pktio_stats_t));
+	}
+
+	return err;
+}
+
 const pktio_if_ops_t sock_mmsg_pktio_ops = {
 	.name = "socket",
 	.init = NULL,
@@ -775,6 +858,8 @@ const pktio_if_ops_t sock_mmsg_pktio_ops = {
 	.close = sock_close,
 	.start = NULL,
 	.stop = NULL,
+	.stats = sock_stats,
+	.stats_reset = sock_stats_reset,
 	.recv = sock_mmsg_recv,
 	.send = sock_mmsg_send,
 	.mtu_get = sock_mtu_get,
diff --git a/platform/linux-generic/pktio/socket_mmap.c b/platform/linux-generic/pktio/socket_mmap.c
index 628848a..5a0503a 100644
--- a/platform/linux-generic/pktio/socket_mmap.c
+++ b/platform/linux-generic/pktio/socket_mmap.c
@@ -445,6 +445,7 @@ static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
 {
 	int if_idx;
 	int ret = 0;
+	odp_pktio_stats_t cur_stats;
 
 	if (getenv("ODP_PKTIO_DISABLE_SOCKET_MMAP"))
 		return -1;
@@ -504,6 +505,22 @@ static int sock_mmap_open(odp_pktio_t id ODP_UNUSED,
 			goto error;
 	}
 
+	ret = ethtool_stats_get_fd(pktio_entry->s.pkt_sock.sockfd,
+				   pktio_entry->s.name,
+				   &cur_stats);
+	if (ret != 0) {
+		ret = sysfs_stats(pktio_entry, &cur_stats);
+		if (ret != 0)
+			ODP_ABORT("statistic counters are not reachable\n");
+		pktio_entry->s.use_ethtool = 0;
+	} else {
+		pktio_entry->s.use_ethtool = 1;
+	}
+
+	ret = sock_stats_reset(pktio_entry);
+	if (ret != 0)
+		goto error;
+
 	return 0;
 
 error:
@@ -562,6 +579,8 @@ const pktio_if_ops_t sock_mmap_pktio_ops = {
 	.close = sock_mmap_close,
 	.start = NULL,
 	.stop = NULL,
+	.stats = sock_stats,
+	.stats_reset = sock_stats_reset,
 	.recv = sock_mmap_recv,
 	.send = sock_mmap_send,
 	.mtu_get = sock_mmap_mtu_get,
