Repository: secdev/scapy

Issue:
Answers and IP in IP
Hi,

Using IP in IP packets is a useful way to "route" packets across specific paths in a network. I'm sending IP+ICMP packets with an IP in IP (ip protocol 4) wrapper, and getting the ICMP response back according to tcpdump:

```
19:10:40.158647 IP (tos 0x0, ttl 255, id 1, offset 0, flags [none], proto IPIP (4), length 48)
    10.20.30.93 > 10.0.99.1: IP (tos 0x0, ttl 255, id 1, offset 0, flags [none], proto ICMP (1), length 28)
    10.20.30.93 > 10.20.0.207: ICMP echo request, id 0, seq 1, length 8
19:10:40.228185 IP (tos 0x0, ttl 48, id 26655, offset 0, flags [none], proto ICMP (1), length 28)
    10.20.0.207 > 10.20.30.93: ICMP echo reply, id 0, seq 1, length 8
```

However, the response doesn't match so I don't get an "answer" when using `srp()`.  Is there any way to "ignore" the ip-in-ip layer when calculating the hashret(), or otherwise "make it match"?

Thanks,
John



Discussion:

Resolving patch:
diff --git a/scapy/config.py b/scapy/config.py
--- a/scapy/config.py
+++ b/scapy/config.py
@@ -301,6 +301,8 @@ class Conf(ConfClass):
            if 1, checks that they either are equal or byte swapped equals (bug in some IP stacks)
            if 2, strictly checks that they are equals
 checkIPsrc: if 1, checks IP src in IP and ICMP IP citation match (bug in some NAT stacks)
+checkIPinIP: if True, checks that IP-in-IP layers match. If False, do not
+             check IP layers that encapsulates another IP layer
 check_TCPerror_seqack: if 1, also check that TCP seq and ack match the ones in ICMP citation
 iff      : selects the default output interface for srp() and sendp(). default:"eth0")
 verb     : level of verbosity, from 0 (almost mute) to 3 (verbose)
@@ -334,6 +336,7 @@ class Conf(ConfClass):
     checkIPID = 0
     checkIPsrc = 1
     checkIPaddr = 1
+    checkIPinIP = True
     check_TCPerror_seqack = 0
     verb = 2
     prompt = ">>> "
diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py
--- a/scapy/layers/inet.py
+++ b/scapy/layers/inet.py
@@ -401,14 +401,24 @@ def hashret(self):
              and (isinstance(self.payload, ICMP))
              and (self.payload.type in [3,4,5,11,12]) ):
             return self.payload.payload.hashret()
-        else:
-            if self.dst == "224.0.0.251":  # mDNS
-                return struct.pack("B", self.proto) + self.payload.hashret()
-            if conf.checkIPsrc and conf.checkIPaddr:
-                return strxor(inet_aton(self.src),inet_aton(self.dst))+struct.pack("B",self.proto)+self.payload.hashret()
-            else:
-                return struct.pack("B", self.proto)+self.payload.hashret()
+        if not conf.checkIPinIP and self.proto in [4, 41]:  # IP, IPv6
+            return self.payload.hashret()
+        if self.dst == "224.0.0.251":  # mDNS
+            return struct.pack("B", self.proto) + self.payload.hashret()
+        if conf.checkIPsrc and conf.checkIPaddr:
+            return (strxor(inet_aton(self.src), inet_aton(self.dst))
+                    + struct.pack("B",self.proto) + self.payload.hashret())
+        return struct.pack("B", self.proto) + self.payload.hashret()
     def answers(self, other):
+        if not conf.checkIPinIP:  # skip IP in IP and IPv6 in IP
+            if self.proto in [4, 41]:
+                return self.payload.answers(other)
+            if isinstance(other, IP) and other.proto in [4, 41]:
+                return self.answers(other.payload)
+            if conf.ipv6_enabled \
+               and isinstance(other, scapy.layers.inet6.IPv6) \
+               and other.nh in [4, 41]:
+                return self.answers(other.payload)                
         if not isinstance(other,IP):
             return 0
         if conf.checkIPaddr:
diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py
--- a/scapy/layers/inet6.py
+++ b/scapy/layers/inet6.py
@@ -410,6 +410,9 @@ def hashret(self):
             elif (self.payload.type in [133,134,135,136,144,145]):
                 return struct.pack("B", self.nh)+self.payload.hashret()
 
+        if not conf.checkIPinIP and self.nh in [4, 41]:  # IP, IPv6
+            return self.payload.hashret()
+
         nh = self.nh
         sd = self.dst
         ss = self.src
@@ -453,6 +456,13 @@ def hashret(self):
             return struct.pack("B", nh)+self.payload.hashret()
 
     def answers(self, other):
+        if not conf.checkIPinIP:  # skip IP in IP and IPv6 in IP
+            if self.nh in [4, 41]:
+                return self.payload.answers(other)
+            if isinstance(other, IPv6) and other.nh in [4, 41]:
+                return self.answers(other.payload)
+            if isinstance(other, IP) and other.proto in [4, 41]:
+                return self.answers(other.payload)
         if not isinstance(other, IPv6): # self is reply, other is request
             return False
         if conf.checkIPaddr: 


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -156,6 +156,26 @@ t=Ether()/IP()/TCP()
 x==y, x==z, x==t, y==z, y==t, z==t, w==x
 _ == (False, False, False, False, False, False, True)
 
+= answers
+~ basic
+a1, a2 = "1.2.3.4", "5.6.7.8"
+p1 = IP(src=a1, dst=a2)/ICMP(type=8)
+p2 = IP(src=a2, dst=a1)/ICMP(type=0)
+assert p1.hashret() == p2.hashret()
+assert not p1.answers(p2)
+assert p2.answers(p1)
+conf_back = conf.checkIPinIP
+conf.checkIPinIP = True
+px = [IP()/p1, IPv6()/p1]
+assert not any(p.hashret() == p2.hashret() for p in px)
+assert not any(p.answers(p2) for p in px)
+assert not any(p2.answers(p) for p in px)
+conf.checkIPinIP = False
+assert all(p.hashret() == p2.hashret() for p in px)
+assert not any(p.answers(p2) for p in px)
+assert all(p2.answers(p) for p in px)
+conf.checkIPinIP = conf_back
+
 
 ############
 ############