diff options
Diffstat (limited to 'usr.sbin/arp')
| -rw-r--r-- | usr.sbin/arp/Makefile | 19 | ||||
| -rw-r--r-- | usr.sbin/arp/Makefile.depend | 17 | ||||
| -rw-r--r-- | usr.sbin/arp/arp.4 | 237 | ||||
| -rw-r--r-- | usr.sbin/arp/arp.8 | 194 | ||||
| -rw-r--r-- | usr.sbin/arp/arp.c | 899 | ||||
| -rw-r--r-- | usr.sbin/arp/arp.h | 22 | ||||
| -rw-r--r-- | usr.sbin/arp/arp_netlink.c | 451 |
7 files changed, 1839 insertions, 0 deletions
diff --git a/usr.sbin/arp/Makefile b/usr.sbin/arp/Makefile new file mode 100644 index 000000000000..7b6911f8e0f4 --- /dev/null +++ b/usr.sbin/arp/Makefile @@ -0,0 +1,19 @@ +.include <src.opts.mk> + +PROG= arp +MAN= arp.4 arp.8 + + +SRCS= arp.c + +.if ${MK_NETLINK_SUPPORT} != "no" +SRCS+= arp_netlink.c +.else +CFLAGS+=-DWITHOUT_NETLINK +.endif + +LIBADD= xo + +WARNS?= 3 + +.include <bsd.prog.mk> diff --git a/usr.sbin/arp/Makefile.depend b/usr.sbin/arp/Makefile.depend new file mode 100644 index 000000000000..11a91dcad4b2 --- /dev/null +++ b/usr.sbin/arp/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/arpa \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libxo/libxo \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/usr.sbin/arp/arp.4 b/usr.sbin/arp/arp.4 new file mode 100644 index 000000000000..244f784dd028 --- /dev/null +++ b/usr.sbin/arp/arp.4 @@ -0,0 +1,237 @@ +.\" Copyright (c) 1985, 1986, 1988, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd October 7, 2016 +.Dt ARP 4 +.Os +.Sh NAME +.Nm arp +.Nd Address Resolution Protocol +.Sh SYNOPSIS +.Cd "device ether" +.Sh DESCRIPTION +The Address Resolution Protocol (ARP) is used to dynamically +map between Protocol Addresses (such as IP addresses) and +Local Network Addresses (such as Ethernet addresses). +This implementation maps IP addresses to Ethernet addresses. +It is used by all the Ethernet interface drivers. +.Pp +ARP caches Internet-Ethernet address mappings. +When an interface requests a mapping for an address not in the cache, +ARP queues the message which requires the mapping and broadcasts +a message on the associated network requesting the address mapping. +If a response is provided, the new mapping is cached and any pending +message is transmitted. +ARP will queue at most +.Va net.link.ether.inet.maxhold +packets while waiting for a response to a mapping request; +only the most recently ``transmitted'' packets are kept. +If the target host does not respond after several requests, +the host is considered to be down allowing an error to be returned to +transmission attempts. +Further demand for this mapping causes ARP request retransmissions, that +are ratelimited to one packet per second. +The error is +.Er EHOSTDOWN +for a non-responding destination host, and +.Er EHOSTUNREACH +for a non-responding router. +.Pp +The ARP cache is stored in per-interface link-level table. +.Pp +ARP entries may be added, deleted or changed with the +.Xr arp 8 +utility. +Manually-added entries may be temporary or permanent, +and may be +.Dq published , +in which case the system will respond to ARP requests for that host +as if it were the target of the request. +.Pp +In the past, +ARP was used to negotiate the use of a trailer encapsulation. +This is no longer supported. +.Pp +ARP watches passively for hosts impersonating the local host (i.e., a host +which responds to an ARP mapping request for the local host's address). +.Pp +Proxy ARP is a feature whereby the local host will respond to requests +for addresses other than itself, with its own address. +Normally, proxy ARP in +.Fx +is set up on a host-by-host basis using the +.Xr arp 8 +utility, by adding an entry for each host inside a given subnet for +which proxying of ARP requests is desired. +However, the +.Dq "proxy all" +feature causes the local host to act as a proxy for +.Em all +hosts reachable through some other network interface, +different from the one the request came in from. +It may be enabled by setting the +.Xr sysctl 8 +MIB variable +.Va net.link.ether.inet.proxyall +to 1. +.Sh MIB Variables +The ARP protocol implements a number of configurable variables in +.Va net.link.ether.inet +branch +of the +.Xr sysctl 3 +MIB. +.Bl -tag -width "log_arp_permanent_modify" +.It Va allow_multicast +Install ARP entries with the multicast bit set in the hardware address. +Installing such entries is an RFC 1812 violation, but some proprietary load +balancing techniques require routers to do so. +Turned off by default. +.It Va garp_rexmit_count +Retransmit gratuitous ARP (GARP) packets when an IPv4 address is added to an +interface. +A GARP is always transmitted when an IPv4 address is added to an interface. +A non-zero value causes the GARP packet to be retransmitted the stated number +of times. +The interval between retransmissions is doubled each time, so the +retransmission intervals are: {1, 2, 4, 8, 16, ...} (seconds). +The default value of zero means only the initial GARP is sent; no +additional GARP packets are retransmitted. +The maximum value is sixteen. +.Pp +The default behavior of a single GARP packet is usually sufficient. +However, a single GARP might be dropped or lost in some circumstances. +This is particularly harmful when a shared address is passed between cluster +nodes. +Neighbors on the network link might then work with a stale ARP cache and send +packets destined for that address to the node that previously owned the +address, which might not respond. +.It Va log_arp_movements +Log movements of IP addresses from one hardware address to another. +See +.Sx DIAGNOSTICS +below. +Turned on by default. +.It Va log_arp_permanent_modify +Log attempts by a remote host to modify a permanent ARP entry. +See +.Sx DIAGNOSTICS +below. +Turned on by default. +.It Va log_arp_wrong_iface +Log attempts to insert an ARP entry on an interface when the IP network to +which the address belongs is connected to another interface. +See +.Sx DIAGNOSTICS +below. +Turned on by default. +.It Va max_log_per_second +Limit the number of remotely triggered logging events to a configured value per +second. +Default is 1 log message per second. +.It Va max_age +How long an ARP entry is held in the cache until it needs to be refreshed. +Default is 1200 seconds. +.It Va maxhold +How many packets to hold in the per-entry output queue while the entry +is being resolved. +Default is 16 packets. +.It Va maxtries +Number of retransmits before a host is considered down and an error is +returned. +Default is 5 tries. +.It Va proxyall +Enables ARP proxying. +Turned off by default. +.It Va wait +Lifetime of an incomplete ARP entry. +Default is 20 seconds. +.El +.Sh DIAGNOSTICS +.Bl -diag +.It "arp: %x:%x:%x:%x:%x:%x is using my IP address %d.%d.%d.%d on %s!" +ARP has discovered another host on the local network which responds to +mapping requests for its own Internet address with a different Ethernet +address, generally indicating that two hosts are attempting to use the +same Internet address. +.It "arp: link address is broadcast for IP address %d.%d.%d.%d!" +ARP requested information for a host, and received an answer indicating +that the host's ethernet address is the ethernet broadcast address. +This indicates a misconfigured or broken device. +.It "arp: %d.%d.%d.%d moved from %x:%x:%x:%x:%x:%x to %x:%x:%x:%x:%x:%x on %s" +ARP had a cached value for the ethernet address of the referenced host, +but received a reply indicating that the host is at a new address. +This can happen normally when host hardware addresses change, +or when a mobile node arrives or leaves the local subnet. +It can also indicate a problem with proxy ARP. +This message can only be issued if the sysctl +.Va net.link.ether.inet.log_arp_movements +is set to 1, which is the system's default behaviour. +.It "arpresolve: can't allocate llinfo for %d.%d.%d.%d" +The route for the referenced host points to a device upon which ARP is +required, but ARP was unable to allocate a routing table entry in which +to store the host's MAC address. +This usually points to a misconfigured routing table. +It can also occur if the kernel cannot allocate memory. +.It "arp: %d.%d.%d.%d is on if0 but got reply from %x:%x:%x:%x:%x:%x on if1" +Physical connections exist to the same logical IP network on both if0 and if1. +It can also occur if an entry already exists in the ARP cache for the IP +address above, and the cable has been disconnected from if0, then reconnected +to if1. +This message can only be issued if the sysctl +.Va net.link.ether.inet.log_arp_wrong_iface +is set to 1, which is the system's default behaviour. +.It "arp: %x:%x:%x:%x:%x:%x attempts to modify permanent entry for %d.%d.%d.%d on %s" +ARP has received an ARP reply that attempts to overwrite a permanent +entry in the local ARP table. +This error will only be logged if the sysctl +.Va net.link.ether.inet.log_arp_permanent_modify +is set to 1, which is the system's default behaviour. +.It "arp: %x:%x:%x:%x:%x:%x is multicast" +Kernel refused to install an entry with multicast hardware address. +If you really want such addresses being installed, set the sysctl +.Va net.link.ether.inet.allow_multicast +to a positive value. +.El +.Sh SEE ALSO +.Xr inet 4 , +.Xr route 4 , +.Xr arp 8 , +.Xr ifconfig 8 , +.Xr route 8 , +.Xr sysctl 8 +.Rs +.%A Plummer, D. +.%B "An Ethernet Address Resolution Protocol" +.%T RFC826 +.Re +.Rs +.%A Leffler, S.J. +.%A Karels, M.J. +.%B "Trailer Encapsulations" +.%T RFC893 +.Re diff --git a/usr.sbin/arp/arp.8 b/usr.sbin/arp/arp.8 new file mode 100644 index 000000000000..0a171c9e36be --- /dev/null +++ b/usr.sbin/arp/arp.8 @@ -0,0 +1,194 @@ +.\" Copyright (c) 1985, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd July 16, 2025 +.Dt ARP 8 +.Os +.Sh NAME +.Nm arp +.Nd address resolution display and control +.Sh SYNOPSIS +.Nm +.Op Fl -libxo Ar options +.Op Fl n +.Op Fl i Ar interface +.Ar hostname +.Nm +.Op Fl -libxo Ar options +.Op Fl n +.Op Fl i Ar interface +.Fl a +.Nm +.Fl d Ar hostname +.Nm +.Fl d +.Op Fl i Ar interface +.Fl a +.Nm +.Fl s Ar hostname ether_addr +.Op Cm temp +.Op Cm blackhole No \&| Cm reject +.Op Cm pub +.Nm +.Fl S Ar hostname ether_addr +.Op Cm temp +.Op Cm blackhole No \&| Cm reject +.Op Cm pub +.Nm +.Fl f Ar filename +.Sh DESCRIPTION +The +.Nm +utility displays and modifies the Internet-to-Ethernet address translation +tables used by the address resolution protocol +.Pq Xr arp 4 . +With no flags, the program displays the current +.Tn ARP +entry for +.Ar hostname . +The host may be specified by name or by number, +using Internet dot notation. +.Pp +Available options: +.Bl -tag -width indent +.It Fl -libxo +Generate output via +.Xr libxo 3 +in a selection of different human and machine readable formats. +See +.Xr xo_options 7 +for details on command line arguments. +.It Fl a +The program displays or, if it is used with the +.Fl d +flag, deletes all of the current +.Tn ARP +entries. +.It Fl d +A super-user may delete an entry for the host called +.Ar hostname +with the +.Fl d +flag. +.Pp +The +.Fl d +flag may be combined with the +.Fl a +flag to delete all entries. +.It Fl i Ar interface +Limit the operation scope to the +.Tn ARP +entries on +.Ar interface . +Applicable only to the following operations: +display one, display all, delete all. +.It Fl n +Show network addresses as numbers (normally +.Nm +attempts to display addresses symbolically). +.It Fl s Ar hostname ether_addr +Create an +.Tn ARP +entry for the host called +.Ar hostname +with the Ethernet address +.Ar ether_addr . +The Ethernet address is given as six hex bytes separated by colons. +The entry will be permanent unless the word +.Cm temp +is given in the command. +If the word +.Cm pub +is given, the entry will be +.Dq published ; +i.e., this system will +act as an +.Tn ARP +server, +responding to requests for +.Ar hostname +even though the host address is not its own. +In this case the +.Ar ether_addr +can be given as +.Cm auto +in which case the interfaces on this host will be examined, +and if one of them is found to occupy the same subnet, its +Ethernet address will be used. +.Pp +If the +.Cm reject +keyword is specified the entry will be marked so that traffic to +the host will be discarded and the sender will be notified the +host is unreachable. +The +.Cm blackhole +keyword is similar in that traffic is discarded but the sender is +not notified. +These can be used to block external traffic to a host without +using a firewall. +.It Fl S Ar hostname ether_addr +Is just like +.Fl s +except any existing +.Tn ARP +entry for this host will be deleted first. +.It Fl f Ar filename +Cause the file +.Ar filename +to be read and multiple entries to be set in the +.Tn ARP +tables. +Entries +in the file should be of the form +.Pp +.Bd -ragged -offset indent -compact +.Ar hostname ether_addr +.Op Cm temp +.Op Cm blackhole No \&| Cm reject +.Op Cm pub +.Ed +.Pp +with argument meanings as given above. +Leading whitespace and empty lines are ignored. +A +.Ql # +character will mark the rest of the line as a comment. +.El +.Sh SEE ALSO +.Xr inet 3 , +.Xr libxo 3 , +.Xr xo_options 7 , +.Xr arp 4 , +.Xr ifconfig 8 , +.Xr ndp 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.3 . diff --git a/usr.sbin/arp/arp.c b/usr.sbin/arp/arp.c new file mode 100644 index 000000000000..055ef2ffe225 --- /dev/null +++ b/usr.sbin/arp/arp.c @@ -0,0 +1,899 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1984, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Sun Microsystems, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * arp - display, set, and delete arp table entries + */ + +#include <sys/param.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> + +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#include <nlist.h> +#include <paths.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <ifaddrs.h> +#include <libxo/xo.h> +#include "arp.h" + +typedef void (action_fn)(struct sockaddr_dl *sdl, struct sockaddr_in *s_in, + struct rt_msghdr *rtm); +static void nuke_entries(uint32_t ifindex, struct in_addr addr); +static int print_entries(uint32_t ifindex, struct in_addr addr); + +static int delete(char *host); +static void usage(void) __dead2; +static int set(int argc, char **argv); +static int get(char *host); +static int file(char *name); +static struct rt_msghdr *rtmsg(int cmd, + struct sockaddr_in *dst, struct sockaddr_dl *sdl); +static int set_rtsock(struct sockaddr_in *dst, struct sockaddr_dl *sdl_m, + char *host); + +struct if_nameindex *ifnameindex; + +struct arp_opts opts = {}; + +/* which function we're supposed to do */ +#define F_GET 1 +#define F_SET 2 +#define F_FILESET 3 +#define F_REPLACE 4 +#define F_DELETE 5 + +#define SETFUNC(f) { if (func) usage(); func = (f); } + +#define ARP_XO_VERSION "1" + +int +main(int argc, char *argv[]) +{ + int ch, func = 0; + int rtn = 0; + + argc = xo_parse_args(argc, argv); + if (argc < 0) + exit(1); + + while ((ch = getopt(argc, argv, "andfsSi:")) != -1) + switch(ch) { + case 'a': + opts.aflag = true; + break; + case 'd': + SETFUNC(F_DELETE); + break; + case 'n': + opts.nflag = true; + break; + case 'S': + SETFUNC(F_REPLACE); + break; + case 's': + SETFUNC(F_SET); + break; + case 'f' : + SETFUNC(F_FILESET); + break; + case 'i': + opts.rifname = optarg; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (!func) + func = F_GET; + if (opts.rifname) { + if (func != F_GET && func != F_SET && func != F_REPLACE && + !(func == F_DELETE && opts.aflag)) + xo_errx(1, "-i not applicable to this operation"); + if ((opts.rifindex = if_nametoindex(opts.rifname)) == 0) { + if (errno == ENXIO) + xo_errx(1, "interface %s does not exist", + opts.rifname); + else + xo_err(1, "if_nametoindex(%s)", opts.rifname); + } + } + switch (func) { + case F_GET: + if (opts.aflag) { + if (argc != 0) + usage(); + + xo_set_version(ARP_XO_VERSION); + xo_open_container("arp"); + xo_open_list("arp-cache"); + + struct in_addr all_addrs = {}; + print_entries(opts.rifindex, all_addrs); + + xo_close_list("arp-cache"); + xo_close_container("arp"); + if (xo_finish() < 0) + xo_err(1, "stdout"); + } else { + if (argc != 1) + usage(); + rtn = get(argv[0]); + } + break; + case F_SET: + case F_REPLACE: + if (argc < 2 || argc > 6) + usage(); + if (func == F_REPLACE) + (void)delete(argv[0]); + rtn = set(argc, argv) ? 1 : 0; + break; + case F_DELETE: + if (opts.aflag) { + if (argc != 0) + usage(); + struct in_addr all_addrs = {}; + nuke_entries(0, all_addrs); + } else { + if (argc != 1) + usage(); + rtn = delete(argv[0]); + } + break; + case F_FILESET: + if (argc != 1) + usage(); + rtn = file(argv[0]); + break; + } + + if (ifnameindex != NULL) + if_freenameindex(ifnameindex); + + exit(rtn); +} + +/* + * Process a file to set standard arp entries + */ +static int +file(char *name) +{ + FILE *fp; + int i, retval; + char line[100], arg[5][50], *args[5], *p; + + if ((fp = fopen(name, "r")) == NULL) + xo_err(1, "cannot open %s", name); + args[0] = &arg[0][0]; + args[1] = &arg[1][0]; + args[2] = &arg[2][0]; + args[3] = &arg[3][0]; + args[4] = &arg[4][0]; + retval = 0; + while(fgets(line, sizeof(line), fp) != NULL) { + if ((p = strchr(line, '#')) != NULL) + *p = '\0'; + for (p = line; isblank(*p); p++); + if (*p == '\n' || *p == '\0') + continue; + i = sscanf(p, "%49s %49s %49s %49s %49s", arg[0], arg[1], + arg[2], arg[3], arg[4]); + if (i < 2) { + xo_warnx("bad line: %s", line); + retval = 1; + continue; + } + if (set(i, args)) + retval = 1; + } + fclose(fp); + return (retval); +} + +/* + * Given a hostname, fills up a (static) struct sockaddr_in with + * the address of the host and returns a pointer to the + * structure. + */ +struct sockaddr_in * +getaddr(char *host) +{ + struct hostent *hp; + static struct sockaddr_in reply; + + bzero(&reply, sizeof(reply)); + reply.sin_len = sizeof(reply); + reply.sin_family = AF_INET; + reply.sin_addr.s_addr = inet_addr(host); + if (reply.sin_addr.s_addr == INADDR_NONE) { + if (!(hp = gethostbyname(host))) { + xo_warnx("%s: %s", host, hstrerror(h_errno)); + return (NULL); + } + bcopy((char *)hp->h_addr, (char *)&reply.sin_addr, + sizeof reply.sin_addr); + } + return (&reply); +} + +/* + * Returns true if the type is a valid one for ARP. + */ +int +valid_type(int type) +{ + + switch (type) { + case IFT_ETHER: + case IFT_FDDI: + case IFT_IEEE1394: + case IFT_INFINIBAND: + case IFT_ISO88023: + case IFT_ISO88024: + case IFT_L2VLAN: + case IFT_BRIDGE: + return (1); + default: + return (0); + } +} + +/* + * Set an individual arp entry + */ +static int +set(int argc, char **argv) +{ + struct sockaddr_in *dst; /* what are we looking for */ + struct ether_addr *ea; + char *host = argv[0], *eaddr = argv[1]; + struct sockaddr_dl sdl_m; + + argc -= 2; + argv += 2; + + bzero(&sdl_m, sizeof(sdl_m)); + sdl_m.sdl_len = sizeof(sdl_m); + sdl_m.sdl_family = AF_LINK; + + dst = getaddr(host); + if (dst == NULL) + return (1); + while (argc-- > 0) { + if (strcmp(argv[0], "temp") == 0) { + int max_age; + size_t len = sizeof(max_age); + + if (sysctlbyname("net.link.ether.inet.max_age", + &max_age, &len, NULL, 0) != 0) + xo_err(1, "sysctlbyname"); + opts.expire_time = max_age; + } else if (strcmp(argv[0], "pub") == 0) { + opts.flags |= RTF_ANNOUNCE; + if (argc && strcmp(argv[1], "only") == 0) { + /* + * Compatibility: in pre FreeBSD 8 times + * the "only" keyword used to mean that + * an ARP entry should be announced, but + * not installed into routing table. + */ + argc--; argv++; + } + } else if (strcmp(argv[0], "blackhole") == 0) { + if (opts.flags & RTF_REJECT) { + xo_errx(1, "Choose one of blackhole or reject, " + "not both."); + } + opts.flags |= RTF_BLACKHOLE; + } else if (strcmp(argv[0], "reject") == 0) { + if (opts.flags & RTF_BLACKHOLE) { + xo_errx(1, "Choose one of blackhole or reject, " + "not both."); + } + opts.flags |= RTF_REJECT; + } else { + xo_warnx("Invalid parameter '%s'", argv[0]); + usage(); + } + argv++; + } + ea = (struct ether_addr *)LLADDR(&sdl_m); + if ((opts.flags & RTF_ANNOUNCE) && !strcmp(eaddr, "auto")) { + uint32_t ifindex; + if (!get_ifinfo(dst->sin_addr.s_addr, ea, &ifindex)) { + xo_warnx("no interface found for %s", + inet_ntoa(dst->sin_addr)); + return (1); + } + if (opts.rifindex == 0) + opts.rifindex = ifindex; + sdl_m.sdl_alen = ETHER_ADDR_LEN; + } else { + struct ether_addr *ea1 = ether_aton(eaddr); + + if (ea1 == NULL) { + xo_warnx("invalid Ethernet address '%s'", eaddr); + return (1); + } else { + *ea = *ea1; + sdl_m.sdl_alen = ETHER_ADDR_LEN; + } + } +#ifndef WITHOUT_NETLINK + return (set_nl(dst, &sdl_m, host)); +#else + return (set_rtsock(dst, &sdl_m, host)); +#endif +} + +#ifdef WITHOUT_NETLINK +static int +set_rtsock(struct sockaddr_in *dst, struct sockaddr_dl *sdl_m, char *host) +{ + struct sockaddr_in *addr; + struct sockaddr_dl *sdl; + struct rt_msghdr *rtm; + + /* + * In the case a proxy-arp entry is being added for + * a remote end point, the RTF_ANNOUNCE flag in the + * RTM_GET command is an indication to the kernel + * routing code that the interface associated with + * the prefix route covering the local end of the + * PPP link should be returned, on which ARP applies. + */ + rtm = rtmsg(RTM_GET, dst, NULL); + if (rtm == NULL) { + xo_warn("%s", host); + return (1); + } + addr = (struct sockaddr_in *)(rtm + 1); + sdl = (struct sockaddr_dl *)(SA_SIZE(addr) + (char *)addr); + + if ((sdl->sdl_family != AF_LINK) || + (rtm->rtm_flags & RTF_GATEWAY) || + !valid_type(sdl->sdl_type)) { + xo_warnx("cannot intuit interface index and type for %s", host); + return (1); + } + sdl_m->sdl_type = sdl->sdl_type; + sdl_m->sdl_index = sdl->sdl_index; + return (rtmsg(RTM_ADD, dst, sdl_m) == NULL); +} +#endif + +/* + * Display an individual arp entry + */ +static int +get(char *host) +{ + struct sockaddr_in *addr; + int found; + + addr = getaddr(host); + if (addr == NULL) + return (1); + + xo_set_version(ARP_XO_VERSION); + xo_open_container("arp"); + xo_open_list("arp-cache"); + + found = print_entries(opts.rifindex, addr->sin_addr); + + if (found == 0) { + xo_emit("{d:hostname/%s} ({d:ip-address/%s}) -- no entry", + host, inet_ntoa(addr->sin_addr)); + if (opts.rifname) + xo_emit(" on {d:interface/%s}", opts.rifname); + xo_emit("\n"); + } + + xo_close_list("arp-cache"); + xo_close_container("arp"); + if (xo_finish() < 0) + xo_err(1, "stdout"); + + return (found == 0); +} + +/* + * Delete an arp entry + */ +#ifdef WITHOUT_NETLINK +static int +delete_rtsock(char *host) +{ + struct sockaddr_in *addr, *dst; + struct rt_msghdr *rtm; + struct sockaddr_dl *sdl; + + dst = getaddr(host); + if (dst == NULL) + return (1); + + /* + * Perform a regular entry delete first. + */ + opts.flags &= ~RTF_ANNOUNCE; + + for (;;) { /* try twice */ + rtm = rtmsg(RTM_GET, dst, NULL); + if (rtm == NULL) { + xo_warn("%s", host); + return (1); + } + addr = (struct sockaddr_in *)(rtm + 1); + sdl = (struct sockaddr_dl *)(SA_SIZE(addr) + (char *)addr); + + /* + * With the new L2/L3 restructure, the route + * returned is a prefix route. The important + * piece of information from the previous + * RTM_GET is the interface index. In the + * case of ECMP, the kernel will traverse + * the route group for the given entry. + */ + if (sdl->sdl_family == AF_LINK && + !(rtm->rtm_flags & RTF_GATEWAY) && + valid_type(sdl->sdl_type) ) { + addr->sin_addr.s_addr = dst->sin_addr.s_addr; + break; + } + + /* + * Regular entry delete failed, now check if there + * is a proxy-arp entry to remove. + */ + if (opts.flags & RTF_ANNOUNCE) { + xo_warnx("delete: cannot locate %s", host); + return (1); + } + + opts.flags |= RTF_ANNOUNCE; + } + rtm->rtm_flags |= RTF_LLDATA; + if (rtmsg(RTM_DELETE, dst, NULL) != NULL) { + printf("%s (%s) deleted\n", host, inet_ntoa(addr->sin_addr)); + return (0); + } + return (1); +} +#endif + +static int +delete(char *host) +{ +#ifdef WITHOUT_NETLINK + return (delete_rtsock(host)); +#else + return (delete_nl(host)); +#endif +} + + +/* + * Search the arp table and do some action on matching entries + */ +static int +search(u_long addr, action_fn *action) +{ + int mib[6]; + size_t needed; + char *lim, *buf, *next; + struct rt_msghdr *rtm; + struct sockaddr_in *sin2; + struct sockaddr_dl *sdl; + int st, found_entry = 0; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + mib[4] = NET_RT_FLAGS; +#ifdef RTF_LLINFO + mib[5] = RTF_LLINFO; +#else + mib[5] = 0; +#endif + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) + xo_err(1, "route-sysctl-estimate"); + if (needed == 0) /* empty table */ + return 0; + buf = NULL; + for (;;) { + buf = reallocf(buf, needed); + if (buf == NULL) + xo_errx(1, "could not reallocate memory"); + st = sysctl(mib, 6, buf, &needed, NULL, 0); + if (st == 0 || errno != ENOMEM) + break; + needed += needed / 8; + } + if (st == -1) + xo_err(1, "actual retrieval of routing table"); + lim = buf + needed; + for (next = buf; next < lim; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)next; + sin2 = (struct sockaddr_in *)(rtm + 1); + sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2)); + if (opts.rifindex && + (opts.rifindex != sdl->sdl_index)) + continue; + if (addr && + (addr != sin2->sin_addr.s_addr)) + continue; + found_entry = 1; + (*action)(sdl, sin2, rtm); + } + free(buf); + return (found_entry); +} + +/* + * Display an arp entry + */ + +static void +print_entry(struct sockaddr_dl *sdl, + struct sockaddr_in *addr, struct rt_msghdr *rtm) +{ + const char *host; + struct hostent *hp; + struct if_nameindex *p; + + if (ifnameindex == NULL) + if ((ifnameindex = if_nameindex()) == NULL) + xo_err(1, "cannot retrieve interface names"); + + xo_open_instance("arp-cache"); + + if (!opts.nflag) + hp = gethostbyaddr((caddr_t)&(addr->sin_addr), + sizeof addr->sin_addr, AF_INET); + else + hp = 0; + if (hp) + host = hp->h_name; + else { + host = "?"; + if (h_errno == TRY_AGAIN) + opts.nflag = true; + } + xo_emit("{:hostname/%s} ({:ip-address/%s}) at ", host, + inet_ntoa(addr->sin_addr)); + if (sdl->sdl_alen) { + if ((sdl->sdl_type == IFT_ETHER || + sdl->sdl_type == IFT_L2VLAN || + sdl->sdl_type == IFT_BRIDGE) && + sdl->sdl_alen == ETHER_ADDR_LEN) + xo_emit("{:mac-address/%s}", + ether_ntoa((struct ether_addr *)LLADDR(sdl))); + else { + int n = sdl->sdl_nlen > 0 ? sdl->sdl_nlen + 1 : 0; + + xo_emit("{:mac-address/%s}", link_ntoa(sdl) + n); + } + } else + xo_emit("{d:/(incomplete)}{en:incomplete/true}"); + + for (p = ifnameindex; p && p->if_index && p->if_name; p++) { + if (p->if_index == sdl->sdl_index) { + xo_emit(" on {:interface/%s}", p->if_name); + break; + } + } + + if (rtm->rtm_rmx.rmx_expire == 0) + xo_emit("{d:/ permanent}{en:permanent/true}"); + else { + static struct timespec tp; + time_t expire_time = 0; + + if (tp.tv_sec == 0) + clock_gettime(CLOCK_MONOTONIC, &tp); + if ((expire_time = rtm->rtm_rmx.rmx_expire - tp.tv_sec) > 0) + xo_emit(" expires in {:expires/%d} seconds", + (int)expire_time); + else + xo_emit("{d:/ expired}{en:expired/true}"); + } + + if (rtm->rtm_flags & RTF_ANNOUNCE) + xo_emit("{d:/ published}{en:published/true}"); + + switch(sdl->sdl_type) { + case IFT_ETHER: + xo_emit(" [{:type/ethernet}]"); + break; + case IFT_FDDI: + xo_emit(" [{:type/fddi}]"); + break; + case IFT_ATM: + xo_emit(" [{:type/atm}]"); + break; + case IFT_L2VLAN: + xo_emit(" [{:type/vlan}]"); + break; + case IFT_IEEE1394: + xo_emit(" [{:type/firewire}]"); + break; + case IFT_BRIDGE: + xo_emit(" [{:type/bridge}]"); + break; + case IFT_INFINIBAND: + xo_emit(" [{:type/infiniband}]"); + break; + default: + break; + } + + xo_emit("\n"); + + xo_close_instance("arp-cache"); +} + +static int +print_entries(uint32_t ifindex, struct in_addr addr) +{ +#ifndef WITHOUT_NETLINK + return (print_entries_nl(ifindex, addr)); +#else + return (search(addr.s_addr, print_entry)); +#endif +} + + +/* + * Nuke an arp entry + */ +static void +nuke_entry(struct sockaddr_dl *sdl __unused, + struct sockaddr_in *addr, struct rt_msghdr *rtm) +{ + char ip[20]; + + if (rtm->rtm_flags & RTF_PINNED) + return; + + snprintf(ip, sizeof(ip), "%s", inet_ntoa(addr->sin_addr)); + delete(ip); +} + +static void +nuke_entries(uint32_t ifindex, struct in_addr addr) +{ + search(addr.s_addr, nuke_entry); +} + +static void +usage(void) +{ + xo_error("%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + "usage: arp [-n] [-i interface] hostname", + " arp [-n] [-i interface] -a", + " arp -d hostname [pub]", + " arp -d [-i interface] -a", + " arp -s hostname ether_addr [temp] [reject | blackhole] [pub [only]]", + " arp -S hostname ether_addr [temp] [reject | blackhole] [pub [only]]", + " arp -f filename"); + exit(1); +} + +static struct rt_msghdr * +rtmsg(int cmd, struct sockaddr_in *dst, struct sockaddr_dl *sdl) +{ + static int seq; + int rlen; + int l; + static int s = -1; + static pid_t pid; + + static struct { + struct rt_msghdr m_rtm; + char m_space[512]; + } m_rtmsg; + + struct rt_msghdr *rtm = &m_rtmsg.m_rtm; + char *cp = m_rtmsg.m_space; + + if (s < 0) { /* first time: open socket, get pid */ + s = socket(PF_ROUTE, SOCK_RAW, 0); + if (s < 0) + xo_err(1, "socket"); + pid = getpid(); + } + + errno = 0; + /* + * XXX RTM_DELETE relies on a previous RTM_GET to fill the buffer + * appropriately. + */ + if (cmd == RTM_DELETE) + goto doit; + bzero((char *)&m_rtmsg, sizeof(m_rtmsg)); + rtm->rtm_flags = opts.flags; + rtm->rtm_version = RTM_VERSION; + + switch (cmd) { + default: + xo_errx(1, "internal wrong cmd"); + case RTM_ADD: + rtm->rtm_addrs |= RTA_GATEWAY; + if (opts.expire_time != 0) { + struct timespec tp; + + clock_gettime(CLOCK_MONOTONIC, &tp); + rtm->rtm_rmx.rmx_expire = opts.expire_time + tp.tv_sec; + } + rtm->rtm_inits = RTV_EXPIRE; + rtm->rtm_flags |= (RTF_HOST | RTF_STATIC | RTF_LLDATA); + /* FALLTHROUGH */ + case RTM_GET: + rtm->rtm_addrs |= RTA_DST; + } +#define NEXTADDR(w, s) \ + do { \ + if ((s) != NULL && rtm->rtm_addrs & (w)) { \ + bcopy((s), cp, sizeof(*(s))); \ + cp += SA_SIZE(s); \ + } \ + } while (0) + + NEXTADDR(RTA_DST, dst); + NEXTADDR(RTA_GATEWAY, sdl); + + rtm->rtm_msglen = cp - (char *)&m_rtmsg; +doit: + l = rtm->rtm_msglen; + rtm->rtm_seq = ++seq; + rtm->rtm_type = cmd; + if ((rlen = write(s, (char *)&m_rtmsg, l)) < 0) { + if (errno != ESRCH || cmd != RTM_DELETE) { + xo_warn("writing to routing socket"); + return (NULL); + } + } + do { + l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg)); + } while (l > 0 && (rtm->rtm_type != cmd || rtm->rtm_seq != seq || + rtm->rtm_pid != pid)); + if (l < 0) + xo_warn("read from routing socket"); + return (rtm); +} + +/* + * get_ifinfo - get the hardware address and if_index of an interface + * on the same subnet as ipaddr. + */ +int +get_ifinfo(in_addr_t ipaddr, struct ether_addr *hwaddr, uint32_t *pifindex) +{ + struct ifaddrs *ifa, *ifd, *ifas = NULL; + in_addr_t ina, mask; + struct sockaddr_dl *dla; + int retval = 0; + + /* + * Scan through looking for an interface with an Internet + * address on the same subnet as `ipaddr'. + */ + if (getifaddrs(&ifas) < 0) { + xo_warnx("getifaddrs"); + goto done; + } + + for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_netmask == NULL) + continue; + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + /* + * Check that the interface is up, + * and not point-to-point or loopback. + */ + if ((ifa->ifa_flags & + (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT| + IFF_LOOPBACK|IFF_NOARP)) != (IFF_UP|IFF_BROADCAST)) + continue; + /* Get its netmask and check that it's on the right subnet. */ + mask = ((struct sockaddr_in *) + ifa->ifa_netmask)->sin_addr.s_addr; + ina = ((struct sockaddr_in *) + ifa->ifa_addr)->sin_addr.s_addr; + if ((ipaddr & mask) == (ina & mask)) + break; /* ok, we got it! */ + } + if (ifa == NULL) + goto done; + if (pifindex != NULL) + *pifindex = if_nametoindex(ifa->ifa_name); + if (hwaddr == NULL) { + /* ether addr is not required */ + retval = ETHER_ADDR_LEN; + goto done; + } + /* + * Now scan through again looking for a link-level address + * for this interface. + */ + for (ifd = ifas; ifd != NULL; ifd = ifd->ifa_next) { + if (ifd->ifa_addr == NULL) + continue; + if (strcmp(ifa->ifa_name, ifd->ifa_name) == 0 && + ifd->ifa_addr->sa_family == AF_LINK) + break; + } + if (ifd == NULL) + goto done; + /* + * Found the link-level address - copy it out + */ + dla = (struct sockaddr_dl *)ifd->ifa_addr; + memcpy(hwaddr, LLADDR(dla), dla->sdl_alen); + printf("using interface %s for proxy with address %s\n", ifa->ifa_name, + ether_ntoa(hwaddr)); + retval = dla->sdl_alen; +done: + if (ifas != NULL) + freeifaddrs(ifas); + return (retval); +} diff --git a/usr.sbin/arp/arp.h b/usr.sbin/arp/arp.h new file mode 100644 index 000000000000..512a238df425 --- /dev/null +++ b/usr.sbin/arp/arp.h @@ -0,0 +1,22 @@ +#ifndef _USR_SBIN_ARP_ARP_H_ +#define _USR_SBIN_ARP_ARP_H_ + +int valid_type(int type); +int get_ifinfo(in_addr_t ipaddr, struct ether_addr *hwaddr, uint32_t *pifindex); +struct sockaddr_in *getaddr(char *host); + +struct arp_opts { + bool aflag; + bool nflag; + time_t expire_time; + int flags; + char *rifname; + uint32_t rifindex; +}; +extern struct arp_opts opts; + +int print_entries_nl(uint32_t ifindex, struct in_addr addr); +int delete_nl(char *host); +int set_nl(struct sockaddr_in *dst, struct sockaddr_dl *sdl, char *host); + +#endif diff --git a/usr.sbin/arp/arp_netlink.c b/usr.sbin/arp/arp_netlink.c new file mode 100644 index 000000000000..34f21cf96f4f --- /dev/null +++ b/usr.sbin/arp/arp_netlink.c @@ -0,0 +1,451 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <errno.h> +#include <netdb.h> + +#include <sys/bitcount.h> +#include <sys/param.h> +#include <sys/linker.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/types.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <net/ethernet.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <netlink/netlink.h> +#include <netlink/netlink_route.h> +#include <netlink/netlink_snl.h> +#include <netlink/netlink_snl_route.h> +#include <netlink/netlink_snl_route_compat.h> +#include <netlink/netlink_snl_route_parsers.h> + +#include <libxo/xo.h> +#include "arp.h" + +#define RTF_ANNOUNCE RTF_PROTO2 + +static void +nl_init_socket(struct snl_state *ss) +{ + if (snl_init(ss, NETLINK_ROUTE)) + return; + + if (modfind("netlink") == -1 && errno == ENOENT) { + /* Try to load */ + if (kldload("netlink") == -1) + xo_err(1, "netlink is not loaded and load attempt failed"); + if (snl_init(ss, NETLINK_ROUTE)) + return; + } + + xo_err(1, "unable to open netlink socket"); +} + +static bool +get_link_info(struct snl_state *ss, uint32_t ifindex, + struct snl_parsed_link_simple *link) +{ + struct snl_writer nw; + + snl_init_writer(ss, &nw); + + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK); + struct ifinfomsg *ifmsg = snl_reserve_msg_object(&nw, struct ifinfomsg); + if (ifmsg != NULL) + ifmsg->ifi_index = ifindex; + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) + return (false); + + hdr = snl_read_reply(ss, hdr->nlmsg_seq); + + if (hdr == NULL || hdr->nlmsg_type != RTM_NEWLINK) + return (false); + + if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, link)) + return (false); + + return (true); +} + + + +static bool +has_l2(struct snl_state *ss, uint32_t ifindex, uint32_t *pflags) +{ + struct snl_parsed_link_simple link = {}; + + *pflags = 0; + if (!get_link_info(ss, ifindex, &link)) + return (false); + + *pflags = link.ifi_flags; + return (valid_type(link.ifi_type) != 0); +} + +static uint32_t +get_myfib(void) +{ + uint32_t fibnum = 0; + size_t len = sizeof(fibnum); + + sysctlbyname("net.my_fibnum", (void *)&fibnum, &len, NULL, 0); + + return (fibnum); +} + +static int +guess_ifindex(struct snl_state *ss, uint32_t fibnum, struct in_addr addr) +{ + struct snl_writer nw; + uint32_t ifindex, ifflags; + + snl_init_writer(ss, &nw); + + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETROUTE); + struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg); + rtm->rtm_family = AF_INET; + + struct sockaddr_in dst = { .sin_family = AF_INET, .sin_addr = addr }; + snl_add_msg_attr_ip(&nw, RTA_DST, (struct sockaddr *)&dst); + snl_add_msg_attr_u32(&nw, RTA_TABLE, fibnum); + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) + return (0); + + hdr = snl_read_reply(ss, hdr->nlmsg_seq); + + if (hdr->nlmsg_type != NL_RTM_NEWROUTE) { + /* No route found, unable to guess ifindex */ + return (0); + } + + struct snl_parsed_route r = {}; + if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r)) + return (0); + + if (r.rta_multipath.num_nhops > 0 || (r.rta_rtflags & RTF_GATEWAY)) + return (0); + + /* Check if the interface is of supported type */ + if (has_l2(ss, r.rta_oif, &ifflags)) + return (r.rta_oif); + + /* Check if we are doing proxy arp for P2P interface */ + if (ifflags & IFF_POINTOPOINT) { + /* Guess interface by dst prefix */ + if (get_ifinfo(addr.s_addr, NULL, &ifindex)) + return (ifindex); + } + + /* Check the case when we matched the loopback route for P2P */ + snl_init_writer(ss, &nw); + hdr = snl_create_msg_request(&nw, RTM_GETNEXTHOP); + snl_reserve_msg_object(&nw, struct nhmsg); + + int off = snl_add_msg_attr_nested(&nw, NHA_FREEBSD); + snl_add_msg_attr_u32(&nw, NHAF_KID, r.rta_knh_id); + snl_add_msg_attr_u8(&nw, NHAF_FAMILY, AF_INET); + snl_add_msg_attr_u32(&nw, NHAF_TABLE, fibnum); + snl_end_attr_nested(&nw, off); + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) + return (0); + + hdr = snl_read_reply(ss, hdr->nlmsg_seq); + + if (hdr->nlmsg_type != NL_RTM_NEWNEXTHOP) { + /* No nexthop found, unable to guess ifindex */ + return (0); + } + + struct snl_parsed_nhop nh = {}; + if (!snl_parse_nlmsg(ss, hdr, &snl_nhmsg_parser, &nh)) + return (0); + + return (nh.nhaf_aif); +} + +static uint32_t +fix_ifindex(struct snl_state *ss, uint32_t ifindex, struct in_addr addr) +{ + if (ifindex == 0) + ifindex = guess_ifindex(ss, get_myfib(), addr); + return (ifindex); +} + +static void +print_entry(struct snl_parsed_neigh *neigh, struct snl_parsed_link_simple *link) +{ + const char *host; + struct hostent *hp; + struct sockaddr_in *addr = (struct sockaddr_in *)neigh->nda_dst; + + xo_open_instance("arp-cache"); + + if (!opts.nflag) + hp = gethostbyaddr((caddr_t)&(addr->sin_addr), + sizeof(addr->sin_addr), AF_INET); + else + hp = 0; + if (hp) + host = hp->h_name; + else { + host = "?"; + if (h_errno == TRY_AGAIN) + opts.nflag = true; + } + xo_emit("{:hostname/%s} ({:ip-address/%s}) at ", host, + inet_ntoa(addr->sin_addr)); + if (neigh->nda_lladdr != NULL) { + struct sockaddr_dl sdl = { + .sdl_family = AF_LINK, + .sdl_type = link->ifi_type, + .sdl_len = sizeof(struct sockaddr_dl), + .sdl_alen = NLA_DATA_LEN(neigh->nda_lladdr), + }; + memcpy(sdl.sdl_data, NLA_DATA(neigh->nda_lladdr), sdl.sdl_alen); + + if ((sdl.sdl_type == IFT_ETHER || + sdl.sdl_type == IFT_L2VLAN || + sdl.sdl_type == IFT_BRIDGE) && + sdl.sdl_alen == ETHER_ADDR_LEN) + xo_emit("{:mac-address/%s}", + ether_ntoa((struct ether_addr *)LLADDR(&sdl))); + else { + + xo_emit("{:mac-address/%s}", link_ntoa(&sdl)); + } + } else + xo_emit("{d:/(incomplete)}{en:incomplete/true}"); + xo_emit(" on {:interface/%s}", link->ifla_ifname); + + if (neigh->ndaf_next_ts == 0) + xo_emit("{d:/ permanent}{en:permanent/true}"); + else { + time_t expire_time; + struct timeval now; + + gettimeofday(&now, 0); + if ((expire_time = neigh->ndaf_next_ts - now.tv_sec) > 0) + xo_emit(" expires in {:expires/%d} seconds", + (int)expire_time); + else + xo_emit("{d:/ expired}{en:expired/true}"); + } + + if (neigh->ndm_flags & NTF_PROXY) + xo_emit("{d:/ published}{en:published/true}"); + + switch(link->ifi_type) { + case IFT_ETHER: + xo_emit(" [{:type/ethernet}]"); + break; + case IFT_FDDI: + xo_emit(" [{:type/fddi}]"); + break; + case IFT_ATM: + xo_emit(" [{:type/atm}]"); + break; + case IFT_L2VLAN: + xo_emit(" [{:type/vlan}]"); + break; + case IFT_IEEE1394: + xo_emit(" [{:type/firewire}]"); + break; + case IFT_BRIDGE: + xo_emit(" [{:type/bridge}]"); + break; + case IFT_INFINIBAND: + xo_emit(" [{:type/infiniband}]"); + break; + default: + break; + } + + xo_emit("\n"); + + xo_close_instance("arp-cache"); +} + +int +print_entries_nl(uint32_t ifindex, struct in_addr addr) +{ + struct snl_state ss_req = {}, ss_cmd = {}; + struct snl_parsed_link_simple link = {}; + struct snl_writer nw; + + nl_init_socket(&ss_req); + snl_init_writer(&ss_req, &nw); + + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETNEIGH); + struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); + if (ndmsg != NULL) { + ndmsg->ndm_family = AF_INET; + /* let kernel filter results by interface if provided */ + ndmsg->ndm_ifindex = ifindex; + } + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss_req, hdr)) { + snl_free(&ss_req); + return (0); + } + + uint32_t nlmsg_seq = hdr->nlmsg_seq; + struct snl_errmsg_data e = {}; + int count = 0; + nl_init_socket(&ss_cmd); + + while ((hdr = snl_read_reply_multi(&ss_req, nlmsg_seq, &e)) != NULL) { + struct snl_parsed_neigh neigh = {}; + struct sockaddr_in *neighaddr; + + if (!snl_parse_nlmsg(&ss_req, hdr, &snl_rtm_neigh_parser, &neigh)) + continue; + + if (neigh.nda_ifindex != link.ifi_index) { + snl_clear_lb(&ss_cmd); + memset(&link, 0, sizeof(link)); + if (!get_link_info(&ss_cmd, neigh.nda_ifindex, &link)) + continue; + } + + /* filter results based on host if provided */ + neighaddr = (struct sockaddr_in *)neigh.nda_dst; + if (addr.s_addr && + (addr.s_addr != neighaddr->sin_addr.s_addr)) + continue; + + print_entry(&neigh, &link); + count++; + snl_clear_lb(&ss_req); + } + + snl_free(&ss_req); + snl_free(&ss_cmd); + + return (count); +} + +int +delete_nl(char *host) +{ + struct snl_state ss = {}; + struct snl_writer nw; + struct sockaddr_in *dst; + uint32_t ifindex = opts.rifindex; + + dst = getaddr(host); + if (dst == NULL) + return (1); + + nl_init_socket(&ss); + + ifindex = fix_ifindex(&ss, ifindex, dst->sin_addr); + if (ifindex == 0) { + xo_warnx("delete: cannot locate %s", host); + snl_free(&ss); + return (0); + } + + snl_init_writer(&ss, &nw); + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_DELNEIGH); + struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); + if (ndmsg != NULL) { + ndmsg->ndm_family = AF_INET; + ndmsg->ndm_ifindex = ifindex; + } + snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)dst); + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss, hdr)) { + snl_free(&ss); + return (1); + } + + struct snl_errmsg_data e = {}; + snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); + if (e.error != 0) { + if (e.error_str != NULL) + xo_warnx("delete %s: %s (%s)", host, strerror(e.error), e.error_str); + else + xo_warnx("delete %s: %s", host, strerror(e.error)); + } else + printf("%s (%s) deleted\n", host, inet_ntoa(dst->sin_addr)); + + snl_free(&ss); + + return (e.error != 0); +} + +int +set_nl(struct sockaddr_in *dst, struct sockaddr_dl *sdl, char *host) +{ + struct snl_state ss = {}; + struct snl_writer nw; + uint32_t ifindex = opts.rifindex; + + nl_init_socket(&ss); + + ifindex = fix_ifindex(&ss, ifindex, dst->sin_addr); + if (ifindex == 0) { + xo_warnx("set: cannot locate %s", host); + snl_free(&ss); + return (0); + } + + snl_init_writer(&ss, &nw); + struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_NEWNEIGH); + hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); + if (ndmsg != NULL) { + uint8_t nl_flags = 0; + + ndmsg->ndm_family = AF_INET; + ndmsg->ndm_ifindex = ifindex; + ndmsg->ndm_state = (opts.expire_time == 0) ? \ + NUD_PERMANENT : NUD_NONE; + + if (opts.flags & RTF_ANNOUNCE) + nl_flags |= NTF_PROXY; + if (opts.expire_time == 0) + nl_flags |= NTF_STICKY; + ndmsg->ndm_flags = nl_flags; + } + snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)dst); + snl_add_msg_attr(&nw, NDA_LLADDR, sdl->sdl_alen, LLADDR(sdl)); + + if (opts.expire_time != 0) { + struct timeval now; + + gettimeofday(&now, 0); + int off = snl_add_msg_attr_nested(&nw, NDA_FREEBSD); + snl_add_msg_attr_u32(&nw, NDAF_NEXT_STATE_TS, now.tv_sec + opts.expire_time); + snl_end_attr_nested(&nw, off); + } + + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss, hdr)) { + snl_free(&ss); + return (1); + } + + struct snl_errmsg_data e = {}; + snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); + if (e.error != 0) { + if (e.error_str != NULL) + xo_warnx("set: %s: %s (%s)", host, strerror(e.error), e.error_str); + else + xo_warnx("set %s: %s", host, strerror(e.error)); + } + snl_free(&ss); + + return (e.error != 0); +} + |
