diff --git a/ip/ip6tunnel.c b/ip/ip6tunnel.c
index 2e6f513..c7fa082 100644
--- a/ip/ip6tunnel.c
+++ b/ip/ip6tunnel.c
@@ -67,8 +67,9 @@ static void usage(void)
 	exit(-1);
 }
 
-static void print_tunnel(struct ip6_tnl_parm2 *p)
+static void print_tunnel(const void *t)
 {
+	const struct ip6_tnl_parm2 *p = t;
 	char s1[1024];
 	char s2[1024];
 
@@ -313,13 +314,24 @@ static void ip6_tnl_parm_init(struct ip6_tnl_parm2 *p, int apply_default)
 	}
 }
 
-/*
- * @p1: user specified parameter
- * @p2: database entry
- */
-static int ip6_tnl_parm_match(const struct ip6_tnl_parm2 *p1,
-			      const struct ip6_tnl_parm2 *p2)
+static void ip6_tnl_parm_initialize(const struct tnl_print_nlmsg_info *info)
+{
+	const struct ifinfomsg *ifi = info->ifi;
+	const struct ip6_tnl_parm2 *p1 = info->p1;
+	struct ip6_tnl_parm2 *p2 = info->p2;
+
+	ip6_tnl_parm_init(p2, 0);
+	if (ifi->ifi_type == ARPHRD_IP6GRE)
+		p2->proto = IPPROTO_GRE;
+	p2->link = ifi->ifi_index;
+	strcpy(p2->name, p1->name);
+}
+
+static bool ip6_tnl_parm_match(const struct tnl_print_nlmsg_info *info)
 {
+	const struct ip6_tnl_parm2 *p1 = info->p1;
+	const struct ip6_tnl_parm2 *p2 = info->p2;
+
 	return ((!p1->link || p1->link == p2->link) &&
 		(!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
 		(IN6_IS_ADDR_UNSPECIFIED(&p1->laddr) ||
@@ -336,91 +348,27 @@ static int ip6_tnl_parm_match(const struct ip6_tnl_parm2 *p1,
 		(!p1->flags || (p1->flags & p2->flags)));
 }
 
-static int do_tunnels_list(struct ip6_tnl_parm2 *p)
-{
-	char buf[512];
-	int err = -1;
-	FILE *fp = fopen("/proc/net/dev", "r");
-
-	if (fp == NULL) {
-		perror("fopen");
-		return -1;
-	}
-
-	/* skip two lines at the begenning of the file */
-	if (!fgets(buf, sizeof(buf), fp) ||
-	    !fgets(buf, sizeof(buf), fp)) {
-		fprintf(stderr, "/proc/net/dev read error\n");
-		goto end;
-	}
-
-	while (fgets(buf, sizeof(buf), fp) != NULL) {
-		char name[IFNAMSIZ];
-		int index, type;
-		struct ip6_tnl_parm2 p1;
-		char *ptr;
-
-		buf[sizeof(buf) - 1] = '\0';
-		ptr = strchr(buf, ':');
-		if (ptr == NULL ||
-		    (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) {
-			fprintf(stderr, "Wrong format for /proc/net/dev. Giving up.\n");
-			goto end;
-		}
-		if (p->name[0] && strcmp(p->name, name))
-			continue;
-		index = ll_name_to_index(name);
-		if (index == 0)
-			continue;
-		type = ll_index_to_type(index);
-		if (type == -1) {
-			fprintf(stderr, "Failed to get type of \"%s\"\n", name);
-			continue;
-		}
-		switch (type) {
-		case ARPHRD_TUNNEL6:
-		case ARPHRD_IP6GRE:
-			break;
-		default:
-			continue;
-		}
-		ip6_tnl_parm_init(&p1, 0);
-		if (type == ARPHRD_IP6GRE)
-			p1.proto = IPPROTO_GRE;
-		p1.link = index;
-		strcpy(p1.name, name);
-		if (tnl_get_ioctl(name, &p1))
-			continue;
-		if (!ip6_tnl_parm_match(p, &p1))
-			continue;
-		print_tunnel(&p1);
-		if (show_stats) {
-			struct rtnl_link_stats64 s;
-
-			if (!tnl_get_stats(ptr, &s))
-				tnl_print_stats(&s);
-		}
-		fputc('\n', stdout);
-	}
-	err = 0;
- end:
-	fclose(fp);
-	return err;
-}
-
 static int do_show(int argc, char **argv)
 {
-	struct ip6_tnl_parm2 p;
+	struct ip6_tnl_parm2 p, p1;
 
-	ll_init_map(&rth);
 	ip6_tnl_parm_init(&p, 0);
 	p.proto = 0;  /* default to any */
 
 	if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0)
 		return -1;
 
-	if (!p.name[0] || show_stats)
-		return do_tunnels_list(&p);
+	if (!p.name[0] || show_stats) {
+		struct tnl_print_nlmsg_info info = {
+			.p1    = &p,
+			.p2    = &p1,
+			.init  = ip6_tnl_parm_initialize,
+			.match = ip6_tnl_parm_match,
+			.print = print_tunnel,
+		};
+
+		return do_tunnels_list(&info);
+	}
 
 	if (tnl_get_ioctl(p.name, &p))
 		return -1;
diff --git a/ip/iptunnel.c b/ip/iptunnel.c
index ff201a7..1f04f95 100644
--- a/ip/iptunnel.c
+++ b/ip/iptunnel.c
@@ -286,8 +286,9 @@ static int do_del(int argc, char **argv)
 	return tnl_del_ioctl(tnl_defname(&p) ? : p.name, p.name, &p);
 }
 
-static void print_tunnel(struct ip_tunnel_parm *p)
+static void print_tunnel(const void *t)
 {
+	const struct ip_tunnel_parm *p = t;
 	struct ip_tunnel_6rd ip6rd = {};
 	char s1[1024];
 	char s2[1024];
@@ -373,13 +374,19 @@ static void print_tunnel(struct ip_tunnel_parm *p)
 		printf("%s  Checksum output packets.", _SL_);
 }
 
-/*
- * @p1: user specified parameter
- * @p2: database entry
- */
-static int ip_tunnel_parm_match(const struct ip_tunnel_parm *p1,
-				const struct ip_tunnel_parm *p2)
+
+static void ip_tunnel_parm_initialize(const struct tnl_print_nlmsg_info *info)
+{
+	struct ip_tunnel_parm *p2 = info->p2;
+
+	memset(p2, 0, sizeof(*p2));
+}
+
+static bool ip_tunnel_parm_match(const struct tnl_print_nlmsg_info *info)
 {
+	const struct ip_tunnel_parm *p1 = info->p1;
+	const struct ip_tunnel_parm *p2 = info->p2;
+
 	return ((!p1->link || p1->link == p2->link) &&
 		(!p1->name[0] || strcmp(p1->name, p2->name) == 0) &&
 		(!p1->iph.daddr || p1->iph.daddr == p2->iph.daddr) &&
@@ -387,87 +394,26 @@ static int ip_tunnel_parm_match(const struct ip_tunnel_parm *p1,
 		(!p1->i_key || p1->i_key == p2->i_key));
 }
 
-static int do_tunnels_list(struct ip_tunnel_parm *p)
-{
-	char buf[512];
-	int err = -1;
-	FILE *fp = fopen("/proc/net/dev", "r");
-
-	if (fp == NULL) {
-		perror("fopen");
-		return -1;
-	}
-
-	/* skip two lines at the begenning of the file */
-	if (!fgets(buf, sizeof(buf), fp) ||
-	    !fgets(buf, sizeof(buf), fp)) {
-		fprintf(stderr, "/proc/net/dev read error\n");
-		goto end;
-	}
-
-	while (fgets(buf, sizeof(buf), fp) != NULL) {
-		char name[IFNAMSIZ];
-		int index, type;
-		struct ip_tunnel_parm p1;
-		char *ptr;
-
-		buf[sizeof(buf) - 1] = '\0';
-		ptr = strchr(buf, ':');
-		if (ptr == NULL ||
-		    (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) {
-			fprintf(stderr, "Wrong format for /proc/net/dev. Giving up.\n");
-			goto end;
-		}
-		if (p->name[0] && strcmp(p->name, name))
-			continue;
-		index = ll_name_to_index(name);
-		if (index == 0)
-			continue;
-		type = ll_index_to_type(index);
-		if (type == -1) {
-			fprintf(stderr, "Failed to get type of \"%s\"\n", name);
-			continue;
-		}
-		switch (type) {
-		case ARPHRD_TUNNEL:
-		case ARPHRD_IPGRE:
-		case ARPHRD_SIT:
-			break;
-		default:
-			continue;
-		}
-		memset(&p1, 0, sizeof(p1));
-		if (tnl_get_ioctl(name, &p1))
-			continue;
-		if (!ip_tunnel_parm_match(p, &p1))
-			continue;
-		print_tunnel(&p1);
-		if (show_stats) {
-			struct rtnl_link_stats64 s;
-
-			if (!tnl_get_stats(ptr, &s))
-				tnl_print_stats(&s);
-		}
-		fputc('\n', stdout);
-	}
-	err = 0;
- end:
-	fclose(fp);
-	return err;
-}
-
 static int do_show(int argc, char **argv)
 {
-	struct ip_tunnel_parm p;
+	struct ip_tunnel_parm p, p1;
 	const char *basedev;
 
-	ll_init_map(&rth);
 	if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0)
 		return -1;
 
 	basedev = tnl_defname(&p);
-	if (!basedev)
-		return do_tunnels_list(&p);
+	if (!basedev) {
+		struct tnl_print_nlmsg_info info = {
+			.p1    = &p,
+			.p2    = &p1,
+			.init  = ip_tunnel_parm_initialize,
+			.match = ip_tunnel_parm_match,
+			.print = print_tunnel,
+		};
+
+		return do_tunnels_list(&info);
+	}
 
 	if (tnl_get_ioctl(p.name[0] ? p.name : basedev, &p))
 		return -1;
diff --git a/ip/tunnel.c b/ip/tunnel.c
index 06533cf..7030995 100644
--- a/ip/tunnel.c
+++ b/ip/tunnel.c
@@ -33,6 +33,7 @@
 #include <linux/if.h>
 #include <linux/ip.h>
 #include <linux/if_tunnel.h>
+#include <linux/if_arp.h>
 
 #include "utils.h"
 #include "tunnel.h"
@@ -307,37 +308,7 @@ void tnl_print_endpoint(const char *name, const struct rtattr *rta, int family)
 	}
 }
 
-int tnl_get_stats(const char *buf, struct rtnl_link_stats64 *s)
-{
-	/* rx */
-	__u64 *rx_bytes   = &s->rx_bytes;
-	__u64 *rx_packets = &s->rx_packets;
-	__u64 *rx_errs    = &s->rx_errors;
-	__u64 *rx_drops   = &s->rx_dropped;
-	__u64 *rx_fifo    = &s->rx_fifo_errors;
-	__u64 *rx_frame   = &s->rx_frame_errors;
-	__u64 *rx_multi   = &s->multicast;
-	/* tx */
-	__u64 *tx_bytes   = &s->tx_bytes;
-	__u64 *tx_packets = &s->tx_packets;
-	__u64 *tx_errs    = &s->tx_errors;
-	__u64 *tx_drops   = &s->tx_dropped;
-	__u64 *tx_fifo    = &s->tx_fifo_errors;
-	__u64 *tx_carrier = &s->tx_carrier_errors;
-	__u64 *tx_colls   = &s->collisions;
-
-	if (sscanf(buf,
-		   "%llu%llu%llu%llu%llu%llu%llu%*d%llu%llu%llu%llu%llu%llu%llu",
-		   rx_bytes, rx_packets, rx_errs, rx_drops,
-		   rx_fifo, rx_frame, rx_multi,
-		   tx_bytes, tx_packets, tx_errs, tx_drops,
-		   tx_fifo, tx_colls, tx_carrier) != 14)
-		return -1;
-
-	return 0;
-}
-
-void tnl_print_stats(const struct rtnl_link_stats64 *s)
+static void tnl_print_stats(const struct rtnl_link_stats64 *s)
 {
 	printf("%s", _SL_);
 	printf("RX: Packets    Bytes        Errors CsumErrs OutOfSeq Mcasts%s", _SL_);
@@ -349,3 +320,87 @@ void tnl_print_stats(const struct rtnl_link_stats64 *s)
 	       s->tx_packets, s->tx_bytes, s->tx_errors, s->collisions,
 	       s->tx_carrier_errors, s->tx_dropped);
 }
+
+static int print_nlmsg_tunnel(const struct sockaddr_nl *who,
+			      struct nlmsghdr *n, void *arg)
+{
+	struct tnl_print_nlmsg_info *info = arg;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+	struct rtattr *tb[IFLA_MAX+1];
+	const char *name, *n1;
+
+	if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+		return 0;
+
+	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi)))
+		return -1;
+
+	if (preferred_family == AF_INET) {
+		switch (ifi->ifi_type) {
+		case ARPHRD_TUNNEL:
+		case ARPHRD_IPGRE:
+		case ARPHRD_SIT:
+			break;
+		default:
+			return 0;
+		}
+	} else {
+		switch (ifi->ifi_type) {
+		case ARPHRD_TUNNEL6:
+		case ARPHRD_IP6GRE:
+			break;
+		default:
+			return 0;
+		}
+	}
+
+	parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
+
+	if (!tb[IFLA_IFNAME])
+		return 0;
+
+	name = rta_getattr_str(tb[IFLA_IFNAME]);
+
+	/* Assume p1->name[IFNAMSIZ] is first field of structure */
+	n1 = info->p1;
+	if (n1[0] && strcmp(n1, name))
+		return 0;
+
+	info->ifi = ifi;
+	info->init(info);
+
+	/* TODO: parse netlink attributes */
+	if (tnl_get_ioctl(name, info->p2))
+		return 0;
+
+	if (!info->match(info))
+		return 0;
+
+	info->print(info->p2);
+	if (show_stats) {
+		struct rtnl_link_stats64 s;
+
+		if (get_rtnl_link_stats_rta(&s, tb) <= 0)
+			return -1;
+
+		tnl_print_stats(&s);
+	}
+	fputc('\n', stdout);
+
+	return 0;
+}
+
+int do_tunnels_list(struct tnl_print_nlmsg_info *info)
+{
+	if (rtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK) < 0) {
+		perror("Cannot send dump request\n");
+		return -1;
+	}
+
+	if (rtnl_dump_filter(&rth, print_nlmsg_tunnel, info) < 0) {
+		fprintf(stderr, "Dump terminated\n");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/ip/tunnel.h b/ip/tunnel.h
index 5fe488b..e530d07 100644
--- a/ip/tunnel.h
+++ b/ip/tunnel.h
@@ -21,10 +21,25 @@
 #ifndef __TUNNEL_H__
 #define __TUNNEL_H__ 1
 
+#include <stdbool.h>
 #include <linux/types.h>
 
 struct rtattr;
-struct rtnl_link_stats64;
+struct ifinfomsg;
+
+extern struct rtnl_handle rth;
+
+struct tnl_print_nlmsg_info {
+	const struct ifinfomsg *ifi;
+	const void *p1;
+	void *p2;
+
+	void (*init)(const struct tnl_print_nlmsg_info *info);
+	bool (*match)(const struct tnl_print_nlmsg_info *info);
+	void (*print)(const void *t);
+};
+
+int do_tunnels_list(struct tnl_print_nlmsg_info *info);
 
 const char *tnl_strproto(__u8 proto);
 
@@ -40,8 +55,5 @@ void tnl_print_encap(struct rtattr *tb[],
 		     int encap_sport, int encap_dport);
 void tnl_print_endpoint(const char *name,
 			const struct rtattr *rta, int family);
-void tnl_print_stats(const struct rtnl_link_stats64 *s);
-
-int tnl_get_stats(const char *buf, struct rtnl_link_stats64 *s);
 
 #endif
