diff --git a/Makefile.am b/Makefile.am
index 30cda5b1fc6f..11acdab1a65e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,6 +30,7 @@ ethtool_SOURCES += \
 		  netlink/nlsock.h netlink/strset.c netlink/strset.h \
 		  netlink/monitor.c netlink/bitset.c netlink/bitset.h \
 		  netlink/settings.c netlink/parser.c netlink/parser.h \
+		  netlink/permaddr.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h \
 		  uapi/linux/rtnetlink.h uapi/linux/if_link.h
diff --git a/ethtool.c b/ethtool.c
index baa6458bb486..1b4e08b6e60f 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5372,6 +5372,7 @@ static const struct option args[] = {
 	{
 		.opts	= "-P|--show-permaddr",
 		.func	= do_permaddr,
+		.nlfunc	= nl_permaddr,
 		.help	= "Show permanent hardware address"
 	},
 	{
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 612002e8228b..d5b3cd92d4ab 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -17,6 +17,7 @@ void netlink_done(struct cmd_context *ctx);
 
 int nl_gset(struct cmd_context *ctx);
 int nl_sset(struct cmd_context *ctx);
+int nl_permaddr(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void nl_monitor_usage(void);
@@ -38,6 +39,7 @@ static inline void nl_monitor_usage(void)
 
 #define nl_gset			NULL
 #define nl_sset			NULL
+#define nl_permaddr		NULL
 
 #endif /* ETHTOOL_ENABLE_NETLINK */
 
diff --git a/netlink/netlink.h b/netlink/netlink.h
index 3ab5f2329e2f..db078d28fabb 100644
--- a/netlink/netlink.h
+++ b/netlink/netlink.h
@@ -27,6 +27,7 @@ struct nl_context {
 	uint32_t		ethnl_mongrp;
 	struct nl_socket	*ethnl_socket;
 	struct nl_socket	*ethnl2_socket;
+	struct nl_socket	*rtnl_socket;
 	bool			is_monitor;
 	uint32_t		filter_cmds[CMDMASK_WORDS];
 	const char		*filter_devname;
@@ -76,4 +77,11 @@ static inline int netlink_init_ethnl2_socket(struct nl_context *nlctx)
 	return nlsock_init(nlctx, &nlctx->ethnl2_socket, NETLINK_GENERIC);
 }
 
+static inline int netlink_init_rtnl_socket(struct nl_context *nlctx)
+{
+	if (nlctx->rtnl_socket)
+		return 0;
+	return nlsock_init(nlctx, &nlctx->rtnl_socket, NETLINK_ROUTE);
+}
+
 #endif /* ETHTOOL_NETLINK_INT_H__ */
diff --git a/netlink/permaddr.c b/netlink/permaddr.c
new file mode 100644
index 000000000000..006eac6c0094
--- /dev/null
+++ b/netlink/permaddr.c
@@ -0,0 +1,114 @@
+/*
+ * permaddr.c - netlink implementation of permanent address request
+ *
+ * Implementation of "ethtool -P <dev>"
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_link.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+
+/* PERMADDR_GET */
+
+static int permaddr_prep_request(struct nl_socket *nlsk)
+{
+	unsigned int nlm_flags = NLM_F_REQUEST | NLM_F_ACK;
+	struct nl_context *nlctx = nlsk->nlctx;
+	const char *devname = nlctx->ctx->devname;
+	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
+	struct ifinfomsg *ifinfo;
+	struct nlmsghdr *nlhdr;
+	int ret;
+
+	if (devname && !strcmp(devname, WILDCARD_DEVNAME)) {
+		devname = NULL;
+		nlm_flags |= NLM_F_DUMP;
+	}
+	nlctx->is_dump = !devname;
+
+        ret = msgbuff_realloc(msgbuff, MNL_SOCKET_BUFFER_SIZE);
+        if (ret < 0)
+                return ret;
+        memset(msgbuff->buff, '\0', NLMSG_HDRLEN + sizeof(*ifinfo));
+
+	nlhdr = mnl_nlmsg_put_header(msgbuff->buff);
+	nlhdr->nlmsg_type = RTM_GETLINK;
+	nlhdr->nlmsg_flags = nlm_flags;
+	msgbuff->nlhdr = nlhdr;
+	ifinfo = mnl_nlmsg_put_extra_header(nlhdr, sizeof(*ifinfo));
+
+	if (devname) {
+		uint16_t type = IFLA_IFNAME;
+
+		if (strlen(devname) >= IFNAMSIZ)
+			type = IFLA_ALT_IFNAME;
+		if (ethnla_put_strz(msgbuff, type, devname))
+			return -EMSGSIZE;
+	}
+	if (ethnla_put_u32(msgbuff, IFLA_EXT_MASK, RTEXT_FILTER_SKIP_STATS))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+int permaddr_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[__IFLA_MAX] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	const uint8_t *permaddr;
+	unsigned int i;
+	int ret;
+
+	if (nlhdr->nlmsg_type != RTM_NEWLINK)
+		goto err;
+	ret = mnl_attr_parse(nlhdr, sizeof(struct ifinfomsg), attr_cb,
+			     &tb_info);
+	if (ret < 0 || !tb[IFLA_IFNAME])
+		goto err;
+	nlctx->devname = mnl_attr_get_str(tb[IFLA_IFNAME]);
+	if (!dev_ok(nlctx))
+		goto err;
+
+	if (!tb[IFLA_PERM_ADDRESS]) {
+		if (!nlctx->is_dump)
+			printf("Permanent address: not set\n");
+		return MNL_CB_OK;
+	}
+
+	if (nlctx->is_dump)
+		printf("Permanent address of %s:", nlctx->devname);
+	else
+		printf("Permanent address:");
+	permaddr = mnl_attr_get_payload(tb[IFLA_PERM_ADDRESS]);
+	for (i = 0; i < mnl_attr_get_payload_len(tb[IFLA_PERM_ADDRESS]); i++)
+		printf("%c%02x", i ? ':' : ' ', permaddr[i]);
+	putchar('\n');
+	return MNL_CB_OK;
+
+err:
+	if (nlctx->is_dump || nlctx->is_monitor)
+		return MNL_CB_OK;
+	nlctx->exit_code = 2;
+	return MNL_CB_ERROR;
+}
+
+int nl_permaddr(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	int ret;
+
+	ret = netlink_init_rtnl_socket(nlctx);
+	if (ret < 0)
+		return ret;
+	ret = permaddr_prep_request(nlctx->rtnl_socket);
+	if (ret < 0)
+		return ret;
+	return nlsock_send_get_request(nlctx->rtnl_socket, permaddr_reply_cb);
+}
