From: Simon Kelley Date: Wed, 23 Dec 2015 16:15:58 +0000 (+0000) Subject: Cache access to the kernel's ARP table. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=11867dc28c7bd7c8a509ee7c8c7438cd2bcc1770;p=people%2Fms%2Fdnsmasq.git Cache access to the kernel's ARP table. --- diff --git a/Makefile b/Makefile index dfb0347..41e368f 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ - poll.o rrfilter.o edns0.o + poll.o rrfilter.o edns0.o arp.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h ip6addr.h diff --git a/bld/Android.mk b/bld/Android.mk index 87966d2..eafef35 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -10,7 +10,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \ radv.c slaac.c auth.c ipset.c domain.c \ dnssec.c dnssec-openssl.c blockdata.c tables.c \ - loop.c inotify.c poll.c rrfilter.c edns0.c + loop.c inotify.c poll.c rrfilter.c edns0.c arp.c LOCAL_MODULE := dnsmasq diff --git a/src/arp.c b/src/arp.c new file mode 100644 index 0000000..b624dac --- /dev/null +++ b/src/arp.c @@ -0,0 +1,201 @@ +/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley + + 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; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "dnsmasq.h" + +#define ARP_FREE 0 +#define ARP_FOUND 1 +#define ARP_NEW 2 +#define ARP_EMPTY 3 + +struct arp_record { + short hwlen, status; + int family; + unsigned char hwaddr[DHCP_CHADDR_MAX]; + struct all_addr addr; + struct arp_record *next; +}; + +static struct arp_record *arps = NULL, *old = NULL; + +static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) +{ + int match = 0; + struct arp_record *arp; + + if (maclen > DHCP_CHADDR_MAX) + return 1; + + /* Look for existing entry */ + for (arp = arps; arp; arp = arp->next) + { + if (family != arp->family || arp->status == ARP_NEW) + continue; + + if (family == AF_INET) + { + if (arp->addr.addr.addr4.s_addr != ((struct in_addr *)addrp)->s_addr) + continue; + } +#ifdef HAVE_IPV6 + else + { + if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr.addr6, (struct in6_addr *)addrp)) + continue; + } +#endif + + if (arp->status != ARP_EMPTY && arp->hwlen == maclen && memcmp(arp->hwaddr, mac, maclen) == 0) + arp->status = ARP_FOUND; + else + { + /* existing address, MAC changed or arrived new. */ + arp->status = ARP_NEW; + arp->hwlen = maclen; + arp->family = family; + memcpy(arp->hwaddr, mac, maclen); + } + + break; + } + + if (!arp) + { + /* New entry */ + if (old) + { + arp = old; + old = old->next; + } + else if (!(arp = whine_malloc(sizeof(struct arp_record)))) + return 1; + + arp->next = arps; + arps = arp; + arp->status = ARP_NEW; + arp->hwlen = maclen; + arp->family = family; + memcpy(arp->hwaddr, mac, maclen); + if (family == AF_INET) + arp->addr.addr.addr4.s_addr = ((struct in_addr *)addrp)->s_addr; +#ifdef HAVE_IPV6 + else + memcpy(&arp->addr.addr.addr6, addrp, IN6ADDRSZ); +#endif + } + + return 1; +} + +/* If in lazy mode, we cache absence of ARP entries. */ +int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy) +{ + struct arp_record *arp, **up; + int updated = 0; + + again: + + for (arp = arps; arp; arp = arp->next) + { + if (addr->sa.sa_family == arp->family) + { + if (arp->addr.addr.addr4.s_addr != addr->in.sin_addr.s_addr) + continue; + } +#ifdef HAVE_IPV6 + else + { + if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr.addr6, &addr->in6.sin6_addr)) + continue; + } +#endif + + /* Only accept poitive entries unless in lazy mode. */ + if (arp->status != ARP_EMPTY || lazy || updated) + { + if (mac && arp->hwlen != 0) + memcpy(mac, arp->hwaddr, arp->hwlen); + return arp->hwlen; + } + } + + /* Not found, try the kernel */ + if (!updated) + { + updated = 1; + + /* Mark all non-negative entries */ + for (arp = arps, up = &arps; arp; arp = arp->next) + if (arp->status != ARP_EMPTY) + arp->status = ARP_FREE; + + iface_enumerate(AF_UNSPEC, NULL, filter_mac); + + /* Remove all unconfirmed entries to old list, announce new ones. */ + for (arp = arps, up = &arps; arp; arp = arp->next) + if (arp->status == ARP_FREE) + { + *up = arp->next; + arp->next = old; + old = arp; + } + else + { + up = &arp->next; + if (arp->status == ARP_NEW) + { + char a[ADDRSTRLEN], m[ADDRSTRLEN]; + union mysockaddr pa; + pa.sa.sa_family = arp->family; + pa.in.sin_addr.s_addr = arp->addr.addr.addr4.s_addr; + prettyprint_addr(&pa, a); + print_mac(m, arp->hwaddr, arp->hwlen); + my_syslog(LOG_INFO, _("new arp: %s %s"), a, m); + } + } + + goto again; + } + + /* record failure, so we don't consult the kernel each time + we're asked for this address */ + if (old) + { + arp = old; + old = old->next; + } + else + arp = whine_malloc(sizeof(struct arp_record)); + + if (arp) + { + arp->next = arps; + arps = arp; + arp->status = ARP_EMPTY; + arp->family = addr->sa.sa_family; + + if (addr->sa.sa_family == AF_INET) + arp->addr.addr.addr4.s_addr = addr->in.sin_addr.s_addr; +#ifdef HAVE_IPV6 + else + memcpy(&arp->addr.addr.addr6, &addr->in6.sin6_addr, IN6ADDRSZ); +#endif + } + + return 0; +} + + diff --git a/src/dhcp6.c b/src/dhcp6.c index 8286ff4..7b1a7c7 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -27,17 +27,10 @@ struct iface_param { int ind, addr_match; }; -struct mac_param { - struct in6_addr *target; - unsigned char *mac; - unsigned int maclen; -}; - static int complete_context6(struct in6_addr *local, int prefix, int scope, int if_index, int flags, unsigned int preferred, unsigned int valid, void *vparam); -static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv); static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm); void dhcp6_init(void) @@ -264,9 +257,8 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsi find the sender. Repeat a few times in case of packet loss. */ struct neigh_packet neigh; - struct sockaddr_in6 addr; - struct mac_param mac_param; - int i; + union mysockaddr addr; + int i, maclen; neigh.type = ND_NEIGHBOR_SOLICIT; neigh.code = 0; @@ -277,55 +269,31 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsi memset(&addr, 0, sizeof(addr)); #ifdef HAVE_SOCKADDR_SA_LEN - addr.sin6_len = sizeof(struct sockaddr_in6); + addr.in6.sin6_len = sizeof(struct sockaddr_in6); #endif - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(IPPROTO_ICMPV6); - addr.sin6_addr = *client; - addr.sin6_scope_id = iface; - - mac_param.target = client; - mac_param.maclen = 0; - mac_param.mac = mac; + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_port = htons(IPPROTO_ICMPV6); + addr.in6.sin6_addr = *client; + addr.in6.sin6_scope_id = iface; for (i = 0; i < 5; i++) { struct timespec ts; - iface_enumerate(AF_UNSPEC, &mac_param, find_mac); - - if (mac_param.maclen != 0) + if ((maclen = find_mac(&addr, mac, 0)) != 0) break; - - sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, (struct sockaddr *)&addr, sizeof(addr)); + + sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, &addr.sa, sizeof(addr)); ts.tv_sec = 0; ts.tv_nsec = 100000000; /* 100ms */ nanosleep(&ts, NULL); } - *maclenp = mac_param.maclen; + *maclenp = maclen; *mactypep = ARPHRD_ETHER; } -static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) -{ - struct mac_param *parm = parmv; - - if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(parm->target, (struct in6_addr *)addrp)) - { - if (maclen <= DHCP_CHADDR_MAX) - { - parm->maclen = maclen; - memcpy(parm->mac, mac, maclen); - } - - return 0; /* found, abort */ - } - - return 1; -} - static int complete_context6(struct in6_addr *local, int prefix, int scope, int if_index, int flags, unsigned int preferred, unsigned int valid, void *vparam) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 1c94f2a..4459594 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1516,3 +1516,7 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source); size_t add_do_bit(struct dns_header *header, size_t plen, char *limit); int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); + +/* arp.c */ +int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy); + diff --git a/src/edns0.c b/src/edns0.c index f82ba1b..9d8c0b9 100644 --- a/src/edns0.c +++ b/src/edns0.c @@ -213,42 +213,15 @@ size_t add_do_bit(struct dns_header *header, size_t plen, char *limit) return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, 0, NULL, 0, 1); } -static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) -{ - struct macparm *parm = parmv; - int match = 0; - - if (family == parm->l3->sa.sa_family) - { - if (family == AF_INET && memcmp(&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0) - match = 1; -#ifdef HAVE_IPV6 - else - if (family == AF_INET6 && memcmp(&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0) - match = 1; -#endif - } - - if (!match) - return 1; /* continue */ - - parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, PACKETSZ, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0); - - return 0; /* done */ -} - size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3) { - struct macparm parm; - - parm.header = header; - parm.limit = (unsigned char *)limit; - parm.plen = plen; - parm.l3 = l3; + int maclen; + unsigned char mac[DHCP_CHADDR_MAX]; - iface_enumerate(AF_UNSPEC, &parm, filter_mac); + if ((maclen = find_mac(l3, mac, 1)) != 0) + plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0); - return parm.plen; + return plen; } struct subnet_opt {