From d79c849e964759a09c0c0c69635e42af6a5070f6 Mon Sep 17 00:00:00 2001
From: Jan Sebechlebsky <sebechlebskyjan@gmail.com>
Date: Fri, 20 Jan 2017 22:01:17 +0100
Subject: [PATCH] Add support for PPP Link Control Protocol

---
 scapy/layers/ppp.py | 224 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 220 insertions(+), 4 deletions(-)

diff --git a/scapy/layers/ppp.py b/scapy/layers/ppp.py
index b2e40ca7..e415eebf 100644
--- a/scapy/layers/ppp.py
+++ b/scapy/layers/ppp.py
@@ -11,13 +11,13 @@ PPP (Point to Point Protocol)
 
 import struct
 from scapy.packet import Packet, bind_layers
-from scapy.layers.l2 import Ether, CookedLinux
+from scapy.layers.l2 import Ether, CookedLinux, GRE_PPTP
 from scapy.layers.inet import IP
 from scapy.layers.inet6 import IPv6
 from scapy.fields import BitField, ByteEnumField, ByteField, \
-    ConditionalField, FieldLenField, IPField, PacketListField, \
-    ShortEnumField, ShortField, StrFixedLenField, StrLenField, XByteField, \
-    XShortField
+    ConditionalField, FieldLenField, IntField, IPField, LenField, \
+    PacketListField, PacketField, ShortEnumField, ShortField, \
+    StrFixedLenField, StrLenField, XByteField, XShortField
 
 
 class PPPoE(Packet):
@@ -339,6 +339,220 @@ class PPP_ECP(Packet):
                     FieldLenField("len" , None, fmt="H", length_of="options", adjust=lambda p,x:x+4 ),
                     PacketListField("options", [],  PPP_ECP_Option, length_from=lambda p:p.len-4,) ]
 
+### Link Control Protocol (RFC 1661)
+
+_PPP_lcptypes = {1: "Configure-Request",
+                 2: "Configure-Ack",
+                 3: "Configure-Nak",
+                 4: "Configure-Reject",
+                 5: "Terminate-Request",
+                 6: "Terminate-Ack",
+                 7: "Code-Reject",
+                 8: "Protocol-Reject",
+                 9: "Echo-Request",
+                10: "Echo-Reply",
+                11: "Discard-Request"}
+
+
+class PPP_LCP(Packet):
+    name = "PPP Link Control Protocol"
+    fields_desc = [ByteEnumField("code", 5, _PPP_lcptypes),
+                   XByteField("id", 0),
+                   FieldLenField("len", None, fmt="H", length_of="data",
+                                 adjust=lambda p, x: x + 4),
+                   StrLenField("data", "",
+                               length_from=lambda p:p.len-4)]
+
+    def extract_padding(self, pay):
+        return "",pay
+
+    @classmethod
+    def dispatch_hook(cls, _pkt = None, *args, **kargs):
+        if _pkt:
+            o = ord(_pkt[0])
+            if o in [1, 2, 3, 4]:
+                return PPP_LCP_Configure
+            elif o in [5,6]:
+                return PPP_LCP_Terminate
+            elif o == 7:
+                return PPP_LCP_Code_Reject
+            elif o == 8:
+                return PPP_LCP_Protocol_Reject
+            elif o in [9, 10]:
+                return PPP_LCP_Echo
+            elif o == 11:
+                return PPP_LCP_Discard_Request
+            else:
+                return cls
+        return cls
+
+
+_PPP_lcp_optiontypes = {1: "Maximum-Receive-Unit",
+                        2: "Async-Control-Character-Map",
+                        3: "Authentication-protocol",
+                        4: "Quality-protocol",
+                        5: "Magic-number",
+                        7: "Protocol-Field-Compression",
+                        8: "Address-and-Control-Field-Compression",
+                        13: "Callback"}
+
+
+class PPP_LCP_Option(Packet):
+    name = "PPP LCP Option"
+    fields_desc = [ByteEnumField("type", None, _PPP_lcp_optiontypes),
+                   FieldLenField("len", None, fmt="B", length_of="data",
+                                 adjust=lambda p,x:x+2),
+                   StrLenField("data", None, length_from=lambda p:p.len-2)]
+
+    def extract_padding(self, pay):
+        return "", pay
+
+    registered_options = {}
+
+    @classmethod
+    def register_variant(cls):
+        cls.registered_options[cls.type.default] = cls
+
+    @classmethod
+    def dispatch_hook(cls, _pkt=None, *args, **kargs):
+        if _pkt:
+            o = ord(_pkt[0])
+            return cls.registered_options.get(o, cls)
+        return cls
+
+
+class PPP_LCP_MRU_Option(PPP_LCP_Option):
+    fields_desc = [ByteEnumField("type", 1, _PPP_lcp_optiontypes),
+                   FieldLenField("len", 4, fmt="B", adjust=lambda p,x:4),
+                   ShortField("max_recv_unit", 1500)]
+
+_PPP_LCP_auth_protocols = {0xc023: "Password authentication protocol",
+                           0xc223: "Challenge-response authentication protocol",
+                           0xc227: "PPP Extensible authentication protocol"}
+
+_PPP_LCP_CHAP_algorithms = {5: "MD5",
+                            6: "SHA1",
+                            128: "MS-CHAP",
+                            129: "MS-CHAP-v2"}
+
+
+class PPP_LCP_ACCM_Option(PPP_LCP_Option):
+    fields_desc = [ByteEnumField("type", 2, _PPP_lcp_optiontypes),
+                   FieldLenField("len", 6, fmt="B"),
+                   BitField("accm", 0x00000000, 32)]
+
+
+def adjust_auth_len(pkt, x):
+    if pkt.auth_protocol == 0xc223:
+        return 5
+    elif pkt.auth_protocol == 0xc023:
+        return 4
+    else:
+        return x + 4
+
+
+class PPP_LCP_Auth_Protocol_Option(PPP_LCP_Option):
+    fields_desc = [ByteEnumField("type", 3, _PPP_lcp_optiontypes),
+                   FieldLenField("len", None, fmt="B", length_of="data",
+                                 adjust=adjust_auth_len),
+                   ShortEnumField("auth_protocol", 0xc023, _PPP_LCP_auth_protocols),
+                   ConditionalField(StrLenField("data", '', length_from=lambda p:p.len-4),
+                                    lambda p:p.auth_protocol != 0xc223),
+                   ConditionalField(ByteEnumField("algorithm", 5, _PPP_LCP_CHAP_algorithms),
+                                    lambda p:p.auth_protocol == 0xc223)]
+
+
+_PPP_LCP_quality_protocols = {0xc025: "Link Quality Report"}
+
+
+class PPP_LCP_Quality_Protocol_Option(PPP_LCP_Option):
+    fields_desc = [ByteEnumField("type", 4, _PPP_lcp_optiontypes),
+                   FieldLenField("len", None, fmt="B", length_of="data",
+                                 adjust=lambda p,x:x+4),
+                   ShortEnumField("quality_protocol", 0xc025, _PPP_LCP_quality_protocols),
+                   StrLenField("data", "", length_from=lambda p:p.len-4)]
+
+
+class PPP_LCP_Magic_Number_Option(PPP_LCP_Option):
+    fields_desc = [ByteEnumField("type", 5, _PPP_lcp_optiontypes),
+                   FieldLenField("len", 6, fmt="B", adjust = lambda p,x:6),
+                   IntField("magic_number", None)]
+
+
+_PPP_lcp_callback_operations = {0: "Location determined by user authentication",
+                                1: "Dialing string",
+                                2: "Location identifier",
+                                3: "E.164 number",
+                                4: "Distinguished name"}
+
+
+class PPP_LCP_Callback_Option(PPP_LCP_Option):
+    fields_desc = [ByteEnumField("type", 13, _PPP_lcp_optiontypes),
+                   FieldLenField("len", None, fmt="B", length_of="message",
+                                 adjust=lambda p,x:x+3),
+                   ByteEnumField("operation", 0, _PPP_lcp_callback_operations),
+                   StrLenField("message", "", length_from=lambda p:p.len-3)]
+
+
+class PPP_LCP_Configure(PPP_LCP):
+    fields_desc = [ByteEnumField("code", 1, _PPP_lcptypes),
+                   XByteField("id", 0),
+                   FieldLenField("len", None, fmt="H", length_of="options",
+                                 adjust=lambda p,x:x+4),
+                   PacketListField("options", [], PPP_LCP_Option,
+                                   length_from=lambda p:p.len-4)]
+
+    def answers(self, other):
+        return isinstance(other, PPP_LCP_Configure) and self.code in [2, 3, 4]\
+           and other.code == 1 and other.id == self.id
+
+
+class PPP_LCP_Terminate(PPP_LCP):
+
+    def answers(self, other):
+        return isinstance(other, PPP_LCP_Terminate) and self.code == 6\
+           and other.code == 5 and other.id == self.id
+
+
+class PPP_LCP_Code_Reject(PPP_LCP):
+    fields_desc = [ByteEnumField("code", 7, _PPP_lcptypes),
+                   XByteField("id", 0),
+                   FieldLenField("len", None, fmt="H", length_of="rejected_packet",
+                                 adjust=lambda p,x:x+4),
+                   PacketField("rejected_packet", None, PPP_LCP)]
+
+
+class PPP_LCP_Protocol_Reject(PPP_LCP):
+    fields_desc = [ByteEnumField("code", 8, _PPP_lcptypes),
+                   XByteField("id", 0),
+                   FieldLenField("len", None, fmt="H", length_of="rejected_information",
+                                 adjust=lambda p,x:x+6),
+                   ShortEnumField("rejected_protocol", None, _PPP_proto),
+                   PacketField("rejected_information", None, Packet)]
+
+
+class PPP_LCP_Echo(PPP_LCP):
+     fields_desc = [ByteEnumField("code", 9, _PPP_lcptypes),
+                    XByteField("id", 0),
+                    FieldLenField("len", None, fmt="H", length_of="data",
+                                 adjust=lambda p,x:x+8),
+                    IntField("magic_number", None),
+                    StrLenField("data", "", length_from=lambda p:p.len-8)]
+
+     def answers(self, other):
+         return isinstance(other, PPP_LCP_Echo) and self.code == 10\
+            and other.code == 9 and self.id == other.id
+
+
+class PPP_LCP_Discard_Request(PPP_LCP):
+    fields_desc = [ByteEnumField("code", 11, _PPP_lcptypes),
+                   XByteField("id", 0),
+                   FieldLenField("len", None, fmt="H", length_of="data",
+                                 adjust=lambda p,x:x+8),
+                   IntField("magic_number", None),
+                   StrLenField("data", "", length_from=lambda p:p.len-8)]
+
+
 bind_layers( Ether,         PPPoED,        type=0x8863)
 bind_layers( Ether,         PPPoE,         type=0x8864)
 bind_layers( CookedLinux,   PPPoED,        proto=0x8863)
@@ -349,5 +563,7 @@ bind_layers( PPP,           IP,            proto=0x0021)
 bind_layers( PPP,           IPv6,          proto=0x0057)
 bind_layers( PPP,           PPP_IPCP,      proto=0x8021)
 bind_layers( PPP,           PPP_ECP,       proto=0x8053)
+bind_layers( PPP,           PPP_LCP,       proto=0xc021)
 bind_layers( Ether,         PPP_IPCP,      type=0x8021)
 bind_layers( Ether,         PPP_ECP,       type=0x8053)
+bind_layers( GRE_PPTP,      PPP,           proto=0x880b)
-- 
GitLab