mirror of
				https://git.proxmox.com/git/mirror_iproute2
				synced 2025-10-31 11:48:18 +00:00 
			
		
		
		
	 6648853975
			
		
	
	
		6648853975
		
	
	
	
	
		
			
			Any iproute utility that uses any function from lib/utils.c needs to declare its own resolve_hosts variable instance although it does not need/use hostname resolving functionality (currently only 'ip' and 'ss' commands uses this). The patch declares single common instance of resolve_hosts directly in utils.c so the existing ones can be removed (the same approach that is used for timestamp_short). Cc: Jiri Pirko <jiri@mellanox.com> Cc: Arkadi Sharshevsky <arkadis@mellanox.com> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
		
			
				
	
	
		
			838 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			838 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * arpd.c	ARP helper daemon.
 | |
|  *
 | |
|  *		This program is free software; you can redistribute it and/or
 | |
|  *		modify it under the terms of the GNU General Public License
 | |
|  *		as published by the Free Software Foundation; either version
 | |
|  *		2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
 | |
|  */
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <syslog.h>
 | |
| #include <malloc.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| #include <stdlib.h>
 | |
| #include <netdb.h>
 | |
| #include <db_185.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <sys/poll.h>
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>
 | |
| #include <sys/uio.h>
 | |
| #include <sys/socket.h>
 | |
| #include <sys/time.h>
 | |
| #include <time.h>
 | |
| #include <signal.h>
 | |
| #include <linux/if.h>
 | |
| #include <linux/if_ether.h>
 | |
| #include <linux/if_arp.h>
 | |
| #include <netinet/in.h>
 | |
| #include <arpa/inet.h>
 | |
| #include <linux/if_packet.h>
 | |
| #include <linux/filter.h>
 | |
| 
 | |
| #include "libnetlink.h"
 | |
| #include "utils.h"
 | |
| #include "rt_names.h"
 | |
| 
 | |
| DB	*dbase;
 | |
| char	*dbname = "/var/lib/arpd/arpd.db";
 | |
| 
 | |
| int	ifnum;
 | |
| int	*ifvec;
 | |
| char	**ifnames;
 | |
| 
 | |
| struct dbkey {
 | |
| 	__u32	iface;
 | |
| 	__u32	addr;
 | |
| };
 | |
| 
 | |
| #define IS_NEG(x)	(((__u8 *)(x))[0] == 0xFF)
 | |
| #define NEG_TIME(x)	(((x)[2]<<24)|((x)[3]<<16)|((x)[4]<<8)|(x)[5])
 | |
| #define NEG_AGE(x)	((__u32)time(NULL) - NEG_TIME((__u8 *)x))
 | |
| #define NEG_VALID(x)	(NEG_AGE(x) < negative_timeout)
 | |
| #define NEG_CNT(x)	(((__u8 *)(x))[1])
 | |
| 
 | |
| struct rtnl_handle rth;
 | |
| 
 | |
| struct pollfd pset[2];
 | |
| int udp_sock = -1;
 | |
| 
 | |
| volatile int do_exit;
 | |
| volatile int do_sync;
 | |
| volatile int do_stats;
 | |
| 
 | |
| struct {
 | |
| 	unsigned long arp_new;
 | |
| 	unsigned long arp_change;
 | |
| 
 | |
| 	unsigned long app_recv;
 | |
| 	unsigned long app_success;
 | |
| 	unsigned long app_bad;
 | |
| 	unsigned long app_neg;
 | |
| 	unsigned long app_suppressed;
 | |
| 
 | |
| 	unsigned long kern_neg;
 | |
| 	unsigned long kern_new;
 | |
| 	unsigned long kern_change;
 | |
| 
 | |
| 	unsigned long probes_sent;
 | |
| 	unsigned long probes_suppressed;
 | |
| } stats;
 | |
| 
 | |
| int active_probing;
 | |
| int negative_timeout = 60;
 | |
| int no_kernel_broadcasts;
 | |
| int broadcast_rate = 1000;
 | |
| int broadcast_burst = 3000;
 | |
| int poll_timeout = 30000;
 | |
| 
 | |
| static void usage(void)
 | |
| {
 | |
| 	fprintf(stderr,
 | |
| 		"Usage: arpd [ -lkh? ] [ -a N ] [ -b dbase ] [ -B number ] [ -f file ] [ -n time ] [-p interval ] [ -R rate ] [ interfaces ]\n");
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| static int handle_if(int ifindex)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	if (ifnum == 0)
 | |
| 		return 1;
 | |
| 
 | |
| 	for (i = 0; i < ifnum; i++)
 | |
| 		if (ifvec[i] == ifindex)
 | |
| 			return 1;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int sysctl_adjusted;
 | |
| 
 | |
| static void do_sysctl_adjustments(void)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	if (!ifnum)
 | |
| 		return;
 | |
| 
 | |
| 	for (i = 0; i < ifnum; i++) {
 | |
| 		char buf[128];
 | |
| 		FILE *fp;
 | |
| 
 | |
| 		if (active_probing) {
 | |
| 			sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/mcast_solicit", ifnames[i]);
 | |
| 			if ((fp = fopen(buf, "w")) != NULL) {
 | |
| 				if (no_kernel_broadcasts)
 | |
| 					strcpy(buf, "0\n");
 | |
| 				else
 | |
| 					sprintf(buf, "%d\n", active_probing >= 2 ? 1 : 3-active_probing);
 | |
| 				fputs(buf, fp);
 | |
| 				fclose(fp);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/app_solicit", ifnames[i]);
 | |
| 		if ((fp = fopen(buf, "w")) != NULL) {
 | |
| 			sprintf(buf, "%d\n", active_probing <= 1 ? 1 : active_probing);
 | |
| 			fputs(buf, fp);
 | |
| 			fclose(fp);
 | |
| 		}
 | |
| 	}
 | |
| 	sysctl_adjusted = 1;
 | |
| }
 | |
| 
 | |
| static void undo_sysctl_adjustments(void)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	if (!sysctl_adjusted)
 | |
| 		return;
 | |
| 
 | |
| 	for (i = 0; i < ifnum; i++) {
 | |
| 		char buf[128];
 | |
| 		FILE *fp;
 | |
| 
 | |
| 		if (active_probing) {
 | |
| 			sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/mcast_solicit", ifnames[i]);
 | |
| 			if ((fp = fopen(buf, "w")) != NULL) {
 | |
| 				strcpy(buf, "3\n");
 | |
| 				fputs(buf, fp);
 | |
| 				fclose(fp);
 | |
| 			}
 | |
| 		}
 | |
| 		sprintf(buf, "/proc/sys/net/ipv4/neigh/%s/app_solicit", ifnames[i]);
 | |
| 		if ((fp = fopen(buf, "w")) != NULL) {
 | |
| 			strcpy(buf, "0\n");
 | |
| 			fputs(buf, fp);
 | |
| 			fclose(fp);
 | |
| 		}
 | |
| 	}
 | |
| 	sysctl_adjusted = 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int send_probe(int ifindex, __u32 addr)
 | |
| {
 | |
| 	struct ifreq ifr = { .ifr_ifindex = ifindex };
 | |
| 	struct sockaddr_in dst = {
 | |
| 		.sin_family = AF_INET,
 | |
| 		.sin_port = htons(1025),
 | |
| 		.sin_addr.s_addr = addr,
 | |
| 	};
 | |
| 	socklen_t len;
 | |
| 	unsigned char buf[256];
 | |
| 	struct arphdr *ah = (struct arphdr *)buf;
 | |
| 	unsigned char *p = (unsigned char *)(ah+1);
 | |
| 	struct sockaddr_ll sll = {
 | |
| 		.sll_family = AF_PACKET,
 | |
| 		.sll_ifindex = ifindex,
 | |
| 		.sll_protocol = htons(ETH_P_ARP),
 | |
| 	};
 | |
| 
 | |
| 	if (ioctl(udp_sock, SIOCGIFNAME, &ifr))
 | |
| 		return -1;
 | |
| 	if (ioctl(udp_sock, SIOCGIFHWADDR, &ifr))
 | |
| 		return -1;
 | |
| 	if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER)
 | |
| 		return -1;
 | |
| 	if (setsockopt(udp_sock, SOL_SOCKET, SO_BINDTODEVICE, ifr.ifr_name, strlen(ifr.ifr_name)+1) < 0)
 | |
| 		return -1;
 | |
| 
 | |
| 	if (connect(udp_sock, (struct sockaddr *)&dst, sizeof(dst)) < 0)
 | |
| 		return -1;
 | |
| 	len = sizeof(dst);
 | |
| 	if (getsockname(udp_sock, (struct sockaddr *)&dst, &len) < 0)
 | |
| 		return -1;
 | |
| 
 | |
| 	ah->ar_hrd = htons(ifr.ifr_hwaddr.sa_family);
 | |
| 	ah->ar_pro = htons(ETH_P_IP);
 | |
| 	ah->ar_hln = 6;
 | |
| 	ah->ar_pln = 4;
 | |
| 	ah->ar_op  = htons(ARPOP_REQUEST);
 | |
| 
 | |
| 	memcpy(p, ifr.ifr_hwaddr.sa_data, ah->ar_hln);
 | |
| 	p += ah->ar_hln;
 | |
| 
 | |
| 	memcpy(p, &dst.sin_addr, 4);
 | |
| 	p += 4;
 | |
| 
 | |
| 	memset(sll.sll_addr, 0xFF, sizeof(sll.sll_addr));
 | |
| 	memcpy(p, &sll.sll_addr, ah->ar_hln);
 | |
| 	p += ah->ar_hln;
 | |
| 
 | |
| 	memcpy(p, &addr, 4);
 | |
| 	p += 4;
 | |
| 
 | |
| 	if (sendto(pset[0].fd, buf, p-buf, 0, (struct sockaddr *)&sll, sizeof(sll)) < 0)
 | |
| 		return -1;
 | |
| 	stats.probes_sent++;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* Be very tough on sending probes: 1 per second with burst of 3. */
 | |
| 
 | |
| static int queue_active_probe(int ifindex, __u32 addr)
 | |
| {
 | |
| 	static struct timeval prev;
 | |
| 	static int buckets;
 | |
| 	struct timeval now;
 | |
| 
 | |
| 	gettimeofday(&now, NULL);
 | |
| 	if (prev.tv_sec) {
 | |
| 		int diff = (now.tv_sec-prev.tv_sec)*1000+(now.tv_usec-prev.tv_usec)/1000;
 | |
| 
 | |
| 		buckets += diff;
 | |
| 	} else {
 | |
| 		buckets = broadcast_burst;
 | |
| 	}
 | |
| 	if (buckets > broadcast_burst)
 | |
| 		buckets = broadcast_burst;
 | |
| 	if (buckets >= broadcast_rate && !send_probe(ifindex, addr)) {
 | |
| 		buckets -= broadcast_rate;
 | |
| 		prev = now;
 | |
| 		return 0;
 | |
| 	}
 | |
| 	stats.probes_suppressed++;
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| static int respond_to_kernel(int ifindex, __u32 addr, char *lla, int llalen)
 | |
| {
 | |
| 	struct {
 | |
| 		struct nlmsghdr	n;
 | |
| 		struct ndmsg		ndm;
 | |
| 		char			buf[256];
 | |
| 	} req = {
 | |
| 		.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)),
 | |
| 		.n.nlmsg_flags = NLM_F_REQUEST,
 | |
| 		.n.nlmsg_type = RTM_NEWNEIGH,
 | |
| 		.ndm.ndm_family = AF_INET,
 | |
| 		.ndm.ndm_state = NUD_STALE,
 | |
| 		.ndm.ndm_ifindex = ifindex,
 | |
| 		.ndm.ndm_type = RTN_UNICAST,
 | |
| 	};
 | |
| 
 | |
| 	addattr_l(&req.n, sizeof(req), NDA_DST, &addr, 4);
 | |
| 	addattr_l(&req.n, sizeof(req), NDA_LLADDR, lla, llalen);
 | |
| 	return rtnl_send(&rth, &req, req.n.nlmsg_len) <= 0;
 | |
| }
 | |
| 
 | |
| static void prepare_neg_entry(__u8 *ndata, __u32 stamp)
 | |
| {
 | |
| 	ndata[0] = 0xFF;
 | |
| 	ndata[1] = 0;
 | |
| 	ndata[2] = stamp>>24;
 | |
| 	ndata[3] = stamp>>16;
 | |
| 	ndata[4] = stamp>>8;
 | |
| 	ndata[5] = stamp;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int do_one_request(struct nlmsghdr *n)
 | |
| {
 | |
| 	struct ndmsg *ndm = NLMSG_DATA(n);
 | |
| 	int len = n->nlmsg_len;
 | |
| 	struct rtattr *tb[NDA_MAX+1];
 | |
| 	struct dbkey key;
 | |
| 	DBT dbkey, dbdat;
 | |
| 	int do_acct = 0;
 | |
| 
 | |
| 	if (n->nlmsg_type == NLMSG_DONE) {
 | |
| 		dbase->sync(dbase, 0);
 | |
| 
 | |
| 		/* Now we have at least mirror of kernel db, so that
 | |
| 		 * may start real resolution.
 | |
| 		 */
 | |
| 		do_sysctl_adjustments();
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (n->nlmsg_type != RTM_GETNEIGH && n->nlmsg_type != RTM_NEWNEIGH)
 | |
| 		return 0;
 | |
| 
 | |
| 	len -= NLMSG_LENGTH(sizeof(*ndm));
 | |
| 	if (len < 0)
 | |
| 		return -1;
 | |
| 
 | |
| 	if (ndm->ndm_family != AF_INET ||
 | |
| 	    (ifnum && !handle_if(ndm->ndm_ifindex)) ||
 | |
| 	    ndm->ndm_flags ||
 | |
| 	    ndm->ndm_type != RTN_UNICAST ||
 | |
| 	    !(ndm->ndm_state&~NUD_NOARP))
 | |
| 		return 0;
 | |
| 
 | |
| 	parse_rtattr(tb, NDA_MAX, NDA_RTA(ndm), len);
 | |
| 
 | |
| 	if (!tb[NDA_DST])
 | |
| 		return 0;
 | |
| 
 | |
| 	key.iface = ndm->ndm_ifindex;
 | |
| 	memcpy(&key.addr, RTA_DATA(tb[NDA_DST]), 4);
 | |
| 	dbkey.data = &key;
 | |
| 	dbkey.size = sizeof(key);
 | |
| 
 | |
| 	if (dbase->get(dbase, &dbkey, &dbdat, 0) != 0) {
 | |
| 		dbdat.data = 0;
 | |
| 		dbdat.size = 0;
 | |
| 	}
 | |
| 
 | |
| 	if (n->nlmsg_type == RTM_GETNEIGH) {
 | |
| 		if (!(n->nlmsg_flags&NLM_F_REQUEST))
 | |
| 			return 0;
 | |
| 
 | |
| 		if (!(ndm->ndm_state&(NUD_PROBE|NUD_INCOMPLETE))) {
 | |
| 			stats.app_bad++;
 | |
| 			return 0;
 | |
| 		}
 | |
| 
 | |
| 		if (ndm->ndm_state&NUD_PROBE) {
 | |
| 			/* If we get this, kernel still has some valid
 | |
| 			 * address, but unicast probing failed and host
 | |
| 			 * is either dead or changed its mac address.
 | |
| 			 * Kernel is going to initiate broadcast resolution.
 | |
| 			 * OK, we invalidate our information as well.
 | |
| 			 */
 | |
| 			if (dbdat.data && !IS_NEG(dbdat.data))
 | |
| 				stats.app_neg++;
 | |
| 
 | |
| 			dbase->del(dbase, &dbkey, 0);
 | |
| 		} else {
 | |
| 			/* If we get this kernel does not have any information.
 | |
| 			 * If we have something tell this to kernel. */
 | |
| 			stats.app_recv++;
 | |
| 			if (dbdat.data && !IS_NEG(dbdat.data)) {
 | |
| 				stats.app_success++;
 | |
| 				respond_to_kernel(key.iface, key.addr, dbdat.data, dbdat.size);
 | |
| 				return 0;
 | |
| 			}
 | |
| 
 | |
| 			/* Sheeit! We have nothing to tell. */
 | |
| 			/* If we have recent negative entry, be silent. */
 | |
| 			if (dbdat.data && NEG_VALID(dbdat.data)) {
 | |
| 				if (NEG_CNT(dbdat.data) >= active_probing) {
 | |
| 					stats.app_suppressed++;
 | |
| 					return 0;
 | |
| 				}
 | |
| 				do_acct = 1;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (active_probing &&
 | |
| 		    queue_active_probe(ndm->ndm_ifindex, key.addr) == 0 &&
 | |
| 		    do_acct) {
 | |
| 			NEG_CNT(dbdat.data)++;
 | |
| 			dbase->put(dbase, &dbkey, &dbdat, 0);
 | |
| 		}
 | |
| 	} else if (n->nlmsg_type == RTM_NEWNEIGH) {
 | |
| 		if (n->nlmsg_flags&NLM_F_REQUEST)
 | |
| 			return 0;
 | |
| 
 | |
| 		if (ndm->ndm_state&NUD_FAILED) {
 | |
| 			/* Kernel was not able to resolve. Host is dead.
 | |
| 			 * Create negative entry if it is not present
 | |
| 			 * or renew it if it is too old. */
 | |
| 			if (!dbdat.data ||
 | |
| 			    !IS_NEG(dbdat.data) ||
 | |
| 			    !NEG_VALID(dbdat.data)) {
 | |
| 				__u8 ndata[6];
 | |
| 
 | |
| 				stats.kern_neg++;
 | |
| 				prepare_neg_entry(ndata, time(NULL));
 | |
| 				dbdat.data = ndata;
 | |
| 				dbdat.size = sizeof(ndata);
 | |
| 				dbase->put(dbase, &dbkey, &dbdat, 0);
 | |
| 			}
 | |
| 		} else if (tb[NDA_LLADDR]) {
 | |
| 			if (dbdat.data && !IS_NEG(dbdat.data)) {
 | |
| 				if (memcmp(RTA_DATA(tb[NDA_LLADDR]), dbdat.data, dbdat.size) == 0)
 | |
| 					return 0;
 | |
| 				stats.kern_change++;
 | |
| 			} else {
 | |
| 				stats.kern_new++;
 | |
| 			}
 | |
| 			dbdat.data = RTA_DATA(tb[NDA_LLADDR]);
 | |
| 			dbdat.size = RTA_PAYLOAD(tb[NDA_LLADDR]);
 | |
| 			dbase->put(dbase, &dbkey, &dbdat, 0);
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void load_initial_table(void)
 | |
| {
 | |
| 	if (rtnl_wilddump_request(&rth, AF_INET, RTM_GETNEIGH) < 0) {
 | |
| 		perror("dump request failed");
 | |
| 		exit(1);
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| static void get_kern_msg(void)
 | |
| {
 | |
| 	int status;
 | |
| 	struct nlmsghdr *h;
 | |
| 	struct sockaddr_nl nladdr = {};
 | |
| 	struct iovec iov;
 | |
| 	char   buf[8192];
 | |
| 	struct msghdr msg = {
 | |
| 		(void *)&nladdr, sizeof(nladdr),
 | |
| 		&iov,	1,
 | |
| 		NULL,	0,
 | |
| 		0
 | |
| 	};
 | |
| 
 | |
| 	iov.iov_base = buf;
 | |
| 	iov.iov_len = sizeof(buf);
 | |
| 
 | |
| 	status = recvmsg(rth.fd, &msg, MSG_DONTWAIT);
 | |
| 
 | |
| 	if (status <= 0)
 | |
| 		return;
 | |
| 
 | |
| 	if (msg.msg_namelen != sizeof(nladdr))
 | |
| 		return;
 | |
| 
 | |
| 	if (nladdr.nl_pid)
 | |
| 		return;
 | |
| 
 | |
| 	for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
 | |
| 		int len = h->nlmsg_len;
 | |
| 		int l = len - sizeof(*h);
 | |
| 
 | |
| 		if (l < 0 || len > status)
 | |
| 			return;
 | |
| 
 | |
| 		if (do_one_request(h) < 0)
 | |
| 			return;
 | |
| 
 | |
| 		status -= NLMSG_ALIGN(len);
 | |
| 		h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /* Receive gratuitous ARP messages and store them, that's all. */
 | |
| static void get_arp_pkt(void)
 | |
| {
 | |
| 	unsigned char buf[1024];
 | |
| 	struct sockaddr_ll sll;
 | |
| 	socklen_t sll_len = sizeof(sll);
 | |
| 	struct arphdr *a = (struct arphdr *)buf;
 | |
| 	struct dbkey key;
 | |
| 	DBT dbkey, dbdat;
 | |
| 	int n;
 | |
| 
 | |
| 	n = recvfrom(pset[0].fd, buf, sizeof(buf), MSG_DONTWAIT,
 | |
| 		     (struct sockaddr *)&sll, &sll_len);
 | |
| 	if (n < 0) {
 | |
| 		if (errno != EINTR && errno != EAGAIN)
 | |
| 			syslog(LOG_ERR, "recvfrom: %m");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (ifnum && !handle_if(sll.sll_ifindex))
 | |
| 		return;
 | |
| 
 | |
| 	/* Sanity checks */
 | |
| 
 | |
| 	if (n < sizeof(*a) ||
 | |
| 	    (a->ar_op != htons(ARPOP_REQUEST) &&
 | |
| 	     a->ar_op != htons(ARPOP_REPLY)) ||
 | |
| 	    a->ar_pln != 4 ||
 | |
| 	    a->ar_pro != htons(ETH_P_IP) ||
 | |
| 	    a->ar_hln != sll.sll_halen ||
 | |
| 	    sizeof(*a) + 2*4 + 2*a->ar_hln > n)
 | |
| 		return;
 | |
| 
 | |
| 	key.iface = sll.sll_ifindex;
 | |
| 	memcpy(&key.addr, (char *)(a+1) + a->ar_hln, 4);
 | |
| 
 | |
| 	/* DAD message, ignore. */
 | |
| 	if (key.addr == 0)
 | |
| 		return;
 | |
| 
 | |
| 	dbkey.data = &key;
 | |
| 	dbkey.size = sizeof(key);
 | |
| 
 | |
| 	if (dbase->get(dbase, &dbkey, &dbdat, 0) == 0 && !IS_NEG(dbdat.data)) {
 | |
| 		if (memcmp(dbdat.data, a+1, dbdat.size) == 0)
 | |
| 			return;
 | |
| 		stats.arp_change++;
 | |
| 	} else {
 | |
| 		stats.arp_new++;
 | |
| 	}
 | |
| 
 | |
| 	dbdat.data = a+1;
 | |
| 	dbdat.size = a->ar_hln;
 | |
| 	dbase->put(dbase, &dbkey, &dbdat, 0);
 | |
| }
 | |
| 
 | |
| static void catch_signal(int sig, void (*handler)(int))
 | |
| {
 | |
| 	struct sigaction sa = { .sa_handler = handler };
 | |
| 
 | |
| #ifdef SA_INTERRUPT
 | |
| 	sa.sa_flags = SA_INTERRUPT;
 | |
| #endif
 | |
| 	sigaction(sig, &sa, NULL);
 | |
| }
 | |
| 
 | |
| #include <setjmp.h>
 | |
| sigjmp_buf env;
 | |
| volatile int in_poll;
 | |
| 
 | |
| static void sig_exit(int signo)
 | |
| {
 | |
| 	do_exit = 1;
 | |
| 	if (in_poll)
 | |
| 		siglongjmp(env, 1);
 | |
| }
 | |
| 
 | |
| static void sig_sync(int signo)
 | |
| {
 | |
| 	do_sync = 1;
 | |
| 	if (in_poll)
 | |
| 		siglongjmp(env, 1);
 | |
| }
 | |
| 
 | |
| static void sig_stats(int signo)
 | |
| {
 | |
| 	do_sync = 1;
 | |
| 	do_stats = 1;
 | |
| 	if (in_poll)
 | |
| 		siglongjmp(env, 1);
 | |
| }
 | |
| 
 | |
| static void send_stats(void)
 | |
| {
 | |
| 	syslog(LOG_INFO, "arp_rcv: n%lu c%lu app_rcv: tot %lu hits %lu bad %lu neg %lu sup %lu",
 | |
| 	       stats.arp_new, stats.arp_change,
 | |
| 
 | |
| 	       stats.app_recv, stats.app_success,
 | |
| 	       stats.app_bad, stats.app_neg, stats.app_suppressed
 | |
| 	       );
 | |
| 	syslog(LOG_INFO, "kern: n%lu c%lu neg %lu arp_send: %lu rlim %lu",
 | |
| 	       stats.kern_new, stats.kern_change, stats.kern_neg,
 | |
| 
 | |
| 	       stats.probes_sent, stats.probes_suppressed
 | |
| 	       );
 | |
| 	do_stats = 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
| 	int opt;
 | |
| 	int do_list = 0;
 | |
| 	char *do_load = NULL;
 | |
| 
 | |
| 	while ((opt = getopt(argc, argv, "h?b:lf:a:n:p:kR:B:")) != EOF) {
 | |
| 		switch (opt) {
 | |
| 		case 'b':
 | |
| 			dbname = optarg;
 | |
| 			break;
 | |
| 		case 'f':
 | |
| 			if (do_load) {
 | |
| 				fprintf(stderr, "Duplicate option -f\n");
 | |
| 				usage();
 | |
| 			}
 | |
| 			do_load = optarg;
 | |
| 			break;
 | |
| 		case 'l':
 | |
| 			do_list = 1;
 | |
| 			break;
 | |
| 		case 'a':
 | |
| 			active_probing = atoi(optarg);
 | |
| 			break;
 | |
| 		case 'n':
 | |
| 			negative_timeout = atoi(optarg);
 | |
| 			break;
 | |
| 		case 'k':
 | |
| 			no_kernel_broadcasts = 1;
 | |
| 			break;
 | |
| 		case 'p':
 | |
| 			if ((poll_timeout = 1000 * strtod(optarg, NULL)) < 100) {
 | |
| 				fprintf(stderr, "Invalid poll timeout\n");
 | |
| 				exit(-1);
 | |
| 			}
 | |
| 			break;
 | |
| 		case 'R':
 | |
| 			if ((broadcast_rate = atoi(optarg)) <= 0 ||
 | |
| 			    (broadcast_rate = 1000/broadcast_rate) <= 0) {
 | |
| 				fprintf(stderr, "Invalid ARP rate\n");
 | |
| 				exit(-1);
 | |
| 			}
 | |
| 			break;
 | |
| 		case 'B':
 | |
| 			if ((broadcast_burst = atoi(optarg)) <= 0 ||
 | |
| 			    (broadcast_burst = 1000*broadcast_burst) <= 0) {
 | |
| 				fprintf(stderr, "Invalid ARP burst\n");
 | |
| 				exit(-1);
 | |
| 			}
 | |
| 			break;
 | |
| 		case 'h':
 | |
| 		case '?':
 | |
| 		default:
 | |
| 			usage();
 | |
| 		}
 | |
| 	}
 | |
| 	argc -= optind;
 | |
| 	argv += optind;
 | |
| 
 | |
| 	if (argc > 0) {
 | |
| 		ifnum = argc;
 | |
| 		ifnames = argv;
 | |
| 		ifvec = malloc(argc*sizeof(int));
 | |
| 		if (!ifvec) {
 | |
| 			perror("malloc");
 | |
| 			exit(-1);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if ((udp_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
 | |
| 		perror("socket");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| 
 | |
| 	if (ifnum) {
 | |
| 		int i;
 | |
| 		struct ifreq ifr = {};
 | |
| 
 | |
| 		for (i = 0; i < ifnum; i++) {
 | |
| 			if (get_ifname(ifr.ifr_name, ifnames[i]))
 | |
| 				invarg("not a valid ifname", ifnames[i]);
 | |
| 			if (ioctl(udp_sock, SIOCGIFINDEX, &ifr)) {
 | |
| 				perror("ioctl(SIOCGIFINDEX)");
 | |
| 				exit(-1);
 | |
| 			}
 | |
| 			ifvec[i] = ifr.ifr_ifindex;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	dbase = dbopen(dbname, O_CREAT|O_RDWR, 0644, DB_HASH, NULL);
 | |
| 	if (dbase == NULL) {
 | |
| 		perror("db_open");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| 
 | |
| 	if (do_load) {
 | |
| 		char buf[128];
 | |
| 		FILE *fp;
 | |
| 		struct dbkey k;
 | |
| 		DBT dbkey, dbdat;
 | |
| 
 | |
| 		dbkey.data = &k;
 | |
| 		dbkey.size = sizeof(k);
 | |
| 
 | |
| 		if (strcmp(do_load, "-") == 0 || strcmp(do_load, "--") == 0) {
 | |
| 			fp = stdin;
 | |
| 		} else if ((fp = fopen(do_load, "r")) == NULL) {
 | |
| 			perror("fopen");
 | |
| 			goto do_abort;
 | |
| 		}
 | |
| 
 | |
| 		buf[sizeof(buf)-1] = 0;
 | |
| 		while (fgets(buf, sizeof(buf), fp)) {
 | |
| 			__u8 b1[6];
 | |
| 			char ipbuf[128];
 | |
| 			char macbuf[128];
 | |
| 
 | |
| 			if (buf[0] == '#')
 | |
| 				continue;
 | |
| 
 | |
| 			if (sscanf(buf, "%u%s%s", &k.iface, ipbuf, macbuf) != 3) {
 | |
| 				fprintf(stderr, "Wrong format of input file \"%s\"\n", do_load);
 | |
| 				goto do_abort;
 | |
| 			}
 | |
| 			if (strncmp(macbuf, "FAILED:", 7) == 0)
 | |
| 				continue;
 | |
| 			if (!inet_aton(ipbuf, (struct in_addr *)&k.addr)) {
 | |
| 				fprintf(stderr, "Invalid IP address: \"%s\"\n", ipbuf);
 | |
| 				goto do_abort;
 | |
| 			}
 | |
| 
 | |
| 			if (ll_addr_a2n((char *) b1, 6, macbuf) != 6)
 | |
| 				goto do_abort;
 | |
| 			dbdat.size = 6;
 | |
| 
 | |
| 			if (dbase->put(dbase, &dbkey, &dbdat, 0)) {
 | |
| 				perror("hash->put");
 | |
| 				goto do_abort;
 | |
| 			}
 | |
| 		}
 | |
| 		dbase->sync(dbase, 0);
 | |
| 		if (fp != stdin)
 | |
| 			fclose(fp);
 | |
| 	}
 | |
| 
 | |
| 	if (do_list) {
 | |
| 		DBT dbkey, dbdat;
 | |
| 
 | |
| 		printf("%-8s %-15s %s\n", "#Ifindex", "IP", "MAC");
 | |
| 		while (dbase->seq(dbase, &dbkey, &dbdat, R_NEXT) == 0) {
 | |
| 			struct dbkey *key = dbkey.data;
 | |
| 
 | |
| 			if (handle_if(key->iface)) {
 | |
| 				if (!IS_NEG(dbdat.data)) {
 | |
| 					char b1[18];
 | |
| 
 | |
| 					printf("%-8d %-15s %s\n",
 | |
| 					       key->iface,
 | |
| 					       inet_ntoa(*(struct in_addr *)&key->addr),
 | |
| 					       ll_addr_n2a(dbdat.data, 6, ARPHRD_ETHER, b1, 18));
 | |
| 				} else {
 | |
| 					printf("%-8d %-15s FAILED: %dsec ago\n",
 | |
| 					       key->iface,
 | |
| 					       inet_ntoa(*(struct in_addr *)&key->addr),
 | |
| 					       NEG_AGE(dbdat.data));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (do_load || do_list)
 | |
| 		goto out;
 | |
| 
 | |
| 	pset[0].fd = socket(PF_PACKET, SOCK_DGRAM, 0);
 | |
| 	if (pset[0].fd < 0) {
 | |
| 		perror("socket");
 | |
| 		exit(-1);
 | |
| 	}
 | |
| 
 | |
| 	if (1) {
 | |
| 		struct sockaddr_ll sll = {
 | |
| 			.sll_family = AF_PACKET,
 | |
| 			.sll_protocol = htons(ETH_P_ARP),
 | |
| 			.sll_ifindex = (ifnum == 1 ? ifvec[0] : 0),
 | |
| 		};
 | |
| 
 | |
| 		if (bind(pset[0].fd, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
 | |
| 			perror("bind");
 | |
| 			goto do_abort;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (rtnl_open(&rth, RTMGRP_NEIGH) < 0) {
 | |
| 		perror("rtnl_open");
 | |
| 		goto do_abort;
 | |
| 	}
 | |
| 	pset[1].fd = rth.fd;
 | |
| 
 | |
| 	load_initial_table();
 | |
| 
 | |
| 	if (daemon(0, 0)) {
 | |
| 		perror("arpd: daemon");
 | |
| 		goto do_abort;
 | |
| 	}
 | |
| 
 | |
| 	openlog("arpd", LOG_PID | LOG_CONS, LOG_DAEMON);
 | |
| 	catch_signal(SIGINT, sig_exit);
 | |
| 	catch_signal(SIGTERM, sig_exit);
 | |
| 	catch_signal(SIGHUP, sig_sync);
 | |
| 	catch_signal(SIGUSR1, sig_stats);
 | |
| 
 | |
| #define EVENTS (POLLIN|POLLPRI|POLLERR|POLLHUP)
 | |
| 	pset[0].events = EVENTS;
 | |
| 	pset[0].revents = 0;
 | |
| 	pset[1].events = EVENTS;
 | |
| 	pset[1].revents = 0;
 | |
| 
 | |
| 	sigsetjmp(env, 1);
 | |
| 
 | |
| 	for (;;) {
 | |
| 		in_poll = 1;
 | |
| 
 | |
| 		if (do_exit)
 | |
| 			break;
 | |
| 		if (do_sync) {
 | |
| 			in_poll = 0;
 | |
| 			dbase->sync(dbase, 0);
 | |
| 			do_sync = 0;
 | |
| 			in_poll = 1;
 | |
| 		}
 | |
| 		if (do_stats)
 | |
| 			send_stats();
 | |
| 		if (poll(pset, 2, poll_timeout) > 0) {
 | |
| 			in_poll = 0;
 | |
| 			if (pset[0].revents&EVENTS)
 | |
| 				get_arp_pkt();
 | |
| 			if (pset[1].revents&EVENTS)
 | |
| 				get_kern_msg();
 | |
| 		} else {
 | |
| 			do_sync = 1;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	undo_sysctl_adjustments();
 | |
| out:
 | |
| 	dbase->close(dbase);
 | |
| 	exit(0);
 | |
| 
 | |
| do_abort:
 | |
| 	dbase->close(dbase);
 | |
| 	exit(-1);
 | |
| }
 |