[Git][NTPsec/ntpsec][master] 2 commits: Implement full sync-packet analysis in Python.

Eric S. Raymond gitlab at mg.gitlab.com
Wed Nov 23 20:51:28 UTC 2016


Eric S. Raymond pushed to branch master at NTPsec / ntpsec


Commits:
d53ff2b2 by Eric S. Raymond at 2016-11-23T14:32:01-05:00
Implement full sync-packet analysis in Python.

- - - - -
11917346 by Eric S. Raymond at 2016-11-23T15:51:06-05:00
Add a packet textualizer to packet.py.

- - - - -


1 changed file:

- pylib/packet.py


Changes:

=====================================
pylib/packet.py
=====================================
--- a/pylib/packet.py
+++ b/pylib/packet.py
@@ -66,7 +66,21 @@ The RFC5905 diagram is slightly out of date in that the digest header assumes
 a 128-bit (16-octet) MD5 hash, but it is also possible for the field to be a
 160-bit (20-octet) SHA-1 hash.
 
-Here's what a Mode 6 packet looks like
+An extension field consists of a 32-bit network-order type field
+length, followed by a 32-bit network-order payload length in octets,
+followed by the payload (which must be padded to a 4-octet boundary).
+
+       0                   1                   2                   3
+       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |         Type field             |      Payload length          |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      |                        Payload (variable)                     |
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+Here's what a Mode 6 packet looks like:
 
        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
@@ -253,13 +267,18 @@ class Packet:
     def mode(self):
         return self.li_vn_mode & 0x7
 
+class SyncException(BaseException):
+    def __init__(self, message, errorcode=0):
+        self.message = message
+        self.errorcode = errorcode
+
 class SyncPacket(Packet):
     "Mode 1-5 time-synchronization packet, including SNTP."
     format = "!BBBBIIIQQQQ"
     HEADER_LEN = 48
     UNIX_EPOCH = 2208988800	# Midnight 1 Jan 1970 in secs since NTP epoch
 
-    def __init__(self, data= None):
+    def __init__(self, data=''):
         Packet.__init__(self)
         self.status = 0         # status word for association (uint16_t)
         self.stratum = 0
@@ -272,23 +291,20 @@ class SyncPacket(Packet):
         self.origin_timestamp = 0
         self.receive_timestamp = 0
         self.transmit_timestamp = 0
-        self.extension = ''
         self.data = data
-        if self.data is not None:
-            self.analyze(self.data)
-            self.posixize()
+        self.extension = ''
+        self.extfields = []
+        self.mac = ''
         self.hostname = None
         self.resolved = None
         self.received = time.time()
-        if len(self.data) > 192:
-            self.extension_data = data[-12:192]
-            self.auth_data = data[-12:]
-        else:
-            self.extension_data = None
-            self.auth_data = None
         self.trusted = True
+        self.analyze()
+        self.posixize()
 
-    def analyze(self, rawdata):
+    def analyze(self):
+        if len(self.data) < SyncPacket.HEADER_LEN or (len(self.data) & 3) != 0:
+            raise SyncException("impossible packet length")
         (self.li_vn_mode,
          self.stratum,
          self.poll,
@@ -300,9 +316,30 @@ class SyncPacket(Packet):
          self.origin_timestamp,
          self.receive_timestamp,
          self.transmit_timestamp) \
-         = struct.unpack(SyncPacket.format, rawdata[:SyncPacket.HEADER_LEN])
-        self.data = rawdata[SyncPacket.HEADER_LEN:]
-
+         = struct.unpack(SyncPacket.format, self.data[:SyncPacket.HEADER_LEN])
+        self.extension = self.data[SyncPacket.HEADER_LEN:]
+	# Parse the extension field if present. We figure out whether
+	# an extension field is present by measuring the MAC size. If
+	# the number of 4-octet words following the packet header is
+	# 0, no MAC is present and the packet is not authenticated. If
+	# 1, the packet is a crypto-NAK; if 3, the packet is
+	# authenticated with DES; if 5, the packet is authenticated
+	# with MD5; if 6, the packet is authenticated with SHA. If 2
+	# or 4, the packet is a runt and discarded forthwith. If
+	# greater than 6, an extension field is present, so we
+	# subtract the length of the field and go around again.
+        while len(self.extension) > 24:
+            (ftype, flen) = struct.unpack("!II", self.extension[:8])
+            self.extfields.append((ftype, self.extension[8:8+flen]))
+            self.extension = self.extension[8+flen:]
+        if len(self.extension) == 4:	# Crypto-NAK
+            self.mac = self.extension
+        if len(self.extension) == 12:	# DES
+            raise SyncException("Unsupported DES authentication")
+        elif len(self.extension) in (8, 16):
+            raise SyncException("Packet is a runt")
+        elif len(self.extension) in (20, 24):	# MD5 or SHA1
+            self.mac = self.extension
     @staticmethod
     def ntp_to_posix(t):
         "Scale from NTP time to POSIX time"
@@ -363,7 +400,32 @@ class SyncPacket(Packet):
         return polystr("%d.%d.%d.%d" % self.refid_octets())
 
     def is_crypto_nak(self):
-        len(self.extension) == 1
+        return len(self.mac) == 4
+
+    def has_MD5(self):
+        return len(self.mac) == 20
+
+    def has_SHA1(self):
+        return len(self.mac) == 24
+
+    def __repr__(self):
+        "Represent a posixized sync packet in an eyeball-friendly format."
+        r = "<NTP:%s:%d%:%d" % (self.leap(), self.version(), self.mode())
+        r += "%f:%f:" % (self.root_delay, self.root_dispersion)
+        rs = self.refid_as_string
+        if not rs.isprint():
+            rd = refid_as_address()
+        r += ":" + rs 
+        r += ":" + ntp.util.rfc3339(self.reference_timestamp)
+        r += ":" + ntp.util.rfc3339(self.origin_timestamp)
+        r += ":" + ntp.util.rfc3339(self.receive_timestamp)
+        r += ":" + ntp.util.rfc3339(self.transmit_timestamp)
+        if self.extfields:
+            r += ":" + repr(self.extfields)
+        if self.mac:
+            r += ":" + repr(self.mac)[1:-1]
+        r += ">"
+        return r
 
 class ControlPacket(Packet):
     "Mode 6 request/response."



View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/fbf291d31a1a2795c94a0f78891261f84a4e6420...119173467b291751be4e4e1cf9a5fa203bc5a30b
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ntpsec.org/pipermail/vc/attachments/20161123/4879951c/attachment.html>


More information about the vc mailing list