diff -urpN iproute2-6.17.0/include/libnetlink.h iproute2-6.17.0-iparp/include/libnetlink.h
--- iproute2-6.17.0/include/libnetlink.h	2025-09-29 17:43:02.000000000 +0300
+++ iproute2-6.17.0-iparp/include/libnetlink.h	2025-10-12 13:04:03.653957329 +0300
@@ -74,6 +74,8 @@ int rtnl_neighdump_req(struct rtnl_handl
 	__attribute__((warn_unused_result));
 int rtnl_neightbldump_req(struct rtnl_handle *rth, int family)
 	__attribute__((warn_unused_result));
+int rtnl_arpdump_req(struct rtnl_handle *rth, int family)
+	__attribute__((warn_unused_result));
 int rtnl_mdbdump_req(struct rtnl_handle *rth, int family)
 	__attribute__((warn_unused_result));
 int rtnl_brvlandump_req(struct rtnl_handle *rth, int family, __u32 dump_flags)
diff -urpN iproute2-6.17.0/include/uapi/linux/rtnetlink.h iproute2-6.17.0-iparp/include/uapi/linux/rtnetlink.h
--- iproute2-6.17.0/include/uapi/linux/rtnetlink.h	2025-09-29 17:43:02.000000000 +0300
+++ iproute2-6.17.0-iparp/include/uapi/linux/rtnetlink.h	2025-10-12 13:04:03.654274068 +0300
@@ -206,6 +206,11 @@ enum {
 #define RTM_MAX		(((__RTM_MAX + 3) & ~3) - 1)
 };
 
+#define RTM_NEWARPRULE	iparp_RTM_NEWARPRULE
+#define RTM_DELARPRULE	iparp_RTM_DELARPRULE
+#define RTM_GETARPRULE	iparp_RTM_GETARPRULE
+extern __u16 iparp_RTM_NEWARPRULE, iparp_RTM_DELARPRULE, iparp_RTM_GETARPRULE;
+
 #define RTM_NR_MSGTYPES	(RTM_MAX + 1 - RTM_BASE)
 #define RTM_NR_FAMILIES	(RTM_NR_MSGTYPES >> 2)
 #define RTM_FAM(cmd)	(((cmd) - RTM_BASE) >> 2)
@@ -792,6 +797,9 @@ enum rtnetlink_groups {
 };
 #define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)
 
+#define RTNLGRP_ARP	iparp_RTNLGRP_ARP
+extern enum rtnetlink_groups iparp_RTNLGRP_ARP;
+
 /* TC action piece */
 struct tcamsg {
 	unsigned char	tca_family;
@@ -841,6 +849,54 @@ enum {
 
 /* End of information exported to user level */
 
+/******************************************************************************
+ *		Definitions used in ARP tables administration
+ ****/
+
+#define ARPA_TABLE_INPUT	0
+#define ARPA_TABLE_OUTPUT	1
+#define ARPA_TABLE_FORWARD	2
+#define ARPA_TABLE_ALL		-1
+
+#define ARPM_F_PREFSRC		0x0001
+#define ARPM_F_WILDIIF		0x0002
+#define ARPM_F_WILDOIF		0x0004
+#define ARPM_F_BROADCAST	0x0008
+#define ARPM_F_UNICAST		0x0010
+
+struct arpmsg
+{
+	unsigned char		arpm_family;
+	unsigned char		arpm_table;
+	unsigned char		arpm_action;
+	unsigned char		arpm_from_len;
+	unsigned char		arpm_to_len;
+	unsigned char		arpm__pad1;
+	unsigned short		arpm__pad2;
+	unsigned		arpm_pref;
+	unsigned		arpm_flags;
+};
+
+enum
+{
+	ARPA_UNSPEC,
+	ARPA_FROM,			/* FROM IP prefix	*/
+	ARPA_TO,			/* TO IP prefix		*/
+	ARPA_LLFROM,			/* FROM LL prefix	*/
+	ARPA_LLTO,			/* TO LL prefix		*/
+	ARPA_LLSRC,			/* New SRC lladdr	*/
+	ARPA_LLDST,			/* New DST lladdr	*/
+	ARPA_IIF,			/* In interface prefix	*/
+	ARPA_OIF,			/* Out interface prefix	*/
+	ARPA_SRC,			/* New IP SRC		*/
+	ARPA_DST,			/* New IP DST, not used	*/
+	ARPA_PACKETS,			/* Packets		*/
+};
+
+#define ARPA_MAX	ARPA_PACKETS
+
+#define ARPA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct arpmsg))))
+#define ARPA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct arpmsg))
 
 
 #endif /* __LINUX_RTNETLINK_H */
diff -urpN iproute2-6.17.0/ip/Makefile iproute2-6.17.0-iparp/ip/Makefile
--- iproute2-6.17.0/ip/Makefile	2025-09-29 17:43:02.000000000 +0300
+++ iproute2-6.17.0-iparp/ip/Makefile	2025-10-12 13:04:03.654508953 +0300
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \
+IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o iparp.o ipnetns.o \
     rtm_map.o iptunnel.o ip6tunnel.o tunnel.o ipneigh.o ipntable.o iplink.o \
     ipmaddr.o ipmonitor.o ipmroute.o ipprefix.o iptuntap.o iptoken.o \
     ipxfrm.o xfrm_state.o xfrm_policy.o xfrm_monitor.o iplink_dummy.o \
diff -urpN iproute2-6.17.0/ip/ip.c iproute2-6.17.0-iparp/ip/ip.c
--- iproute2-6.17.0/ip/ip.c	2025-09-29 17:43:02.000000000 +0300
+++ iproute2-6.17.0-iparp/ip/ip.c	2025-10-12 13:04:03.654685029 +0300
@@ -59,8 +59,8 @@ static void usage(void)
 	fprintf(stderr,
 		"Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }\n"
 		"       ip [ -force ] -batch filename\n"
-		"where  OBJECT := { address | addrlabel | fou | help | ila | ioam | l2tp | link |\n"
-		"                   macsec | maddress | monitor | mptcp | mroute | mrule |\n"
+		"where  OBJECT := { address | addrlabel | arp | fou | help | ila | ioam | l2tp |\n"
+		"                   link | macsec | maddress | monitor | mptcp | mroute | mrule |\n"
 		"                   neighbor | neighbour | netconf | netns | nexthop | ntable |\n"
 		"                   ntbl | route | rule | sr | stats | tap | tcpmetrics |\n"
 		"                   token | tunnel | tuntap | vrf | xfrm }\n"
@@ -87,6 +87,7 @@ static const struct cmd {
 } cmds[] = {
 	{ "address",	do_ipaddr },
 	{ "addrlabel",	do_ipaddrlabel },
+	{ "arp",	do_iparprule },
 	{ "maddress",	do_multiaddr },
 	{ "route",	do_iproute },
 	{ "rule",	do_iprule },
diff -urpN iproute2-6.17.0/ip/ip_common.h iproute2-6.17.0-iparp/ip/ip_common.h
--- iproute2-6.17.0/ip/ip_common.h	2025-09-29 17:43:02.000000000 +0300
+++ iproute2-6.17.0-iparp/ip/ip_common.h	2025-10-12 13:04:03.654866993 +0300
@@ -72,6 +72,9 @@ int do_iptunnel(int argc, char **argv);
 int do_ip6tunnel(int argc, char **argv);
 int do_iptuntap(int argc, char **argv);
 int do_iplink(int argc, char **argv);
+int do_iparprule(int argc, char **argv);
+int print_arprule(struct nlmsghdr *n, void *arg);
+int iparp_detect_version(int needed);
 int do_ipmacsec(int argc, char **argv);
 int do_ipmonitor(int argc, char **argv);
 int do_multiaddr(int argc, char **argv);
diff -urpN iproute2-6.17.0/ip/iparp.c iproute2-6.17.0-iparp/ip/iparp.c
--- iproute2-6.17.0/ip/iparp.c	1970-01-01 02:00:00.000000000 +0200
+++ iproute2-6.17.0-iparp/ip/iparp.c	2025-10-12 13:04:24.610737947 +0300
@@ -0,0 +1,1033 @@
+/*
+ * iparp.c		"ip arp".
+ *
+ * Authors:	Julian Anastasov <ja@ssi.bg>, March 2002-2023
+ *
+ *		This program is free software; you can redistribute it and/or
+ *		modify it under the terms of the GNU General Public License
+ *		version 2 as published by the Free Software Foundation;
+ *
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <linux/if_arp.h>
+#include <linux/version.h>
+#include <sys/utsname.h>
+
+#include "rt_names.h"
+#include "utils.h"
+#include "ip_common.h"
+#include "json_print.h"
+
+static void usage(void) __attribute__((noreturn));
+
+#define CMD_LIST	0x0001	/* list, lst, show		*/
+#define CMD_APPEND	0x0002	/* append			*/
+#define CMD_PREPEND	0x0004	/* prepend, insert		*/
+#define CMD_ADD		0x0008	/* add, create			*/
+#define CMD_DEL		0x0010	/* delete, remove		*/
+#define CMD_CHANGE	0x0020	/* change, chg, update		*/
+#define CMD_REPLACE	0x0040	/* replace, set			*/
+#define CMD_FLUSH	0x0080	/* flush			*/
+#define CMD_TEST	0x0100	/* test				*/
+
+typedef struct {
+	char	*name;
+	int	code;
+} strarr_t;
+
+static strarr_t cmds[] = {
+	{	"list",		CMD_LIST	},
+	{	"lst",		CMD_LIST	},
+	{	"show",		CMD_LIST	},
+	{	"append",	CMD_APPEND	},
+	{	"prepend",	CMD_PREPEND	},
+	{	"insert",	CMD_PREPEND	},
+	{	"add",		CMD_ADD		},
+	{	"create",	CMD_ADD		},
+	{	"delete",	CMD_DEL		},
+	{	"remove",	CMD_DEL		},
+	{	"change",	CMD_CHANGE	},
+	{	"chg",		CMD_CHANGE	},
+	{	"update",	CMD_CHANGE	},
+	{	"replace",	CMD_REPLACE	},
+	{	"set",		CMD_REPLACE	},
+	{	"flush",	CMD_FLUSH	},
+	{	"test",		CMD_TEST	},
+
+	{	NULL,		0		}
+};
+
+static strarr_t arp_tables[] = {
+	{	"input",	ARPA_TABLE_INPUT	},
+	{	"output",	ARPA_TABLE_OUTPUT	},
+	{	"forward",	ARPA_TABLE_FORWARD	},
+
+	{	NULL,		0		}
+};
+
+static strarr_t arp_actions[] = {
+	{	"deny",		0	},
+	{	"allow",	1	},
+
+	{	NULL,		0	}
+};
+
+static int cmd;
+
+struct compat_iparp_codes {
+	__u32			max_version;
+	__u16			rule_base;
+	enum rtnetlink_groups	group;
+};
+
+/* The kernel patch is not official and always appends new constants,
+ * make sure we cover many kernels with same binary.
+ */
+static struct compat_iparp_codes code_table[] = {
+	/* We are not sure for the codes in future kernels */
+#define MAX_IPARP_VERSION	KERNEL_VERSION(6, 17, 99)
+
+	/* A kernel can add rules and/or groups, here is the list
+	 * of such kernels, from latest to oldest supported kernel version
+	 */
+	{ KERNEL_VERSION(6, 14, 0), 124, 40 },
+	{ KERNEL_VERSION(5, 18, 0), 124, 37 },
+	{ KERNEL_VERSION(5, 17, 0), 120, 35 },
+	{ KERNEL_VERSION(5, 13, 0), 120, 34 },
+	{ KERNEL_VERSION(5, 6, 0), 116, 34 },
+	{ KERNEL_VERSION(5, 5, 0), 112, 33 },
+	{ KERNEL_VERSION(5, 3, 0), 108, 33 },
+	{ KERNEL_VERSION(4, 19, 0), 104, 32 },
+	{ KERNEL_VERSION(4, 13, 0), 100, 32 },
+	{ KERNEL_VERSION(4, 11, 0), 96, 30 },
+	{ KERNEL_VERSION(4, 7, 0), 96, 29 },
+	{ KERNEL_VERSION(4, 1, 0), 92, 29 },
+	{ KERNEL_VERSION(4, 0, 0), 92, 27 },
+	{ KERNEL_VERSION(3, 8, 0), 88, 27 },
+	{ KERNEL_VERSION(3, 7, 0), 80, 24 },
+	{ KERNEL_VERSION(3, 1, 0), 80, 23 },
+	{ KERNEL_VERSION(2, 6, 29), 80, 23 },
+	{ KERNEL_VERSION(2, 6, 28), 76, 23 },
+	{ KERNEL_VERSION(2, 6, 25), 76, 21 },
+	{ KERNEL_VERSION(2, 6, 24), 72, 21 },
+	{ KERNEL_VERSION(2, 6, 19), 68, 20 },
+	{ KERNEL_VERSION(2, 6, 15), 68, 19 },
+	{ KERNEL_VERSION(2, 6, 13), 68, 15 },
+};
+
+int iparp_detect_version(int needed)
+{
+	struct utsname un;
+	int m, p, s, v, i;
+
+	if (iparp_RTM_NEWARPRULE)
+		return 1;
+	if (uname(&un) < 0)
+		goto err;
+	sscanf(un.release, "%d.%d.%d", &m, &p, &s);
+	v = KERNEL_VERSION(m, p, s);
+	if (v >= MAX_IPARP_VERSION)
+		goto err;
+	for (i = 0; i < ARRAY_SIZE(code_table); i++) {
+		if (v >= code_table[i].max_version) {
+			iparp_RTM_NEWARPRULE = code_table[i].rule_base + 0;
+			iparp_RTM_DELARPRULE = code_table[i].rule_base + 1;
+			iparp_RTM_GETARPRULE = code_table[i].rule_base + 2;
+			iparp_RTNLGRP_ARP = code_table[i].group;
+			return 1;
+		}
+	}
+	
+err:
+	if (needed) {
+		fprintf(stderr, "ip arp: unsupported kernel version\n");
+		exit(1);
+	}
+	return 0;
+}
+
+static struct
+{
+	int tb;
+	int pref;
+	int action;
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	inet_prefix rto;
+	inet_prefix mto;
+	inet_prefix rfrom;
+	inet_prefix mfrom;
+	inet_prefix rsrc;
+	__u8 llfrom[MAX_ADDR_LEN];
+	__u8 llto[MAX_ADDR_LEN];
+	__u8 llsrc[MAX_ADDR_LEN];
+	__u8 lldst[MAX_ADDR_LEN];
+	int llfrom_len;
+	int llto_len;
+	int llsrc_len;
+	int lldst_len;
+	int broadcasts;
+	int unicasts;
+	char *iif;
+	char *oif;
+	__u32 packets;
+} f = {
+	tb:		ARPA_TABLE_ALL,
+	action:		-1,
+};
+
+static strarr_t *lookup_strarr(strarr_t *arr, char *name)
+{
+	strarr_t *p;
+
+	for (p = arr; p->name; p++) {
+		if (matches(name, p->name) == 0)
+			return p;
+	}
+	return NULL;
+}
+
+static strarr_t *lookup_strarr_code(strarr_t *arr, int code)
+{
+	strarr_t *p;
+
+	for (p = arr; p->name; p++) {
+		if (p->code == code)
+			return p;
+	}
+	return NULL;
+}
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: ip arp [ list | flush ] [ RULE ]\n");
+	fprintf(stderr, "       ip arp [ append | prepend | add | del | change | replace | test ] RULE\n");
+	fprintf(stderr, "RULE := [ table TABLE_NAME ] [ pref NUMBER ] [ from PREFIX ] [ to PREFIX ]\n");
+	fprintf(stderr, "           [ iif STRING ] [ oif STRING ] [ llfrom PREFIX ] [ llto PREFIX ]\n");
+	fprintf(stderr, "           [ broadcasts ] [ unicasts ] [ ACTION ] [ ALTER ]\n");
+	fprintf(stderr, "TABLE_NAME := [ input | forward | output ]\n");
+	fprintf(stderr, "ACTION := [ deny | allow ]\n");
+	fprintf(stderr, "ALTER := [ src IP ] [ llsrc LLADDR ] [ lldst LLADDR ]\n");
+	exit(-1);
+}
+
+static int flush_update(void)
+{
+	if (rtnl_send_check(&rth, f.flushb, f.flushp) < 0) {
+		perror("Failed to send flush request\n");
+		return -1;
+	}
+	f.flushp = 0;
+	return 0;
+}
+
+int print_arprule(struct nlmsghdr *n, void *arg)
+{
+	FILE *fp = (FILE*)arg;
+	struct arpmsg *r = NLMSG_DATA(n);
+	int len = n->nlmsg_len;
+	int host_len = 32, family = AF_INET;
+	struct rtattr * tb[RTA_MAX+1];
+	inet_prefix vto, vfrom, vsrc;
+	__u32 packets = 0;
+	SPRINT_BUF(b1);
+
+	if (n->nlmsg_type != RTM_NEWARPRULE && n->nlmsg_type != RTM_DELARPRULE)
+		return 0;
+
+	if (f.flushb && n->nlmsg_type != RTM_NEWARPRULE)
+		return 0;
+
+	len -= NLMSG_LENGTH(sizeof(*r));
+	if (len < 0)
+		return -1;
+
+	if (f.tb >= 0 && f.tb != r->arpm_table)
+		return 0;
+
+	if (f.pref > 0 && f.pref != r->arpm_pref)
+		return 0;
+
+	if (f.action >= 0 && f.action != r->arpm_action)
+		return 0;
+
+	if (f.rto.family && f.rto.bitlen > r->arpm_to_len)
+		return 0;
+	if (f.mto.family && f.mto.bitlen >= 0 &&
+	    f.mto.bitlen < r->arpm_to_len)
+		return 0;
+
+	if (f.rfrom.family && f.rfrom.bitlen > r->arpm_from_len)
+		return 0;
+	if (f.mfrom.family && f.mfrom.bitlen >= 0 &&
+	    f.mfrom.bitlen < r->arpm_from_len)
+		return 0;
+
+	if (f.broadcasts && !(r->arpm_flags & ARPM_F_BROADCAST))
+		return 0;
+
+	if (f.unicasts && !(r->arpm_flags & ARPM_F_UNICAST))
+		return 0;
+
+	memset(tb, 0, sizeof(tb));
+	parse_rtattr(tb, ARPA_MAX, ARPA_RTA(r), len);
+
+	memset(&vto, 0, sizeof(vto));
+	if (tb[ARPA_TO])
+		memcpy(&vto.data, RTA_DATA(tb[ARPA_TO]), (r->arpm_to_len+7)/8);
+
+	memset(&vfrom, 0, sizeof(vfrom));
+	if (tb[ARPA_FROM])
+		memcpy(&vfrom.data, RTA_DATA(tb[ARPA_FROM]),
+			(r->arpm_from_len+7)/8);
+
+	memset(&vsrc, 0, sizeof(vsrc));
+	if (tb[ARPA_SRC])
+		memcpy(&vsrc.data, RTA_DATA(tb[ARPA_SRC]), 4);
+
+	if (f.rto.family && inet_addr_match(&vto, &f.rto, f.rto.bitlen))
+		return 0;
+	if (f.mto.family && f.mto.bitlen >= 0 &&
+	    inet_addr_match(&vto, &f.mto, r->arpm_to_len))
+		return 0;
+
+	if (f.rfrom.family && inet_addr_match(&vfrom, &f.rfrom, f.rfrom.bitlen))
+		return 0;
+	if (f.mfrom.family && f.mfrom.bitlen >= 0 &&
+	    inet_addr_match(&vfrom, &f.mfrom, r->arpm_from_len))
+		return 0;
+
+	if (f.rsrc.family && inet_addr_match(&vsrc, &f.rsrc, f.rsrc.bitlen))
+		return 0;
+
+	if (f.iif) {
+		char *dev, *cp;
+		int size;
+
+		if (!tb[ARPA_IIF])
+			return 0;
+		dev = (char*)RTA_DATA(tb[ARPA_IIF]);
+		size = strlen(dev);
+		if ((cp = strchr(f.iif, '+')) && size > cp - f.iif)
+			size = cp - f.iif;
+		if (strncmp(dev, f.iif, size) ||
+		    (0 != f.iif[size] && '+' != f.iif[size]))
+			return 0;
+	}
+
+	if (f.oif) {
+		char *dev, *cp;
+		int size;
+
+		if (!tb[ARPA_OIF])
+			return 0;
+		dev = (char*)RTA_DATA(tb[ARPA_OIF]);
+		size = strlen(dev);
+		if ((cp = strchr(f.oif, '+')) && size > cp - f.oif)
+			size = cp - f.oif;
+		if (strncmp(dev, f.oif, size) ||
+		    (0 != f.oif[size] && '+' != f.oif[size]))
+			return 0;
+	}
+
+	if (f.packets) {
+		if (tb[ARPA_PACKETS])
+			memcpy(&packets, RTA_DATA(tb[ARPA_PACKETS]), 4);
+		if (packets < f.packets)
+			return 0;
+	}
+
+	if (f.flushb) {
+		struct nlmsghdr *fn;
+		if (NLMSG_ALIGN(f.flushp) + n->nlmsg_len > f.flushe) {
+			if (flush_update())
+				return -1;
+		}
+		fn = (struct nlmsghdr*)(f.flushb + NLMSG_ALIGN(f.flushp));
+		memcpy(fn, n, n->nlmsg_len);
+		fn->nlmsg_type = RTM_DELARPRULE;
+		fn->nlmsg_flags = NLM_F_REQUEST;
+		fn->nlmsg_seq = ++rth.seq;
+		f.flushp = (((char*)fn) + n->nlmsg_len) - f.flushb;
+		f.flushed++;
+		if (show_stats < 2)
+			return 0;
+	}
+
+	print_headers(fp, "[ARPRULE]");
+
+	open_json_object(NULL);
+
+	if (n->nlmsg_type == RTM_DELARPRULE)
+		print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+	{
+		strarr_t *p = lookup_strarr_code(arp_tables, r->arpm_table);
+		char *text = p? p->name:"unknown";
+		char buf1[64];
+
+		snprintf(buf1, sizeof(buf1), "%s ", text);
+		print_null(PRINT_ANY, text, buf1, NULL);
+	}
+
+	print_uint(PRINT_ANY, "rule", "rule %u ", r->arpm_pref);
+
+	{
+		strarr_t *p = lookup_strarr_code(arp_actions, r->arpm_action);
+		char *text = p? p->name:"unk";
+		char buf1[64];
+
+		snprintf(buf1, sizeof(buf1), "%s ", text);
+		print_null(PRINT_ANY, text, buf1, NULL);
+	}
+
+	if (tb[ARPA_FROM]) {
+		const char *a = rt_addr_n2a_rta(family, tb[ARPA_FROM]);
+
+		print_string(PRINT_FP, NULL, "from ", NULL);
+		print_color_string(PRINT_ANY, ifa_family_color(family),
+					"from", "%s", a);
+		if (r->arpm_from_len != host_len)
+			print_uint(PRINT_ANY, "fromlen", "/%u",
+					r->arpm_from_len);
+	} else if (r->arpm_from_len) {
+		print_string(PRINT_ANY, "from", "from %s", "0");
+		print_uint(PRINT_ANY, "fromlen", "/%u", r->arpm_from_len);
+	} else {
+		print_string(PRINT_ANY, "from", "from %s", "all");
+	}
+
+	if (tb[ARPA_TO]) {
+		const char *a = rt_addr_n2a_rta(family, tb[ARPA_TO]);
+
+		print_string(PRINT_FP, NULL, " to ", NULL);
+		print_color_string(PRINT_ANY, ifa_family_color(family),
+					"to", "%s", a);
+		if (r->arpm_to_len != host_len)
+			print_uint(PRINT_ANY, "tolen", "/%u",
+					r->arpm_to_len);
+	} else if (r->arpm_to_len) {
+		print_string(PRINT_ANY, "to", " to %s", "0");
+		print_uint(PRINT_ANY, "tolen", "/%u", r->arpm_to_len);
+	} else {
+		print_string(PRINT_ANY, "to", " to %s", "all");
+	}
+
+	if (tb[ARPA_LLFROM]) {
+		const char *a = ll_addr_n2a(RTA_DATA(tb[ARPA_LLFROM]),
+					    RTA_PAYLOAD(tb[ARPA_LLFROM]),
+					    ARPHRD_VOID,
+					    b1, sizeof(b1));
+		print_string(PRINT_FP, NULL, " llfrom ", NULL);
+		print_color_string(PRINT_ANY, COLOR_MAC,
+				   "llfrom", "%s", a);
+	}
+
+	if (tb[ARPA_LLTO]) {
+		const char *a = ll_addr_n2a(RTA_DATA(tb[ARPA_LLTO]),
+					    RTA_PAYLOAD(tb[ARPA_LLTO]),
+					    ARPHRD_VOID,
+					    b1, sizeof(b1));
+		print_string(PRINT_FP, NULL, " llto ", NULL);
+		print_color_string(PRINT_ANY, COLOR_MAC,
+				   "llto", "%s", a);
+	}
+
+	if (tb[ARPA_IIF]) {
+		snprintf(b1, sizeof(b1), "%s%s",
+			 (char*)RTA_DATA(tb[ARPA_IIF]),
+			 (r->arpm_flags & ARPM_F_WILDIIF) ? "+" : "");
+		if (!is_json_context())
+			fprintf(fp, " iif ");
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "iif", "%s", b1);
+	}
+
+	if (tb[ARPA_OIF]) {
+		snprintf(b1, sizeof(b1), "%s%s",
+			 (char*)RTA_DATA(tb[ARPA_OIF]),
+			 (r->arpm_flags & ARPM_F_WILDOIF) ? "+" : "");
+		if (!is_json_context())
+			fprintf(fp, " oif ");
+		print_color_string(PRINT_ANY, COLOR_IFNAME,
+				   "oif", "%s", b1);
+	}
+
+	if (r->arpm_flags & ARPM_F_BROADCAST) {
+		print_bool(PRINT_ANY, "broadcasts", " broadcasts", true);
+	}
+
+	if (r->arpm_flags & ARPM_F_UNICAST) {
+		print_bool(PRINT_ANY, "unicasts", " unicasts", true);
+	}
+
+	if (tb[ARPA_LLSRC]) {
+		const char *a = ll_addr_n2a(RTA_DATA(tb[ARPA_LLSRC]),
+					    RTA_PAYLOAD(tb[ARPA_LLSRC]),
+					    ARPHRD_VOID,
+					    b1, sizeof(b1));
+		print_string(PRINT_FP, NULL, " llsrc ", NULL);
+		print_color_string(PRINT_ANY, COLOR_MAC,
+				   "llsrc", "%s", a);
+	}
+
+	if (tb[ARPA_LLDST]) {
+		const char *a = ll_addr_n2a(RTA_DATA(tb[ARPA_LLDST]),
+					    RTA_PAYLOAD(tb[ARPA_LLDST]),
+					    ARPHRD_VOID,
+					    b1, sizeof(b1));
+		print_string(PRINT_FP, NULL, " lldst ", NULL);
+		print_color_string(PRINT_ANY, COLOR_MAC,
+				   "lldst", "%s", a);
+	}
+
+	if (tb[ARPA_SRC]) {
+		print_string(PRINT_FP, NULL, " src ", NULL);
+		print_color_string(PRINT_ANY, ifa_family_color(family),
+				   "src", "%s",
+				   format_host_rta(family, tb[ARPA_SRC]));
+	}
+
+	if (show_stats) {
+		print_string(PRINT_FP, NULL, "%s", _SL_);
+
+		if (tb[ARPA_PACKETS])
+			print_uint(PRINT_ANY, "packets", " packets %u",
+				   *(unsigned*) RTA_DATA(tb[ARPA_PACKETS]));
+	}
+
+	print_string(PRINT_FP, NULL, "\n", "");
+	close_json_object();
+	fflush(fp);
+
+	return 0;
+}
+
+static int iparprule_do_cmd(int c, int argc, char **argv)
+{
+	struct {
+		struct nlmsghdr 	n;
+		struct arpmsg 		r;
+		char   			buf[1024];
+	} req;
+	int af = AF_INET;
+	int wild = c & (CMD_LIST | CMD_FLUSH);
+	char *dev = 0;
+
+	f.tb = ARPA_TABLE_ALL;
+	f.action = -1;
+
+	iparp_detect_version(1);
+
+	if (!wild) {
+		memset(&req, 0, sizeof(req));
+
+		if (c & CMD_DEL)
+			req.n.nlmsg_type = RTM_DELARPRULE;
+		else
+			req.n.nlmsg_type = RTM_NEWARPRULE;
+		req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct arpmsg));
+		req.n.nlmsg_flags = NLM_F_REQUEST;
+		req.r.arpm_table = ARPA_TABLE_ALL;
+
+		switch (c) {
+		case CMD_APPEND:
+			req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_APPEND;
+			break;
+		case CMD_PREPEND:
+			req.n.nlmsg_flags |= NLM_F_CREATE;
+			break;
+		case CMD_ADD:
+			req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL;
+			break;
+		case CMD_DEL:
+			req.n.nlmsg_flags |= 0;
+			break;
+		case CMD_CHANGE:
+			req.n.nlmsg_flags |= NLM_F_REPLACE;
+			break;
+		case CMD_REPLACE:
+			req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_REPLACE;
+			break;
+		case CMD_TEST:
+			req.n.nlmsg_flags |= NLM_F_EXCL;
+			break;
+		}
+	}
+
+	for (; argc > 0; argc--, argv++) {
+		if (matches(*argv, "table") == 0 ||
+		    matches(*argv, "lookup") == 0) {
+			strarr_t *c;
+
+			NEXT_ARG();
+			if (f.tb != ARPA_TABLE_ALL) duparg("table", *argv);
+			c = lookup_strarr(arp_tables, *argv);
+			if (!c && strcmp(*argv, "all") != 0)
+				invarg("table value is invalid", *argv);
+			if (c)
+				f.tb = c->code;
+			continue;
+		}
+
+		if (matches(*argv, "rule") == 0 ||
+		    matches(*argv, "preference") == 0 ||
+		    matches(*argv, "order") == 0 ||
+		    matches(*argv, "priority") == 0) {
+			NEXT_ARG();
+			goto get_pref_;
+		}
+
+		if (matches(*argv, "input") == 0) {
+			if (f.tb != ARPA_TABLE_ALL) duparg("input", *argv);
+			f.tb = ARPA_TABLE_INPUT;
+			continue;
+		}
+
+		if (matches(*argv, "output") == 0) {
+			if (f.tb != ARPA_TABLE_ALL) duparg("output", *argv);
+			f.tb = ARPA_TABLE_OUTPUT;
+			continue;
+		}
+
+		if (matches(*argv, "forward") == 0) {
+			if (f.tb != ARPA_TABLE_ALL) duparg("forward", *argv);
+			f.tb = ARPA_TABLE_FORWARD;
+			continue;
+		}
+
+		if (matches(*argv, "allow") == 0) {
+			f.action = 1;
+			continue;
+		}
+
+		if (matches(*argv, "deny") == 0 ||
+		    matches(*argv, "drop") == 0) {
+			f.action = 0;
+			continue;
+		}
+
+		if (matches(*argv, "from") == 0) {
+			/* For any command */
+			NEXT_ARG();
+			if (wild && matches(*argv, "root") == 0) {
+				NEXT_ARG();
+				get_prefix(&f.rfrom, *argv, af);
+				continue;
+			}
+			if (wild && matches(*argv, "match") == 0) {
+				NEXT_ARG();
+				get_prefix(&f.mfrom, *argv, af);
+				continue;
+			}
+			if (matches(*argv, "exact") == 0) {
+				NEXT_ARG();
+			}
+			if (f.mfrom.family) duparg("from", *argv);
+			get_prefix(&f.mfrom, *argv, af);
+			f.rfrom = f.mfrom;
+			if (!wild) {
+				req.r.arpm_from_len = f.mfrom.bitlen;
+				addattr_l(&req.n, sizeof(req), ARPA_FROM,
+					&f.mfrom.data, 4);
+			}
+			continue;
+		}
+
+		if (matches(*argv, "to") == 0) {
+			/* For any command */
+			NEXT_ARG();
+			if (wild && matches(*argv, "root") == 0) {
+				NEXT_ARG();
+				get_prefix(&f.rto, *argv, af);
+				continue;
+			}
+			if (wild && matches(*argv, "match") == 0) {
+				NEXT_ARG();
+				get_prefix(&f.mto, *argv, af);
+				continue;
+			}
+			if (matches(*argv, "exact") == 0) {
+				NEXT_ARG();
+			}
+			if (f.mto.family) duparg("to", *argv);
+			get_prefix(&f.mto, *argv, af);
+			f.rto = f.mto;
+			if (!wild) {
+				req.r.arpm_to_len = f.mto.bitlen;
+				addattr_l(&req.n, sizeof(req), ARPA_TO,
+					&f.mto.data, 4);
+			}
+			continue;
+		}
+
+		if (matches(*argv, "src") == 0) {
+			NEXT_ARG();
+			if (wild && matches(*argv, "root") == 0) {
+				NEXT_ARG();
+				get_prefix(&f.rsrc, *argv, af);
+				continue;
+			}
+			if (matches(*argv, "exact") == 0) {
+				NEXT_ARG();
+			}
+			if (f.rsrc.family) duparg("src", *argv);
+			get_prefix(&f.rsrc, *argv, af);
+			if (!f.rsrc.bitlen) {
+				f.rsrc.bitlen = 32;
+				f.rsrc.data[0] = 0;
+			}
+			if (f.rsrc.bitlen != 32)
+				invarg("src value is invalid", *argv);
+			if (!wild)
+				addattr_l(&req.n, sizeof(req), ARPA_SRC,
+					&f.rsrc.data, 4);
+			continue;
+		}
+
+		if (matches(*argv, "llfrom") == 0) {
+			/* For any command */
+			char llabuf[MAX_ADDR_LEN];
+			int l;
+
+			NEXT_ARG();
+
+			if (f.llfrom_len) duparg("llfrom", *argv);
+
+			l = ll_addr_a2n(llabuf, sizeof(llabuf), *argv);
+			if (l < 0) return 1;
+			if (l > sizeof(llabuf)) l = sizeof(llabuf);
+			f.llfrom_len = l;
+			memcpy(f.llfrom, llabuf, l);
+			if (!wild)
+				addattr_l(&req.n, sizeof(req), ARPA_LLFROM,
+					llabuf, l);
+			continue;
+		}
+
+		if (matches(*argv, "llto") == 0) {
+			/* For any command */
+			char llabuf[MAX_ADDR_LEN];
+			int l;
+
+			NEXT_ARG();
+
+			if (f.llto_len) duparg("llto", *argv);
+
+			l = ll_addr_a2n(llabuf, sizeof(llabuf), *argv);
+			if (l < 0) return 1;
+			if (l > sizeof(llabuf)) l = sizeof(llabuf);
+			f.llto_len = l;
+			memcpy(f.llto, llabuf, l);
+			if (!wild)
+				addattr_l(&req.n, sizeof(req), ARPA_LLTO,
+					llabuf, l);
+			continue;
+		}
+
+		if (matches(*argv, "llsrc") == 0) {
+			/* For any command */
+			char llabuf[MAX_ADDR_LEN];
+			int l;
+
+			NEXT_ARG();
+
+			if (f.llsrc_len) duparg("llsrc", *argv);
+
+			l = ll_addr_a2n(llabuf, sizeof(llabuf), *argv);
+			if (l < 0) return 1;
+			if (l > sizeof(llabuf)) l = sizeof(llabuf);
+			f.llsrc_len = l;
+			memcpy(f.llsrc, llabuf, l);
+			if (!wild)
+				addattr_l(&req.n, sizeof(req), ARPA_LLSRC,
+					llabuf, l);
+			continue;
+		}
+
+		if (matches(*argv, "lldst") == 0) {
+			/* For any command */
+			char llabuf[MAX_ADDR_LEN];
+			int l;
+
+			NEXT_ARG();
+
+			if (f.lldst_len) duparg("lldst", *argv);
+
+			l = ll_addr_a2n(llabuf, sizeof(llabuf), *argv);
+			if (l < 0) return 1;
+			if (l > sizeof(llabuf)) l = sizeof(llabuf);
+			f.lldst_len = l;
+			memcpy(f.lldst, llabuf, l);
+			if (!wild)
+				addattr_l(&req.n, sizeof(req), ARPA_LLDST,
+					llabuf, l);
+			continue;
+		}
+
+		if (strcmp(*argv, "dev") == 0) {
+			NEXT_ARG();
+			if (dev) duparg("dev", *argv);
+			dev = *argv;
+			continue;
+		}
+
+		if (strcmp(*argv, "iif") == 0) {
+			NEXT_ARG();
+			if (f.iif) duparg("iif", *argv);
+			f.iif = *argv;
+			continue;
+		}
+
+		if (strcmp(*argv, "oif") == 0) {
+			NEXT_ARG();
+			if (f.oif) duparg("oif", *argv);
+			f.oif = *argv;
+			continue;
+		}
+
+		if (matches(*argv, "packets") == 0) {
+			NEXT_ARG();
+			if (f.packets) duparg("packets", *argv);
+			if (get_u32(&f.packets, *argv, 0))
+				invarg("packets value is invalid", *argv);
+			if (!wild)
+				addattr32(&req.n, sizeof(req), ARPA_PACKETS,
+					f.packets);
+			continue;
+		}
+
+		if (matches(*argv, "broadcasts") == 0 ||
+		    strcmp(*argv, "brd") == 0) {
+			f.broadcasts = 1;
+			continue;
+		}
+
+		if (matches(*argv, "unicasts") == 0) {
+			f.unicasts = 1;
+			continue;
+		}
+
+		if (matches(*argv, "help") == 0)
+			usage();
+
+		get_pref_:
+
+		{
+			__u32 pref;
+
+			if (f.pref) duparg("preference", *argv);
+
+			if (get_u32(&pref, *argv, 0) || !pref)
+				invarg("preference value is invalid", *argv);
+			f.pref = pref;
+		}
+	}
+
+	if (f.tb == ARPA_TABLE_ALL && !wild) {
+		f.tb = ARPA_TABLE_INPUT;
+	}
+
+	if (dev) {
+		if (f.tb == ARPA_TABLE_INPUT && !f.iif)
+			f.iif = dev;
+		else
+		if (f.tb == ARPA_TABLE_OUTPUT && !f.oif)
+			f.oif = dev;
+		else 
+			invarg("unexpected or duplicate value for dev", dev);
+	}
+
+	if (!wild) {
+		if (f.action < 0) {
+			/* Default action is allow */
+			f.action = 1;
+		}
+	}
+
+	/* Unexpected parameters for table input */
+	if (!wild && f.tb == ARPA_TABLE_INPUT) {
+		if (f.rsrc.family) {
+			fprintf(stderr, "Unexpected argument for table input: src\n");
+			return 1;
+		}
+		if (f.oif) {
+			fprintf(stderr, "Unexpected argument for table input: oif\n");
+			return 1;
+		}
+	}
+
+	/* Unexpected parameters for table output */
+	if (!wild && f.tb == ARPA_TABLE_OUTPUT) {
+		if (f.iif) {
+			fprintf(stderr, "Unexpected argument for table output: iif\n");
+			return 1;
+		}
+		if (f.broadcasts) {
+			fprintf(stderr, "Unexpected argument for table output: broadcasts\n");
+			return 1;
+		}
+		if (f.unicasts) {
+			fprintf(stderr, "Unexpected argument for table output: unicasts\n");
+			return 1;
+		}
+	}
+
+	/* Unexpected parameters for table forward */
+	if (!wild && f.tb == ARPA_TABLE_FORWARD) {
+		if (f.rsrc.family) {
+			fprintf(stderr, "Unexpected argument for table forward: src\n");
+			return 1;
+		}
+	}
+
+	if (!wild && f.broadcasts && f.unicasts) {
+		fprintf(stderr, "Incompatible arguments: broadcasts and unicasts\n");
+		return 1;
+	}
+
+	if (!wild) {
+		if (!(f.pref || f.mfrom.family || f.mto.family ||
+		      f.llfrom_len || f.llto_len || f.iif || f.oif ||
+		      f.broadcasts || f.unicasts)) {
+			fprintf(stderr, "No arguments for rule selection.\n");
+			return 1;
+		}
+	}
+
+	if (!wild) {
+		req.r.arpm_family = AF_INET;
+		req.r.arpm_table = f.tb;
+		req.r.arpm_pref = f.pref;
+		req.r.arpm_action = f.action;
+
+		if (f.broadcasts)
+			req.r.arpm_flags |= ARPM_F_BROADCAST;
+		if (f.unicasts)
+			req.r.arpm_flags |= ARPM_F_UNICAST;
+
+		if (f.iif) {
+			char *cp = strchr(f.iif, '+');
+
+			if (cp) {
+				*cp = 0;
+				req.r.arpm_flags |= ARPM_F_WILDIIF;
+			}
+			if (strlen(f.iif) > IFNAMSIZ-1) {
+				fprintf(stderr, "Value for iif is too long\n");
+				return 1;
+			}
+			addattr_l(&req.n, sizeof(req), ARPA_IIF,
+				f.iif, strlen(f.iif)+1);
+		}
+		if (f.oif) {
+			char *cp = strchr(f.oif, '+');
+
+			if (cp) {
+				*cp = 0;
+				req.r.arpm_flags |= ARPM_F_WILDOIF;
+			}
+			if (strlen(f.oif) > IFNAMSIZ-1) {
+				fprintf(stderr, "Value for oif is too long\n");
+				return 1;
+			}
+			addattr_l(&req.n, sizeof(req), ARPA_OIF,
+				f.oif, strlen(f.oif)+1);
+		}
+	}
+
+	ll_init_map(&rth);
+
+	if (rtnl_open(&rth, 0) < 0)
+		return 1;
+
+	if (c & CMD_FLUSH) {
+		int round = 0;
+		char flushb[4096-512];
+
+		f.flushb = flushb;
+		f.flushp = 0;
+		f.flushe = sizeof(flushb);
+
+		for (;;) {
+			if (rtnl_arpdump_req(&rth, af) < 0) {
+				perror("Cannot send dump request");
+				exit(1);
+			}
+			f.flushed = 0;
+			if (rtnl_dump_filter(&rth, print_arprule, stdout) < 0) {
+				fprintf(stderr, "Flush terminated\n");
+				exit(1);
+			}
+			if (f.flushed == 0) {
+				if (show_stats) {
+					if (round == 0)
+						printf("Nothing to flush.\n");
+					else
+						printf("*** Flush is complete after %d round%s ***\n", round, round > 1?"s":"");
+				}
+				fflush(stdout);
+				return 0;
+			}
+			round++;
+			if (flush_update() < 0)
+				exit(1);
+			if (show_stats) {
+				printf("\n*** Round %d, deleting %d entries ***\n", round, f.flushed);
+				fflush(stdout);
+			}
+		}
+	}
+	else
+	if (wild) {
+		if (rtnl_arpdump_req(&rth, af) < 0) {
+			perror("Cannot send dump request");
+			exit(1);
+		}
+
+		new_json_obj(json);
+		if (rtnl_dump_filter(&rth, print_arprule, stdout) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			exit(1);
+		}
+		delete_json_obj();
+	} else {
+
+		if (rtnl_talk(&rth, &req.n, NULL) < 0)
+			return 2;
+	}
+
+	return 0;
+}
+
+int do_iparprule(int argc, char **argv)
+{
+strarr_t *c;
+
+	if (argc < 1)
+		return iparprule_do_cmd(CMD_LIST, 0, NULL);
+	c = lookup_strarr(cmds, argv[0]);
+	if (c) {
+		cmd = c->code;
+		return iparprule_do_cmd(cmd, argc-1, argv+1);
+	}
+	if (matches(argv[0], "help") == 0)
+		usage();
+
+	fprintf(stderr, "Command \"%s\" is unknown, try \"ip arp help\".\n", *argv);
+	exit(-1);
+}
+
diff -urpN iproute2-6.17.0/ip/ipmonitor.c iproute2-6.17.0-iparp/ip/ipmonitor.c
--- iproute2-6.17.0/ip/ipmonitor.c	2025-09-29 17:43:02.000000000 +0300
+++ iproute2-6.17.0-iparp/ip/ipmonitor.c	2025-10-12 13:04:03.655429032 +0300
@@ -31,7 +31,7 @@ static void usage(void)
 	fprintf(stderr,
 		"Usage: ip monitor [ all | OBJECTS ] [ FILE ] [ label ] [ all-nsid ]\n"
 		"                  [ dev DEVICE ]\n"
-		"OBJECTS :=  address | link | mroute | maddress | acaddress | neigh |\n"
+		"OBJECTS :=  address | arp | link | mroute | maddress | acaddress | neigh |\n"
 		"            netconf | nexthop | nsid | prefix | route | rule | stats\n"
 		"FILE := file FILENAME\n");
 	exit(-1);
@@ -63,6 +63,12 @@ static int accept_msg(struct rtnl_ctrl_d
 
 	ctrl_data = ctrl;
 
+	if (n->nlmsg_type == RTM_NEWARPRULE ||
+	    n->nlmsg_type == RTM_DELARPRULE) {
+		print_arprule(n, arg);
+		return 0;
+	}
+
 	switch (n->nlmsg_type) {
 	case RTM_NEWROUTE:
 	case RTM_DELROUTE: {
@@ -188,6 +194,7 @@ static int accept_msg(struct rtnl_ctrl_d
 #define IPMON_LNEXTHOP		BIT(10)
 #define IPMON_LMADDR		BIT(11)
 #define IPMON_LACADDR		BIT(12)
+#define IPMON_ARP		BIT(13)
 
 #define IPMON_L_ALL		(~0)
 
@@ -216,6 +223,8 @@ int do_ipmonitor(int argc, char **argv)
 			lmask |= IPMON_LMADDR;
 		} else if (strcmp(*argv, "acaddress") == 0) {
 			lmask |= IPMON_LACADDR;
+		} else if (matches(*argv, "arp") == 0) {
+			lmask |= IPMON_ARP;
 		} else if (matches(*argv, "route") == 0) {
 			lmask |= IPMON_LROUTE;
 		} else if (matches(*argv, "mroute") == 0) {
@@ -372,6 +381,14 @@ int do_ipmonitor(int argc, char **argv)
 		}
 	}
 
+	if (lmask & IPMON_ARP &&
+	    iparp_detect_version(nmask & IPMON_ARP) &&
+	    rtnl_add_nl_group(&rth, RTNLGRP_ARP) < 0 &&
+	    nmask & IPMON_ARP) {
+		fprintf(stderr, "Failed to add arp group to list\n");
+		exit(1);
+	}
+
 	if (listen_all_nsid && rtnl_listen_all_nsid(&rth) < 0)
 		exit(1);
 
diff -urpN iproute2-6.17.0/lib/libnetlink.c iproute2-6.17.0-iparp/lib/libnetlink.c
--- iproute2-6.17.0/lib/libnetlink.c	2025-09-29 17:43:02.000000000 +0300
+++ iproute2-6.17.0-iparp/lib/libnetlink.c	2025-10-12 13:04:03.655741790 +0300
@@ -435,6 +435,26 @@ int rtnl_neightbldump_req(struct rtnl_ha
 	return send(rth->fd, &req, sizeof(req), 0);
 }
 
+__u16 iparp_RTM_NEWARPRULE, iparp_RTM_DELARPRULE, iparp_RTM_GETARPRULE;
+enum rtnetlink_groups iparp_RTNLGRP_ARP;
+
+int rtnl_arpdump_req(struct rtnl_handle *rth, int family)
+{
+	struct {
+		struct nlmsghdr nlh;
+		struct arpmsg arpm;
+		char buf[256];
+	} req = {
+		.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct arpmsg)),
+		.nlh.nlmsg_type = RTM_GETARPRULE,
+		.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+		.nlh.nlmsg_seq = rth->dump = ++rth->seq,
+		.arpm.arpm_family = family,
+	};
+
+	return send(rth->fd, &req, sizeof(req), 0);
+}
+
 int rtnl_mdbdump_req(struct rtnl_handle *rth, int family)
 {
 	struct {
