From db7c72f041294e905fb361b4e61d1902125dc212 Mon Sep 17 00:00:00 2001
From: Pierre Lalet <pierre@droids-corp.org>
Date: Tue, 20 Dec 2016 08:08:57 +0000
Subject: [PATCH] Add conf.checkIPinIP (#387)

* Add conf.checkIPinIP

This setting, when set to False, will exclude IP layers which encapsulate
another IP layer from the checks to tell if a packet is an answer to
another one.

Fixes #383

* conf.checkIPinIP affects both IP() and IPv6()

Setting conf.checkIPinIP will handle IP()/IP(), IP()/IPv6(),
IPv6()/IP(), IPv6()/IPv6().

Thanks @guedou!

* Clean-up IP.hashret and .answers code

* Add tests for .hashret() and .answers()
---
 scapy/config.py       |  3 +++
 scapy/layers/inet.py  | 24 +++++++++++++++++-------
 scapy/layers/inet6.py | 10 ++++++++++
 test/regression.uts   | 20 ++++++++++++++++++++
 4 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/scapy/config.py b/scapy/config.py
index 032ab871..12c6d0d7 100755
--- a/scapy/config.py
+++ b/scapy/config.py
@@ -301,6 +301,8 @@ checkIPID: if 0, doesn't check that IPID matches between IP sent and ICMP IP cit
            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 @@ contribs: a dict which can be used by contrib layers to store local configuratio
     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
index 05091c44..1ed71e97 100644
--- a/scapy/layers/inet.py
+++ b/scapy/layers/inet.py
@@ -402,14 +402,24 @@ class IP(Packet, IPTools):
              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
index 86c63aea..0e510fae 100644
--- a/scapy/layers/inet6.py
+++ b/scapy/layers/inet6.py
@@ -410,6 +410,9 @@ class IPv6(_IPv6GuessPayload, Packet, IPTools):
             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 @@ class IPv6(_IPv6GuessPayload, Packet, IPTools):
             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: 
diff --git a/test/regression.uts b/test/regression.uts
index 11ab3978..ae2954d3 100644
--- 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
+
 
 ############
 ############
-- 
GitLab