Commit 11867dc2 authored by Simon Kelley's avatar Simon Kelley

Cache access to the kernel's ARP table.

parent d3a8b39c
...@@ -74,7 +74,7 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ ...@@ -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 \ 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 \ 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 \ 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 \ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
dns-protocol.h radv-protocol.h ip6addr.h dns-protocol.h radv-protocol.h ip6addr.h
......
...@@ -10,7 +10,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ ...@@ -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 \ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
radv.c slaac.c auth.c ipset.c domain.c \ radv.c slaac.c auth.c ipset.c domain.c \
dnssec.c dnssec-openssl.c blockdata.c tables.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 LOCAL_MODULE := dnsmasq
......
/* 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 <http://www.gnu.org/licenses/>.
*/
#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;
}
...@@ -27,17 +27,10 @@ struct iface_param { ...@@ -27,17 +27,10 @@ struct iface_param {
int ind, addr_match; 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, static int complete_context6(struct in6_addr *local, int prefix,
int scope, int if_index, int flags, int scope, int if_index, int flags,
unsigned int preferred, unsigned int valid, void *vparam); 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); static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm);
void dhcp6_init(void) void dhcp6_init(void)
...@@ -264,9 +257,8 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsi ...@@ -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. */ find the sender. Repeat a few times in case of packet loss. */
struct neigh_packet neigh; struct neigh_packet neigh;
struct sockaddr_in6 addr; union mysockaddr addr;
struct mac_param mac_param; int i, maclen;
int i;
neigh.type = ND_NEIGHBOR_SOLICIT; neigh.type = ND_NEIGHBOR_SOLICIT;
neigh.code = 0; neigh.code = 0;
...@@ -277,55 +269,31 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsi ...@@ -277,55 +269,31 @@ void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsi
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
#ifdef HAVE_SOCKADDR_SA_LEN #ifdef HAVE_SOCKADDR_SA_LEN
addr.sin6_len = sizeof(struct sockaddr_in6); addr.in6.sin6_len = sizeof(struct sockaddr_in6);
#endif #endif
addr.sin6_family = AF_INET6; addr.in6.sin6_family = AF_INET6;
addr.sin6_port = htons(IPPROTO_ICMPV6); addr.in6.sin6_port = htons(IPPROTO_ICMPV6);
addr.sin6_addr = *client; addr.in6.sin6_addr = *client;
addr.sin6_scope_id = iface; addr.in6.sin6_scope_id = iface;
mac_param.target = client;
mac_param.maclen = 0;
mac_param.mac = mac;
for (i = 0; i < 5; i++) for (i = 0; i < 5; i++)
{ {
struct timespec ts; struct timespec ts;
iface_enumerate(AF_UNSPEC, &mac_param, find_mac); if ((maclen = find_mac(&addr, mac, 0)) != 0)
if (mac_param.maclen != 0)
break; 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_sec = 0;
ts.tv_nsec = 100000000; /* 100ms */ ts.tv_nsec = 100000000; /* 100ms */
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
} }
*maclenp = mac_param.maclen; *maclenp = maclen;
*mactypep = ARPHRD_ETHER; *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, static int complete_context6(struct in6_addr *local, int prefix,
int scope, int if_index, int flags, unsigned int preferred, int scope, int if_index, int flags, unsigned int preferred,
unsigned int valid, void *vparam) unsigned int valid, void *vparam)
......
...@@ -1516,3 +1516,7 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock ...@@ -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_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); 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); 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);
...@@ -213,42 +213,15 @@ size_t add_do_bit(struct dns_header *header, size_t plen, char *limit) ...@@ -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); 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) size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3)
{ {
struct macparm parm; int maclen;
unsigned char mac[DHCP_CHADDR_MAX];
parm.header = header;
parm.limit = (unsigned char *)limit;
parm.plen = plen;
parm.l3 = l3;
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 { struct subnet_opt {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment