]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
Cache access to the kernel's ARP table.
authorSimon Kelley <simon@thekelleys.org.uk>
Wed, 23 Dec 2015 16:15:58 +0000 (16:15 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Wed, 23 Dec 2015 16:15:58 +0000 (16:15 +0000)
Makefile
bld/Android.mk
src/arp.c [new file with mode: 0644]
src/dhcp6.c
src/dnsmasq.h
src/edns0.c

index dfb0347f846c93e0c69d0758f5e18fa6d67094b0..41e368fcd7ec41f547717ff44cc10c172a65ba57 100644 (file)
--- 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
index 87966d2fc19a255d692ae201b80d21c96ff4178f..eafef3534d1e40883d70fc2c827e203af99973e3 100644 (file)
@@ -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 (file)
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 <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;
+}
+
+
index 8286ff4002171de8cbc9b92225e44249e9a4c037..7b1a7c7a46e0833a6ce55535bf8d0b70beef3213 100644 (file)
@@ -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)
index 1c94f2a152392c1b42ed15f12ac56c2e7908c7f2..445959443affb065c731bea2884bffa65fdb2e6e 100644 (file)
@@ -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);
+
index f82ba1bb5ef093d5039eaf9105c0ac87c911985b..9d8c0b994119f52563b642782a053b4d4eef78b3 100644 (file)
@@ -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 {