[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