diff -urN iproute2-2.4.7-now-ss020116-try/ip/Makefile iproute2-iparp/ip/Makefile
--- iproute2-2.4.7-now-ss020116-try/ip/Makefile	Thu Jan 10 03:08:18 2002
+++ iproute2-iparp/ip/Makefile	Sun Mar 10 15:08:13 2002
@@ -1,4 +1,4 @@
-IPOBJ=ip.o ipaddress.o iproute.o iprule.o \
+IPOBJ=ip.o ipaddress.o iproute.o iprule.o iparp.o \
     rtm_map.o iptunnel.o ipneigh.o iplink.o ipmaddr.o \
     ipmonitor.o ipmroute.o
 
diff -urN iproute2-2.4.7-now-ss020116-try/ip/ip.c iproute2-iparp/ip/ip.c
--- iproute2-2.4.7-now-ss020116-try/ip/ip.c	Sun Apr 16 17:42:50 2000
+++ iproute2-iparp/ip/ip.c	Sun Mar 31 13:44:30 2002
@@ -39,7 +39,7 @@
 {
 	fprintf(stderr,
 "Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }\n"
-"where  OBJECT := { link | addr | route | rule | neigh | tunnel |\n"
+"where  OBJECT := { link | addr | route | rule | neigh | arp | tunnel |\n"
 "                   maddr | mroute | monitor }\n"
 "       OPTIONS := { -V[ersion] | -s[tatistics] | -r[esolve] |\n"
 "                    -f[amily] { inet | inet6 | ipx | dnet | link } | -o[neline] }\n");
@@ -132,6 +132,8 @@
 		return do_ipneigh(argc-1, argv+1);
 	if (strcmp(basename, "iplink") == 0)
 		return do_iplink(argc-1, argv+1);
+	if (strcmp(basename, "iparp") == 0)
+		return do_iparprule(argc-1, argv+1);
 	if (strcmp(basename, "iptunnel") == 0)
 		return do_iptunnel(argc-1, argv+1);
 	if (strcmp(basename, "ipmonitor") == 0)
@@ -153,6 +155,8 @@
 			return do_ipneigh(argc-2, argv+2);
 		if (matches(argv[1], "link") == 0)
 			return do_iplink(argc-2, argv+2);
+		if (matches(argv[1], "arp") == 0)
+			return do_iparprule(argc-2, argv+2);
 		if (matches(argv[1], "tunnel") == 0 ||
 		    strcmp(argv[1], "tunl") == 0)
 			return do_iptunnel(argc-2, argv+2);
diff -urN iproute2-2.4.7-now-ss020116-try/ip/ip_common.h iproute2-iparp/ip/ip_common.h
--- iproute2-2.4.7-now-ss020116-try/ip/ip_common.h	Sun Apr 16 17:42:50 2000
+++ iproute2-iparp/ip/ip_common.h	Sun Mar 31 20:44:02 2002
@@ -15,6 +15,8 @@
 extern int do_ipneigh(int argc, char **argv);
 extern int do_iptunnel(int argc, char **argv);
 extern int do_iplink(int argc, char **argv);
+extern int do_iparprule(int argc, char **argv);
+extern int print_arprule(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
 extern int do_ipmonitor(int argc, char **argv);
 extern int do_multiaddr(int argc, char **argv);
 extern int do_multiroute(int argc, char **argv);
diff -urN iproute2-2.4.7-now-ss020116-try/ip/iparp.c iproute2-iparp/ip/iparp.c
--- iproute2-2.4.7-now-ss020116-try/ip/iparp.c	Thu Jan  1 00:00:00 1970
+++ iproute2-iparp/ip/iparp.c	Sun Mar 31 20:54:09 2002
@@ -0,0 +1,932 @@
+/*
+ * iparp.c		"ip arp".
+ *
+ * Authors:	Julian Anastasov <ja@ssi.bg>, March 2002
+ *
+ *		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 "rt_names.h"
+#include "utils.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				*/
+#define CMD_GET		0x0200	/* get				*/
+
+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	},
+	{	"get",		CMD_GET		},
+
+	{	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;
+
+static struct
+{
+	int tb;
+	int pref;
+	int action;
+	int flushed;
+	char *flushb;
+	int flushp;
+	int flushe;
+	struct rtnl_handle *rth;
+	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 ] [ ARPRULE ]\n");
+	fprintf(stderr, "       ip arp [ append | prepend | add | del | insert | change ] ARPRULE\n");
+	fprintf(stderr, "ARPRULE := [ 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 ] [ REPLY ]\n");
+	fprintf(stderr, "ACTION := [ deny | allow ]\n");
+	fprintf(stderr, "REPLY := [ src IP ] [ llsrc LLADDR ] [ lldst LLADDR ]\n");
+	exit(-1);
+}
+
+static int flush_update(void)
+{
+	if (rtnl_send(f.rth, f.flushb, f.flushp) < 0) {
+		perror("Failed to send flush request\n");
+		return -1;
+	}
+	f.flushp = 0;
+	return 0;
+}
+
+int print_arprule(struct sockaddr_nl *who, 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[RTATTR_MAX+1];
+	inet_prefix vto, vfrom, vsrc;
+	__u32 packets = 0;
+	char abuf[256];
+	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.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 = ++f.rth->seq;
+		f.flushp = (((char*)fn) + n->nlmsg_len) - f.flushb;
+		f.flushed++;
+		if (show_stats < 2)
+			return 0;
+	}
+
+	if (n->nlmsg_type == RTM_DELARPRULE)
+		fprintf(fp, "Deleted ");
+
+	{
+		strarr_t *p = lookup_strarr_code(arp_tables, r->arpm_table);
+		fprintf(fp, "%s", p? p->name:"unknown");
+	}
+
+	fprintf(fp, " rule %-2u", r->arpm_pref);
+
+	{
+		strarr_t *p = lookup_strarr_code(arp_actions, r->arpm_action);
+		fprintf(fp, " %-5s ", p? p->name:"unk");
+	}
+
+	if (tb[ARPA_FROM]) {
+		if (r->arpm_from_len != host_len) {
+			fprintf(fp, "from %s/%u",
+				rt_addr_n2a(family,
+					    RTA_PAYLOAD(tb[ARPA_FROM]),
+					    RTA_DATA(tb[ARPA_FROM]),
+					    abuf, sizeof(abuf)),
+				r->arpm_from_len
+				);
+		} else {
+			fprintf(fp, "from %s",
+				format_host(family,
+					    RTA_PAYLOAD(tb[ARPA_FROM]),
+					    RTA_DATA(tb[ARPA_FROM]),
+					    abuf, sizeof(abuf))
+				);
+		}
+	} else if (r->arpm_from_len) {
+		fprintf(fp, "from 0/%d", r->arpm_from_len);
+	} else {
+		fprintf(fp, "from all");
+	}
+
+	if (tb[ARPA_TO]) {
+		if (r->arpm_to_len != host_len) {
+			fprintf(fp, " to %s/%u",
+				rt_addr_n2a(family,
+					    RTA_PAYLOAD(tb[ARPA_TO]),
+					    RTA_DATA(tb[ARPA_TO]),
+					    abuf, sizeof(abuf)),
+				r->arpm_to_len
+				);
+		} else {
+			fprintf(fp, " to %s",
+				format_host(family,
+					    RTA_PAYLOAD(tb[ARPA_TO]),
+					    RTA_DATA(tb[ARPA_TO]),
+					    abuf, sizeof(abuf))
+				);
+		}
+	} else if (r->arpm_to_len) {
+		fprintf(fp, " to 0/%d", r->arpm_to_len);
+	} else {
+		fprintf(fp, " to all");
+	}
+
+	if (tb[ARPA_LLFROM]) {
+		fprintf(fp, " llfrom %s",
+			ll_addr_n2a(RTA_DATA(tb[ARPA_LLFROM]),
+				    RTA_PAYLOAD(tb[ARPA_LLFROM]),
+				    ARPHRD_VOID,
+				    b1, sizeof(b1)));
+	}
+
+	if (tb[ARPA_LLTO]) {
+		fprintf(fp, " llto %s",
+			ll_addr_n2a(RTA_DATA(tb[ARPA_LLTO]),
+				    RTA_PAYLOAD(tb[ARPA_LLTO]),
+				    ARPHRD_VOID,
+				    b1, sizeof(b1)));
+	}
+
+	if (tb[ARPA_IIF]) {
+		fprintf(fp, " iif %s%s",
+			(char*)RTA_DATA(tb[ARPA_IIF]),
+			(r->arpm_flags & ARPM_F_WILDIIF) ? "+" : ""
+			);
+	}
+
+	if (tb[ARPA_OIF]) {
+		fprintf(fp, " oif %s%s",
+			(char*)RTA_DATA(tb[ARPA_OIF]),
+			(r->arpm_flags & ARPM_F_WILDOIF) ? "+" : ""
+			);
+	}
+
+	if (r->arpm_flags & ARPM_F_BROADCAST) {
+		fprintf(fp, " broadcasts");
+	}
+
+	if (r->arpm_flags & ARPM_F_UNICAST) {
+		fprintf(fp, " unicasts");
+	}
+
+	if (tb[ARPA_LLSRC]) {
+		fprintf(fp, " llsrc %s",
+			ll_addr_n2a(RTA_DATA(tb[ARPA_LLSRC]),
+				    RTA_PAYLOAD(tb[ARPA_LLSRC]),
+				    ARPHRD_VOID,
+				    b1, sizeof(b1)));
+	}
+
+	if (tb[ARPA_LLDST]) {
+		fprintf(fp, " lldst %s",
+			ll_addr_n2a(RTA_DATA(tb[ARPA_LLDST]),
+				    RTA_PAYLOAD(tb[ARPA_LLDST]),
+				    ARPHRD_VOID,
+				    b1, sizeof(b1)));
+	}
+
+	if (tb[ARPA_SRC]) {
+		fprintf(fp, " src %s",
+			format_host(family,
+				    RTA_PAYLOAD(tb[ARPA_SRC]),
+				    RTA_DATA(tb[ARPA_SRC]),
+				    abuf, sizeof(abuf))
+			);
+	}
+
+	if (show_stats) {
+		fprintf(fp, "%s", _SL_);
+
+		if (tb[ARPA_PACKETS])
+			fprintf(fp, " packets %u",
+				*(unsigned*) RTA_DATA(tb[ARPA_PACKETS]));
+	}
+
+	fprintf(fp, "\n");
+
+	fflush(fp);
+	return 0;
+}
+
+int iparprule_do_cmd(int c, int argc, char **argv)
+{
+	struct rtnl_handle rth;
+	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;
+
+	if (!wild) {
+		memset(&req, 0, sizeof(req));
+
+		if (c & CMD_DEL)
+			req.n.nlmsg_type = RTM_DELARPRULE;
+		else if (c & CMD_GET)
+			req.n.nlmsg_type = RTM_GETARPRULE;
+		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;
+		case CMD_GET:
+			req.n.nlmsg_flags |= 0;
+			break;
+		}
+	}
+
+	for (; argc > 0; argc--, argv++) {
+		if (matches(*argv, "table") == 0 ||
+		    matches(*argv, "lookup") == 0)
+			continue;
+
+		if (matches(*argv, "rule") == 0 ||
+		    matches(*argv, "preference") == 0 ||
+		    matches(*argv, "order") == 0 ||
+		    matches(*argv, "priority") == 0)
+			continue;
+
+		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 */
+			__u8 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 */
+			__u8 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 */
+			__u8 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 */
+			__u8 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();
+
+		{
+			int 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", *argv);
+	}
+
+	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.llto_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);
+		}
+	}
+
+	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);
+		f.rth = &rth;
+
+		for (;;) {
+			if (rtnl_wilddump_request(&rth, af, RTM_GETARPRULE) < 0) {
+				perror("Cannot send dump request");
+				exit(1);
+			}
+			f.flushed = 0;
+			if (rtnl_dump_filter(&rth, print_arprule, stdout, NULL, NULL) < 0) {
+				fprintf(stderr, "Flush terminated\n");
+				exit(1);
+			}
+			if (f.flushed == 0) {
+				if (round == 0) {
+					fprintf(stderr, "Nothing to flush.\n");
+				}
+				else
+				if (show_stats)
+					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_wilddump_request(&rth, af, RTM_GETARPRULE) < 0) {
+			perror("Cannot send dump request");
+			return 1;
+		}
+
+		if (rtnl_dump_filter(&rth, print_arprule, stdout, NULL, NULL) < 0) {
+			fprintf(stderr, "Dump terminated\n");
+			return 1;
+		}
+	} else {
+		if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, 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 -urN iproute2-2.4.7-now-ss020116-try/ip/ipmonitor.c iproute2-iparp/ip/ipmonitor.c
--- iproute2-2.4.7-now-ss020116-try/ip/ipmonitor.c	Thu Aug  2 03:23:25 2001
+++ iproute2-iparp/ip/ipmonitor.c	Sun Mar 31 20:47:33 2002
@@ -54,6 +54,10 @@
 		print_neigh(who, n, arg);
 		return 0;
 	}
+	if (n->nlmsg_type == RTM_NEWARPRULE || n->nlmsg_type == RTM_DELARPRULE) {
+		print_arprule(who, n, arg);
+		return 0;
+	}
 	if (n->nlmsg_type == 15) {
 		char *tstr;
 		time_t secs = ((__u32*)NLMSG_DATA(n))[0];
@@ -86,6 +90,7 @@
 	int llink=0;
 	int laddr=0;
 	int lroute=0;
+	int larprule=0;
 
 	ipaddr_reset_filter(1);
 	iproute_reset_filter();
@@ -104,6 +109,9 @@
 		} else if (matches(*argv, "route") == 0) {
 			lroute=1;
 			groups = 0;
+		} else if (matches(*argv, "arprules") == 0) {
+			larprule=1;
+			groups = 0;
 		} else if (strcmp(*argv, "all") == 0) {
 			groups = ~RTMGRP_TC;
 		} else if (matches(*argv, "help") == 0) {
@@ -129,6 +137,8 @@
 		if (!preferred_family || preferred_family == AF_INET6)
 			groups |= RTMGRP_IPV6_ROUTE;
 	}
+	if (larprule)
+		groups |= RTMGRP_ARP;
 
 	if (file) {
 		FILE *fp;
