From 1a67eb8a9d7e0116d957f52f39f06a838a6b87d3 Mon Sep 17 00:00:00 2001
From: Phil <phil@secdev.org>
Date: Mon, 28 Jul 2008 16:13:00 +0200
Subject: [PATCH] Created a NetCache and Neighbour object to abstract link
 layer address resolution

---
 scapy/arch.py        | 52 ----------------------------
 scapy/config.py      | 64 ++++++++++++++++++++++++++++++++--
 scapy/layers/inet.py |  5 ++-
 scapy/layers/l2.py   | 82 +++++++++++++++++++++++++++++++++++---------
 scapy/route.py       |  3 +-
 scapy/sendrecv.py    |  6 ++--
 6 files changed, 136 insertions(+), 76 deletions(-)

diff --git a/scapy/arch.py b/scapy/arch.py
index f37385c6..8730534e 100644
--- a/scapy/arch.py
+++ b/scapy/arch.py
@@ -371,55 +371,3 @@ def get_if_hwaddr(iff):
 
 
 
-#####################
-## ARP cache stuff ##
-#####################
-
-ARPTIMEOUT=120
-
-
-if 0 and DNET: ## XXX Can't use this because it does not resolve IPs not in cache
-    dnet_arp_object = dnet.arp()
-    def getmacbyip(ip, chainCC=0):
-        tmp = map(ord, inet_aton(ip))
-        if (tmp[0] & 0xf0) == 0xe0: # mcast @
-            return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1]&0x7f,tmp[2],tmp[3])
-        iff,a,gw = config.conf.route.route(ip)
-        if iff == "lo":
-            return "ff:ff:ff:ff:ff:ff"
-        if gw != "0.0.0.0":
-            ip = gw
-        res = dnet_arp_object.get(dnet.addr(ip))
-        if res is None:
-            return None
-        else:
-            return res.ntoa()
-else:
-    def getmacbyip(ip, chainCC=0):
-        tmp = map(ord, inet_aton(ip))
-        if (tmp[0] & 0xf0) == 0xe0: # mcast @
-            return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1]&0x7f,tmp[2],tmp[3])
-        iff,a,gw = config.conf.route.route(ip)
-        if ( (iff == "lo") or (ip == config.conf.route.get_if_bcast(iff)) ):
-            return "ff:ff:ff:ff:ff:ff"
-        if gw != "0.0.0.0":
-            ip = gw
-    
-        if config.conf.arp_cache.has_key(ip):
-            mac, timeout = config.conf.arp_cache[ip]
-            if not timeout or (time.time()-timeout < ARPTIMEOUT):
-                return mac
-
-        res = srp1(Ether(dst=ETHER_BROADCAST)/ARP(op="who-has", pdst=ip),
-                   type=ETH_P_ARP,
-                   iface = iff,
-                   timeout=2,
-                   verbose=0,
-                   chainCC=chainCC,
-                   nofilter=1)
-        if res is not None:
-            mac = res.payload.hwsrc
-            config.conf.arp_cache[ip] = (mac,time.time())
-            return mac
-        return None
-    
diff --git a/scapy/config.py b/scapy/config.py
index 73982fa1..e3e17fc5 100644
--- a/scapy/config.py
+++ b/scapy/config.py
@@ -1,4 +1,4 @@
-import os
+import os,time
 from data import *
 import base_classes
 import arch,themes
@@ -104,6 +104,65 @@ class LayersList(list):
         return "\n".join(s)
     def register(self, layer):
         self.append(layer)
+
+
+class CacheInstance(dict):
+    def __init__(self, name="noname", timeout=None):
+        self.timeout = timeout
+        self.name = name
+        self._timetable = {}
+    def __getitem__(self, item):
+        val = dict.__getitem__(self,item)
+        if self.timeout is not None:
+            t = self._timetable[item]
+            if time.time()-t > self.timeout:
+                raise KeyError(item)
+        return val
+    def get(self, item, default=None):
+        try:
+            return self[item]
+        except KeyError:
+            return default
+    def __setitem__(self, item, v):
+        self._timetable[item] = time.time()
+        dict.__setitem__(self, item,v)
+    def __repr__(self):
+        if self.timeout is None:
+            n = len(self)
+        else:
+            n = 0
+            t0 = time.time()
+            for t,v in self.itervalues():
+                if t0-t <= self.timeout:
+                    n += 1
+        return "%s: %i valid items. Timeout=%rs" % (self.name, n, self.timeout)
+            
+
+
+class NetCache:
+    def __init__(self):
+        self._caches_list = []
+
+
+    def add_cache(self, cache):
+        self._caches_list.append(cache)
+        setattr(self,cache.name,cache)
+    def new_cache(self, name, timeout=None):
+        c = CacheInstance(name=name, timeout=timeout)
+        self.add_cache(c)
+    def __delattr__(self, attr):
+        raise AttributeError("Cannot delete attributes")
+    def update(self, other):
+        for co in other._caches_list:
+            if hasattr(self, co.name):
+                getattr(self,co.name).update(co)
+            else:
+                self.add_cache(co.copy())
+    def flush(self):
+        for c in self._caches_list:
+            c.flush()
+    def __repr__(self):
+        return "\n".join(repr(c) for c in self._caches_list)
         
 
 
@@ -173,7 +232,8 @@ extensions_paths: path or list of paths where extensions are to be looked for
     manufdb = load_manuf("/usr/share/wireshark/wireshark/manuf")
     stats_classic_protocols = []
     stats_dot11_protocols = []
-    arp_cache = {}
+    netcache = NetCache()
+    
 
 conf=Conf()
 
diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py
index 0acf9096..2a1ddaa7 100644
--- a/scapy/layers/inet.py
+++ b/scapy/layers/inet.py
@@ -6,7 +6,7 @@ from scapy.fields import *
 from scapy.packet import *
 from scapy.volatile import *
 from scapy.config import conf
-from scapy.sendrecv import sr,sr1
+from scapy.sendrecv import sr,sr1,srp1
 from scapy.plist import PacketList,SndRcvList
 
 import scapy.as_resolvers
@@ -504,6 +504,9 @@ conf.l3types.register(ETH_P_IP, IP)
 conf.l3types.register_num2layer(ETH_P_ALL, IP)
 
 
+conf.neighbor.register_l3(Ether, IP, lambda l2,l3: getmacbyip(l3.dst))
+
+
 ###################
 ## Fragmentation ##
 ###################
diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py
index 6fd8ab21..2f7bb309 100644
--- a/scapy/layers/l2.py
+++ b/scapy/layers/l2.py
@@ -6,6 +6,61 @@ from scapy.plist import SndRcvList
 from scapy.fields import *
 from scapy.sendrecv import srp,srp1
 
+
+
+
+#################
+## Tools       ##
+#################
+
+
+class Neighbor:
+    def __init__(self):
+        self.resolvers = {}
+
+    def register_l3(self, l2, l3, resolve_method):
+        self.resolvers[l2,l3]=resolve_method
+
+    def resolve(self, l2inst, l3inst):
+        return self.resolvers[l2inst.__class__,l3inst.__class__](l2inst,l3inst)
+
+    def __repr__(self):
+        return "\n".join("%-15s -> %-15s" % (l2.__name__, l3.__name__) for l2,l3 in self.resolvers)
+
+conf.neighbor = Neighbor()
+
+conf.netcache.new_cache("arp_cache", 120) # cache entries expire after 120s
+
+
+def getmacbyip(ip, chainCC=0):
+    tmp = map(ord, inet_aton(ip))
+    if (tmp[0] & 0xf0) == 0xe0: # mcast @
+        return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1]&0x7f,tmp[2],tmp[3])
+    iff,a,gw = config.conf.route.route(ip)
+    if ( (iff == "lo") or (ip == config.conf.route.get_if_bcast(iff)) ):
+        return "ff:ff:ff:ff:ff:ff"
+    if gw != "0.0.0.0":
+        ip = gw
+
+    mac = config.conf.netcache.arp_cache.get(ip)
+    if mac:
+        return mac
+
+    res = srp1(Ether(dst=ETHER_BROADCAST)/ARP(op="who-has", pdst=ip),
+               type=ETH_P_ARP,
+               iface = iff,
+               timeout=2,
+               verbose=0,
+               chainCC=chainCC,
+               nofilter=1)
+    if res is not None:
+        mac = res.payload.hwsrc
+        config.conf.netcache.arp_cache[ip] = mac
+        return mac
+    return None
+
+
+
 ### Fields
 
 class DestMACField(MACField):
@@ -13,23 +68,10 @@ class DestMACField(MACField):
         MACField.__init__(self, name, None)
     def i2h(self, pkt, x):
         if x is None:
-            dstip = None
-            if isinstance(pkt.payload, IPv6):
-                dstip = pkt.payload.dst            
-            elif isinstance(pkt.payload, IP):
-                dstip = pkt.payload.dst
-            elif isinstance(pkt.payload, ARP):
-                dstip = pkt.payload.pdst
-            if isinstance(dstip, Gen):
-                dstip = dstip.__iter__().next()
-            if dstip is not None:
-                if isinstance(pkt.payload, IPv6):
-                    x = getmacbyip6(dstip, chainCC=1)
-                else:    
-                    x = getmacbyip(dstip, chainCC=1)
+            x = conf.neighbor.resolve(pkt,pkt.payload)
             if x is None:
                 x = "ff:ff:ff:ff:ff:ff"
-                warning("Mac address to reach %s not found\n"%dstip)
+                warning("Mac address to reach destination not found")
         return MACField.i2h(self, pkt, x)
     def i2m(self, pkt, x):
         return MACField.i2m(self, pkt, self.i2h(pkt, x))
@@ -48,6 +90,7 @@ class SourceMACField(MACField):
                 dstip = pkt.payload.pdst
             if isinstance(dstip, Gen):
                 dstip = dstip.__iter__().next()
+
             if dstip is not None:
                 if isinstance(pkt.payload, IPv6):
                     iff,a,nh = conf.route6.route(dstip)
@@ -296,7 +339,7 @@ class ARP(Packet):
         else:
             return "ARP %ARP.op% %ARP.psrc% > %ARP.pdst%"
                  
-
+conf.neighbor.register_l3(Ether, ARP, lambda l2,l3: getmacbyip(l3.pdst))
 
 class GRE(Packet):
     name = "GRE"
@@ -352,6 +395,13 @@ conf.l2types.register(144, CookedLinux)  # called LINUX_IRDA, similar to CookedL
 
 conf.l3types.register(ETH_P_ARP, ARP)
 
+
+
+
+### Technics
+
+
+
 def arpcachepoison(target, victim, interval=60):
     """Poison target's cache with (your MAC,victim's IP) couple
 arpcachepoison(target, victim, [interval=60]) -> None
diff --git a/scapy/route.py b/scapy/route.py
index 8ee2a966..1bf0b10a 100644
--- a/scapy/route.py
+++ b/scapy/route.py
@@ -85,8 +85,7 @@ class Route:
                 self.routes[i] = (the_net,the_msk,gw,iface,the_addr)
             else:
                 self.routes[i] = (net,msk,gw,iface,the_addr)
-        for i in conf.arp_cache.keys():
-            del(conf.arp_cache[i])
+        conf.netcache.flush()
         
                 
 
diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py
index 85b2a152..02bf3858 100644
--- a/scapy/sendrecv.py
+++ b/scapy/sendrecv.py
@@ -92,7 +92,7 @@ def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, m
                     try:
                         os.setpgrp() # Chance process group to avoid ctrl-C
                         sent_times = [p.sent_time for p in all_stimuli if p.sent_time]
-                        cPickle.dump( (conf.arp_cache,sent_times), wrpipe )
+                        cPickle.dump( (conf.netcache,sent_times), wrpipe )
                         wrpipe.close()
                     except:
                         pass
@@ -158,11 +158,11 @@ def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, m
                             raise
                 finally:
                     try:
-                        ac,sent_times = cPickle.load(rdpipe)
+                        nc,sent_times = cPickle.load(rdpipe)
                     except EOFError:
                         warning("Child died unexpectedly. Packets may have not been sent %i"%os.getpid())
                     else:
-                        conf.arp_cache.update(ac)
+                        conf.netcache.update(nc)
                         for p,t in zip(all_stimuli, sent_times):
                             p.sent_time = t
                     os.waitpid(pid,0)
-- 
GitLab