# This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Philippe Biondi # This program is published under a GPLv2 license """ NTP (Network Time Protocol). References : RFC 5905, RC 1305, ntpd source code """ from __future__ import absolute_import import struct import time import datetime from scapy.packet import Packet, bind_layers from scapy.fields import BitField, BitEnumField, ByteField, ByteEnumField, \ XByteField, SignedByteField, FlagsField, ShortField, LEShortField, \ IntField, LEIntField, FixedPointField, IPField, StrField, \ StrFixedLenField, StrFixedLenEnumField, XStrFixedLenField, PacketField, \ PacketLenField, PacketListField, FieldListField, ConditionalField, \ PadField from scapy.layers.inet6 import IP6Field from scapy.layers.inet import UDP from scapy.utils import lhex from scapy.compat import orb from scapy.config import conf import scapy.modules.six as six from scapy.modules.six.moves import range ############################################################################# # Constants ############################################################################# _NTP_AUTH_MD5_MIN_SIZE = 68 _NTP_EXT_MIN_SIZE = 16 _NTP_HDR_WITH_EXT_MIN_SIZE = _NTP_AUTH_MD5_MIN_SIZE + _NTP_EXT_MIN_SIZE _NTP_AUTH_MD5_TAIL_SIZE = 20 _NTP_AUTH_MD5_DGST_SIZE = 16 _NTP_PRIVATE_PACKET_MIN_SIZE = 8 # ntpd "Private" messages are the shortest _NTP_PACKET_MIN_SIZE = _NTP_PRIVATE_PACKET_MIN_SIZE _NTP_PRIVATE_REQ_PKT_TAIL_LEN = 28 # seconds between 01-01-1900 and 01-01-1970 _NTP_BASETIME = 2208988800 # include/ntp.h _NTP_SHIFT = 8 _NTP_HASH_SIZE = 128 ############################################################################# # Fields and utilities ############################################################################# class XLEShortField(LEShortField): """ XShortField which value is encoded in little endian. """ def i2repr(self, pkt, x): return lhex(self.i2h(pkt, x)) class TimeStampField(FixedPointField): """ This field handles the timestamp fields in the NTP header. """ def __init__(self, name, default): FixedPointField.__init__(self, name, default, 64, 32) def i2repr(self, pkt, val): if val is None: return "--" val = self.i2h(pkt, val) if val < _NTP_BASETIME: return val return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(val - _NTP_BASETIME)) # noqa: E501 def any2i(self, pkt, val): if isinstance(val, six.string_types): val = int(time.mktime(time.strptime(val))) + _NTP_BASETIME elif isinstance(val, datetime.datetime): val = int(val.strftime("%s")) + _NTP_BASETIME return FixedPointField.any2i(self, pkt, val) def i2m(self, pkt, val): if val is None: val = FixedPointField.any2i(self, pkt, time.time() + _NTP_BASETIME) return FixedPointField.i2m(self, pkt, val) ############################################################################# # NTP ############################################################################# # RFC 5905 / Section 7.3 _leap_indicator = { 0: "no warning", 1: "last minute of the day has 61 seconds", 2: "last minute of the day has 59 seconds", 3: "unknown (clock unsynchronized)" } # RFC 5905 / Section 7.3 _ntp_modes = { 0: "reserved", 1: "symmetric active", 2: "symmetric passive", 3: "client", 4: "server", 5: "broadcast", 6: "NTP control message", 7: "reserved for private use" } # RFC 5905 / Section 7.3 _reference_identifiers = { "GOES": "Geosynchronous Orbit Environment Satellite", "GPS ": "Global Position System", "GAL ": "Galileo Positioning System", "PPS ": "Generic pulse-per-second", "IRIG": "Inter-Range Instrumentation Group", "WWVB": "LF Radio WWVB Ft. Collins, CO 60 kHz", "DCF ": "LF Radio DCF77 Mainflingen, DE 77.5 kHz", "HBG ": "LF Radio HBG Prangins, HB 75 kHz", "MSF ": "LF Radio MSF Anthorn, UK 60 kHz", "JJY ": "LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz", "LORC": "MF Radio LORAN C station, 100 kHz", "TDF ": "MF Radio Allouis, FR 162 kHz", "CHU ": "HF Radio CHU Ottawa, Ontario", "WWV ": "HF Radio WWV Ft. Collins, CO", "WWVH": "HF Radio WWVH Kauai, HI", "NIST": "NIST telephone modem", "ACTS": "NIST telephone modem", "USNO": "USNO telephone modem", "PTB ": "European telephone modem", } # RFC 5905 / Section 7.4 _kiss_codes = { "ACST": "The association belongs to a unicast server.", "AUTH": "Server authentication failed.", "AUTO": "Autokey sequence failed.", "BCST": "The association belongs to a broadcast server.", "CRYP": "Cryptographic authentication or identification failed.", "DENY": "Access denied by remote server.", "DROP": "Lost peer in symmetric mode.", "RSTR": "Access denied due to local policy.", "INIT": "The association has not yet synchronized for the first time.", "MCST": "The association belongs to a dynamically discovered server.", "NKEY": "No key found.", "RATE": "Rate exceeded.", "RMOT": "Alteration of association from a remote host running ntpdc." } # Used by _ntp_dispatcher to instantiate the appropriate class def _ntp_dispatcher(payload): """ Returns the right class for a given NTP packet. """ # By default, calling NTP() will build a NTP packet as defined in RFC 5905 # (see the code of NTPHeader). Use NTPHeader for extension fields and MAC. if payload is None: return NTPHeader else: length = len(payload) if length >= _NTP_PACKET_MIN_SIZE: first_byte = orb(payload[0]) # Extract NTP mode mode = first_byte & 7 return {6: NTPControl, 7: NTPPrivate}.get(mode, NTPHeader) return conf.raw_layer class NTP(Packet): """ Base class that allows easier instantiation of a NTP packet from binary data. """ @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right class for the given data. """ return _ntp_dispatcher(_pkt) def pre_dissect(self, s): """ Check that the payload is long enough to build a NTP packet. """ length = len(s) if length < _NTP_PACKET_MIN_SIZE: err = " ({}".format(length) + " is < _NTP_PACKET_MIN_SIZE " err += "({})).".format(_NTP_PACKET_MIN_SIZE) raise _NTPInvalidDataException(err) return s def mysummary(self): return self.sprintf("NTP v%ir,NTP.version%, %NTP.mode%") class _NTPAuthenticatorPaddingField(StrField): """ StrField handling the padding that may be found before the "authenticator" field. """ def getfield(self, pkt, s): ret = None remain = s length = len(s) if length > _NTP_AUTH_MD5_TAIL_SIZE: start = length - _NTP_AUTH_MD5_TAIL_SIZE ret = s[:start] remain = s[start:] return remain, ret class NTPAuthenticator(Packet): """ Packet handling the "authenticator" part of a NTP packet, as defined in RFC 5905. """ name = "Authenticator" fields_desc = [ _NTPAuthenticatorPaddingField("padding", ""), IntField("key_id", 0), XStrFixedLenField("dgst", "", length_from=lambda x: 16) ] def extract_padding(self, s): return b"", s class NTPExtension(Packet): """ Packet handling a NTPv4 extension. """ ######################################################################### # # RFC 7822 ######################################################################### # # 7.5. NTP Extension Field Format # # In NTPv3, one or more extension fields can be inserted after the # header and before the MAC, if a MAC is present. # # Other than defining the field format, this document makes no use # of the field contents. An extension field contains a request or # response message in the format shown in Figure 14. # # 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 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Field Type | Length | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # . . # . Value . # . . # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Padding (as needed) | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # Figure 14: Extension Field Format # # # All extension fields are zero-padded to a word (four octets) # boundary. ######################################################################### # name = "extension" fields_desc = [ ShortField("type", 0), ShortField("len", 0), PadField(PacketField("value", "", Packet), align=4, padwith=b"\x00") ] class NTPExtPacketListField(PacketListField): """ PacketListField handling NTPv4 extensions (NTPExtension list). """ def m2i(self, pkt, m): ret = None if len(m) >= 16: ret = NTPExtension(m) else: ret = conf.raw_layer(m) return ret def getfield(self, pkt, s): lst = [] remain = s length = len(s) if length > _NTP_AUTH_MD5_TAIL_SIZE: end = length - _NTP_AUTH_MD5_TAIL_SIZE extensions = s[:end] remain = s[end:] extensions_len = len(extensions) while extensions_len >= 16: ext_len = struct.unpack("!H", extensions[2:4])[0] ext_len = min(ext_len, extensions_len) if ext_len < 1: ext_len = extensions_len current = extensions[:ext_len] extensions = extensions[ext_len:] current_packet = self.m2i(pkt, current) lst.append(current_packet) extensions_len = len(extensions) if extensions_len > 0: lst.append(self.m2i(pkt, extensions)) return remain, lst class NTPExtensions(Packet): """ Packet handling the NTPv4 extensions and the "MAC part" of the packet. """ ######################################################################### # # RFC 5905 / RFC 7822 ######################################################################### # # 7.5. NTP Extension Field Format # # In NTPv4, one or more extension fields can be inserted after the # header and before the MAC, if a MAC is present. ######################################################################### # name = "NTPv4 extensions" fields_desc = [ NTPExtPacketListField("extensions", [], Packet), PacketField("mac", NTPAuthenticator(), NTPAuthenticator) ] class NTPHeader(NTP): """ Packet handling the RFC 5905 NTP packet. """ ######################################################################### # # RFC 5905 ######################################################################### # # 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 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # |LI | VN |Mode | Stratum | Poll | Precision | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Root Delay | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Root Dispersion | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Reference ID | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Reference Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Origin Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Receive Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # + Transmit Timestamp (64) + # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # . . # . Extension Field 1 (variable) . # . . # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # . . # . Extension Field 2 (variable) . # . . # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Key Identifier | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # | dgst (128) | # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # Figure 8: Packet Header Format ######################################################################### # name = "NTPHeader" match_subclass = True fields_desc = [ BitEnumField("leap", 0, 2, _leap_indicator), BitField("version", 4, 3), BitEnumField("mode", 3, 3, _ntp_modes), BitField("stratum", 2, 8), BitField("poll", 0xa, 8), BitField("precision", 0, 8), FixedPointField("delay", 0, size=32, frac_bits=16), FixedPointField("dispersion", 0, size=32, frac_bits=16), ConditionalField(IPField("id", "127.0.0.1"), lambda p: p.stratum > 1), ConditionalField( StrFixedLenEnumField( "ref_id", "", length=4, enum=_reference_identifiers ), lambda p: p.stratum < 2 ), TimeStampField("ref", 0), TimeStampField("orig", None), TimeStampField("recv", 0), TimeStampField("sent", None), ] def guess_payload_class(self, payload): """ Handles NTPv4 extensions and MAC part (when authentication is used.) """ plen = len(payload) if plen > _NTP_AUTH_MD5_TAIL_SIZE: return NTPExtensions elif plen == _NTP_AUTH_MD5_TAIL_SIZE: return NTPAuthenticator return Packet.guess_payload_class(self, payload) class _NTPInvalidDataException(Exception): """ Raised when it is not possible to instantiate a NTP packet with the given data. """ def __init__(self, details): Exception.__init__( self, "Data does not seem to be a valid NTP message" + details ) ############################################################################## # Private (mode 7) ############################################################################## # Operation codes _op_codes = { 0: "CTL_OP_UNSPEC", 1: "CTL_OP_READSTAT", 2: "CTL_OP_READVAR", 3: "CTL_OP_WRITEVAR", 4: "CTL_OP_READCLOCK", 5: "CTL_OP_WRITECLOCK", 6: "CTL_OP_SETTRAP", 7: "CTL_OP_ASYNCMSG", 8: "CTL_OP_CONFIGURE", 9: "CTL_OP_SAVECONFIG", 10: "CTL_OP_READ_MRU", 11: "CTL_OP_READ_ORDLIST_A", 12: "CTL_OP_REQ_NONCE", 31: "CTL_OP_UNSETTRAP" } # System status words _system_statuses = { 0: "no warning", 1: "last minute was 61 seconds", 2: "last minute was 59 seconds", 3: "alarm condition (clock not synchronized)" } _clock_sources = { 0: "unspecified or unknown", 1: " Calibrated atomic clock", 2: "VLF (band 4) or LF (band 5) radio", 3: "HF (band 7) radio", 4: "UHF (band 9) satellite", 5: "local net", 6: "UDP/NTP", 7: "UDP/TIME", 8: "eyeball-and-wristwatch", 9: "telephone modem" } _system_event_codes = { 0: "unspecified", 1: "system restart", 2: "system or hardware fault", 3: "system new status word (leap bits or synchronization change)", 4: "system new synchronization source or stratum (sys.peer or sys.stratum change)", # noqa: E501 5: "system clock reset (offset correction exceeds CLOCK.MAX)", 6: "system invalid time or date", 7: "system clock exception", } # Peer status words _peer_statuses = { 0: "configured", 1: "authentication enabled", 2: "authentication okay", 3: "reachability okay", 4: "reserved" } _peer_selection = { 0: "rejected", 1: "passed sanity checks", 2: "passed correctness checks", 3: "passed candidate checks", 4: "passed outlyer checks", 5: "current synchronization source; max distance exceeded", 6: "current synchronization source; max distance okay", 7: "reserved" } _peer_event_codes = { 0: "unspecified", 1: "peer IP error", 2: "peer authentication failure", 3: "peer unreachable", 4: "peer reachable", 5: "peer clock exception", } # Clock status words _clock_statuses = { 0: "clock operating within nominals", 1: "reply timeout", 2: "bad reply format", 3: "hardware or software fault", 4: "propagation failure", 5: "bad date format or value", 6: "bad time format or value" } # Error status words _error_statuses = { 0: "unspecified", 1: "authentication failure", 2: "invalid message length or format", 3: "invalid opcode", 4: "unknown association identifier", 5: "unknown variable name", 6: "invalid variable value", 7: "administratively prohibited" } class NTPStatusPacket(Packet): """ Packet handling a non specific status word. """ name = "status" fields_desc = [ShortField("status", 0)] def extract_padding(self, s): return b"", s class NTPSystemStatusPacket(Packet): """ Packet handling the system status fields. """ name = "system status" fields_desc = [ BitEnumField("leap_indicator", 0, 2, _system_statuses), BitEnumField("clock_source", 0, 6, _clock_sources), BitField("system_event_counter", 0, 4), BitEnumField("system_event_code", 0, 4, _system_event_codes), ] def extract_padding(self, s): return b"", s class NTPPeerStatusPacket(Packet): """ Packet handling the peer status fields. """ name = "peer status" fields_desc = [ BitField("configured", 0, 1), BitField("auth_enabled", 0, 1), BitField("authentic", 0, 1), BitField("reachability", 0, 1), BitField("reserved", 0, 1), BitEnumField("peer_sel", 0, 3, _peer_selection), BitField("peer_event_counter", 0, 4), BitEnumField("peer_event_code", 0, 4, _peer_event_codes), ] def extract_padding(self, s): return b"", s class NTPClockStatusPacket(Packet): """ Packet handling the clock status fields. """ name = "clock status" fields_desc = [ BitEnumField("clock_status", 0, 8, _clock_statuses), BitField("code", 0, 8) ] def extract_padding(self, s): return b"", s class NTPErrorStatusPacket(Packet): """ Packet handling the error status fields. """ name = "error status" fields_desc = [ BitEnumField("error_code", 0, 8, _error_statuses), BitField("reserved", 0, 8) ] def extract_padding(self, s): return b"", s class NTPControlStatusField(PacketField): """ This field provides better readability for the "status" field. """ ######################################################################### # # RFC 1305 ######################################################################### # # Appendix B.3. Commands // ntpd source code: ntp_control.h ######################################################################### # def m2i(self, pkt, m): ret = None association_id = struct.unpack("!H", m[2:4])[0] if pkt.err == 1: ret = NTPErrorStatusPacket(m) # op_code == CTL_OP_READSTAT elif pkt.op_code == 1: if association_id != 0: ret = NTPPeerStatusPacket(m) else: ret = NTPSystemStatusPacket(m) # op_code == CTL_OP_READVAR elif pkt.op_code == 2: if association_id != 0: ret = NTPPeerStatusPacket(m) else: ret = NTPSystemStatusPacket(m) # op_code == CTL_OP_WRITEVAR elif pkt.op_code == 3: ret = NTPStatusPacket(m) # op_code == CTL_OP_READCLOCK or op_code == CTL_OP_WRITECLOCK elif pkt.op_code == 4 or pkt.op_code == 5: ret = NTPClockStatusPacket(m) else: ret = NTPStatusPacket(m) return ret class NTPPeerStatusDataPacket(Packet): """ Packet handling the data field when op_code is CTL_OP_READSTAT and the association_id field is null. """ name = "data / peer status" fields_desc = [ ShortField("association_id", 0), PacketField("peer_status", NTPPeerStatusPacket(), NTPPeerStatusPacket), ] class NTPControlDataPacketLenField(PacketLenField): """ PacketField handling the "data" field of NTP control messages. """ def m2i(self, pkt, m): ret = None # op_code == CTL_OP_READSTAT if pkt.op_code == 1: if pkt.association_id == 0: # Data contains association ID and peer status ret = NTPPeerStatusDataPacket(m) else: ret = conf.raw_layer(m) else: ret = conf.raw_layer(m) return ret def getfield(self, pkt, s): length = self.length_from(pkt) i = None if length > 0: # RFC 1305 # The maximum number of data octets is 468. # # include/ntp_control.h # u_char data[480 + MAX_MAC_LEN]; /* data + auth */ # # Set the minimum length to 480 - 468 length = max(12, length) if length % 4: length += (4 - length % 4) try: i = self.m2i(pkt, s[:length]) except Exception: if conf.debug_dissector: raise i = conf.raw_layer(load=s[:length]) return s[length:], i class NTPControl(NTP): """ Packet handling NTP mode 6 / "Control" messages. """ ######################################################################### # # RFC 1305 ######################################################################### # # Appendix B.3. Commands // ntpd source code: ntp_control.h ######################################################################### # name = "Control message" match_subclass = True fields_desc = [ BitField("zeros", 0, 2), BitField("version", 2, 3), BitField("mode", 6, 3), BitField("response", 0, 1), BitField("err", 0, 1), BitField("more", 0, 1), BitEnumField("op_code", 0, 5, _op_codes), ShortField("sequence", 0), ConditionalField(NTPControlStatusField( "status_word", "", Packet), lambda p: p.response == 1), ConditionalField(ShortField("status", 0), lambda p: p.response == 0), ShortField("association_id", 0), ShortField("offset", 0), ShortField("count", None), NTPControlDataPacketLenField( "data", "", Packet, length_from=lambda p: p.count), PacketField("authenticator", "", NTPAuthenticator), ] def post_build(self, p, pay): if self.count is None: length = 0 if self.data: length = len(self.data) p = p[:11] + struct.pack("!H", length) + p[13:] return p + pay ############################################################################## # Private (mode 7) ############################################################################## _information_error_codes = { 0: "INFO_OKAY", 1: "INFO_ERR_IMPL", 2: "INFO_ERR_REQ", 3: "INFO_ERR_FMT", 4: "INFO_ERR_NODATA", 7: "INFO_ERR_AUTH" } _implementations = { 0: "IMPL_UNIV", 2: "IMPL_XNTPD_OLD", 3: "XNTPD" } _request_codes = { 0: "REQ_PEER_LIST", 1: "REQ_PEER_LIST_SUM", 2: "REQ_PEER_INFO", 3: "REQ_PEER_STATS", 4: "REQ_SYS_INFO", 5: "REQ_SYS_STATS", 6: "REQ_IO_STATS", 7: "REQ_MEM_STATS", 8: "REQ_LOOP_INFO", 9: "REQ_TIMER_STATS", 10: "REQ_CONFIG", 11: "REQ_UNCONFIG", 12: "REQ_SET_SYS_FLAG", 13: "REQ_CLR_SYS_FLAG", 14: "REQ_MONITOR", 15: "REQ_NOMONITOR", 16: "REQ_GET_RESTRICT", 17: "REQ_RESADDFLAGS", 18: "REQ_RESSUBFLAGS", 19: "REQ_UNRESTRICT", 20: "REQ_MON_GETLIST", 21: "REQ_RESET_STATS", 22: "REQ_RESET_PEER", 23: "REQ_REREAD_KEYS", 24: "REQ_DO_DIRTY_HACK", 25: "REQ_DONT_DIRTY_HACK", 26: "REQ_TRUSTKEY", 27: "REQ_UNTRUSTKEY", 28: "REQ_AUTHINFO", 29: "REQ_TRAPS", 30: "REQ_ADD_TRAP", 31: "REQ_CLR_TRAP", 32: "REQ_REQUEST_KEY", 33: "REQ_CONTROL_KEY", 34: "REQ_GET_CTLSTATS", 35: "REQ_GET_LEAPINFO", 36: "REQ_GET_CLOCKINFO", 37: "REQ_SET_CLKFUDGE", 38: "REQ_GET_KERNEL", 39: "REQ_GET_CLKBUGINFO", 41: "REQ_SET_PRECISION", 42: "REQ_MON_GETLIST_1", 43: "REQ_HOSTNAME_ASSOCID", 44: "REQ_IF_STATS", 45: "REQ_IF_RELOAD" } # Flags in the peer information returns _peer_flags = [ "INFO_FLAG_CONFIG", "INFO_FLAG_SYSPEER", "INFO_FLAG_BURST", "INFO_FLAG_REFCLOCK", "INFO_FLAG_PREFER", "INFO_FLAG_AUTHENABLE", "INFO_FLAG_SEL_CANDIDATE", "INFO_FLAG_SHORTLIST", "INFO_FLAG_IBURST" ] # Flags in the system information returns _sys_info_flags = [ "INFO_FLAG_BCLIENT", "INFO_FLAG_AUTHENTICATE", "INFO_FLAG_NTP", "INFO_FLAG_KERNEL", "INFO_FLAG_CAL", "INFO_FLAG_PPS_SYNC", "INFO_FLAG_MONITOR", "INFO_FLAG_FILEGEN", ] class NTPInfoPeerList(Packet): """ Used to return raw lists of peers. """ name = "info_peer_list" fields_desc = [ IPField("addr", "0.0.0.0"), ShortField("port", 0), ByteEnumField("hmode", 0, _ntp_modes), FlagsField("flags", 0, 8, _peer_flags), IntField("v6_flag", 0), IntField("unused1", 0), IP6Field("addr6", "::") ] class NTPInfoPeerSummary(Packet): """ Sort of the info that ntpdc returns by default. """ name = "info_peer_summary" fields_desc = [ IPField("dstaddr", "0.0.0.0"), IPField("srcaddr", "0.0.0.0"), ShortField("srcport", 0), ByteField("stratum", 0), ByteField("hpoll", 0), ByteField("ppoll", 0), ByteField("reach", 0), FlagsField("flags", 0, 8, _peer_flags), ByteField("hmode", _ntp_modes), FixedPointField("delay", 0, size=32, frac_bits=16), TimeStampField("offset", 0), FixedPointField("dispersion", 0, size=32, frac_bits=16), IntField("v6_flag", 0), IntField("unused1", 0), IP6Field("dstaddr6", "::"), IP6Field("srcaddr6", "::") ] class NTPInfoPeer(Packet): """ Peer information structure. """ name = "info_peer" fields_desc = [ IPField("dstaddr", "0.0.0.0"), IPField("srcaddr", "0.0.0.0"), ShortField("srcport", 0), FlagsField("flags", 0, 8, _peer_flags), ByteField("leap", 0), ByteEnumField("hmode", 0, _ntp_modes), ByteField("pmode", 0), ByteField("stratum", 0), ByteField("ppoll", 0), ByteField("hpoll", 0), SignedByteField("precision", 0), ByteField("version", 0), ByteField("unused8", 0), ByteField("reach", 0), ByteField("unreach", 0), XByteField("flash", 0), ByteField("ttl", 0), XLEShortField("flash2", 0), ShortField("associd", 0), LEIntField("keyid", 0), IntField("pkeyid", 0), IPField("refid", 0), IntField("timer", 0), FixedPointField("rootdelay", 0, size=32, frac_bits=16), FixedPointField("rootdispersion", 0, size=32, frac_bits=16), TimeStampField("reftime", 0), TimeStampField("org", 0), TimeStampField("rec", 0), TimeStampField("xmt", 0), FieldListField( "filtdelay", [0.0 for i in range(0, _NTP_SHIFT)], FixedPointField("", 0, size=32, frac_bits=16), count_from=lambda p: _NTP_SHIFT ), FieldListField( "filtoffset", [0.0 for i in range(0, _NTP_SHIFT)], TimeStampField("", 0), count_from=lambda p: _NTP_SHIFT ), FieldListField( "order", [0 for i in range(0, _NTP_SHIFT)], ByteField("", 0), count_from=lambda p: _NTP_SHIFT ), FixedPointField("delay", 0, size=32, frac_bits=16), FixedPointField("dispersion", 0, size=32, frac_bits=16), TimeStampField("offset", 0), FixedPointField("selectdisp", 0, size=32, frac_bits=16), IntField("unused1", 0), IntField("unused2", 0), IntField("unused3", 0), IntField("unused4", 0), IntField("unused5", 0), IntField("unused6", 0), IntField("unused7", 0), FixedPointField("estbdelay", 0, size=32, frac_bits=16), IntField("v6_flag", 0), IntField("unused9", 0), IP6Field("dstaddr6", "::"), IP6Field("srcaddr6", "::"), ] class NTPInfoPeerStats(Packet): """ Peer statistics structure. """ name = "info_peer_stats" fields_desc = [ IPField("dstaddr", "0.0.0.0"), IPField("srcaddr", "0.0.0.0"), ShortField("srcport", 0), FlagsField("flags", 0, 16, _peer_flags), IntField("timereset", 0), IntField("timereceived", 0), IntField("timetosend", 0), IntField("timereachable", 0), IntField("sent", 0), IntField("unused1", 0), IntField("processed", 0), IntField("unused2", 0), IntField("badauth", 0), IntField("bogusorg", 0), IntField("oldpkt", 0), IntField("unused3", 0), IntField("unused4", 0), IntField("seldisp", 0), IntField("selbroken", 0), IntField("unused5", 0), ByteField("candidate", 0), ByteField("unused6", 0), ByteField("unused7", 0), ByteField("unused8", 0), IntField("v6_flag", 0), IntField("unused9", 0), IP6Field("dstaddr6", "::"), IP6Field("srcaddr6", "::"), ] class NTPInfoLoop(Packet): """ Loop filter variables. """ name = "info_loop" fields_desc = [ TimeStampField("last_offset", 0), TimeStampField("drift_comp", 0), IntField("compliance", 0), IntField("watchdog_timer", 0) ] class NTPInfoSys(Packet): """ System info. Mostly the sys.* variables, plus a few unique to the implementation. """ name = "info_sys" fields_desc = [ IPField("peer", "0.0.0.0"), ByteField("peer_mode", 0), ByteField("leap", 0), ByteField("stratum", 0), ByteField("precision", 0), FixedPointField("rootdelay", 0, size=32, frac_bits=16), FixedPointField("rootdispersion", 0, size=32, frac_bits=16), IPField("refid", 0), TimeStampField("reftime", 0), IntField("poll", 0), FlagsField("flags", 0, 8, _sys_info_flags), ByteField("unused1", 0), ByteField("unused2", 0), ByteField("unused3", 0), FixedPointField("bdelay", 0, size=32, frac_bits=16), FixedPointField("frequency", 0, size=32, frac_bits=16), TimeStampField("authdelay", 0), FixedPointField("stability", 0, size=32, frac_bits=16), IntField("v6_flag", 0), IntField("unused4", 0), IP6Field("peer6", "::") ] class NTPInfoSysStats(Packet): """ System stats. These are collected in the protocol module. """ name = "info_sys_stats" fields_desc = [ IntField("timeup", 0), IntField("timereset", 0), IntField("denied", 0), IntField("oldversionpkt", 0), IntField("newversionpkt", 0), IntField("unknownversion", 0), IntField("badlength", 0), IntField("processed", 0), IntField("badauth", 0), IntField("received", 0), IntField("limitrejected", 0) ] class NTPInfoMemStats(Packet): """ Peer memory statistics. """ name = "info_mem_stats" fields_desc = [ IntField("timereset", 0), ShortField("totalpeermem", 0), ShortField("freepeermem", 0), IntField("findpeer_calls", 0), IntField("allocations", 0), IntField("demobilizations", 0), FieldListField( "hashcount", [0.0 for i in range(0, _NTP_HASH_SIZE)], ByteField("", 0), count_from=lambda p: _NTP_HASH_SIZE ) ] class NTPInfoIOStats(Packet): """ I/O statistics. """ name = "info_io_stats" fields_desc = [ IntField("timereset", 0), ShortField("totalrecvbufs", 0), ShortField("freerecvbufs", 0), ShortField("fullrecvbufs", 0), ShortField("lowwater", 0), IntField("dropped", 0), IntField("ignored", 0), IntField("received", 0), IntField("sent", 0), IntField("notsent", 0), IntField("interrupts", 0), IntField("int_received", 0) ] class NTPInfoTimerStats(Packet): """ Timer stats. """ name = "info_timer_stats" fields_desc = [ IntField("timereset", 0), IntField("alarms", 0), IntField("overflows", 0), IntField("xmtcalls", 0), ] _conf_peer_flags = [ "CONF_FLAG_AUTHENABLE", "CONF_FLAG_PREFER", "CONF_FLAG_BURST", "CONF_FLAG_IBURST", "CONF_FLAG_NOSELECT", "CONF_FLAG_SKEY" ] class NTPConfPeer(Packet): """ Structure for passing peer configuration information. """ name = "conf_peer" fields_desc = [ IPField("peeraddr", "0.0.0.0"), ByteField("hmode", 0), ByteField("version", 0), ByteField("minpoll", 0), ByteField("maxpoll", 0), FlagsField("flags", 0, 8, _conf_peer_flags), ByteField("ttl", 0), ShortField("unused1", 0), IntField("keyid", 0), StrFixedLenField("keystr", "", length=128), IntField("v6_flag", 0), IntField("unused2", 0), IP6Field("peeraddr6", "::") ] class NTPConfUnpeer(Packet): """ Structure for passing peer deletion information. """ name = "conf_unpeer" fields_desc = [ IPField("peeraddr", "0.0.0.0"), IntField("v6_flag", 0), IP6Field("peeraddr6", "::") ] _restrict_flags = [ "RES_IGNORE", "RES_DONTSERVE", "RES_DONTTRUST", "RES_VERSION", "RES_NOPEER", "RES_LIMITED", "RES_NOQUERY", "RES_NOMODIFY", "RES_NOTRAP", "RES_LPTRAP", "RES_KOD", "RES_MSSNTP", "RES_FLAKE", "RES_NOMRULIST", ] class NTPConfRestrict(Packet): """ Structure used for specifying restrict entries. """ name = "conf_restrict" fields_desc = [ IPField("addr", "0.0.0.0"), IPField("mask", "0.0.0.0"), FlagsField("flags", 0, 16, _restrict_flags), ShortField("m_flags", 0), IntField("v6_flag", 0), IP6Field("addr6", "::"), IP6Field("mask6", "::") ] class NTPInfoKernel(Packet): """ Structure used for returning kernel pll/PPS information """ name = "info_kernel" fields_desc = [ IntField("offset", 0), IntField("freq", 0), IntField("maxerror", 0), IntField("esterror", 0), ShortField("status", 0), ShortField("shift", 0), IntField("constant", 0), IntField("precision", 0), IntField("tolerance", 0), IntField("ppsfreq", 0), IntField("jitter", 0), IntField("stabil", 0), IntField("jitcnt", 0), IntField("calcnt", 0), IntField("errcnt", 0), IntField("stbcnt", 0), ] class NTPInfoIfStatsIPv4(Packet): """ Interface statistics. """ name = "info_if_stats" fields_desc = [ PadField(IPField("unaddr", "0.0.0.0"), 16, padwith=b"\x00"), PadField(IPField("unbcast", "0.0.0.0"), 16, padwith=b"\x00"), PadField(IPField("unmask", "0.0.0.0"), 16, padwith=b"\x00"), IntField("v6_flag", 0), StrFixedLenField("ifname", "", length=32), IntField("flags", 0), IntField("last_ttl", 0), IntField("num_mcast", 0), IntField("received", 0), IntField("sent", 0), IntField("notsent", 0), IntField("uptime", 0), IntField("scopeid", 0), IntField("ifindex", 0), IntField("ifnum", 0), IntField("peercnt", 0), ShortField("family", 0), ByteField("ignore_packets", 0), ByteField("action", 0), IntField("_filler0", 0) ] class NTPInfoIfStatsIPv6(Packet): """ Interface statistics. """ name = "info_if_stats" fields_desc = [ IP6Field("unaddr", "::"), IP6Field("unbcast", "::"), IP6Field("unmask", "::"), IntField("v6_flag", 0), StrFixedLenField("ifname", "", length=32), IntField("flags", 0), IntField("last_ttl", 0), IntField("num_mcast", 0), IntField("received", 0), IntField("sent", 0), IntField("notsent", 0), IntField("uptime", 0), IntField("scopeid", 0), IntField("ifindex", 0), IntField("ifnum", 0), IntField("peercnt", 0), ShortField("family", 0), ByteField("ignore_packets", 0), ByteField("action", 0), IntField("_filler0", 0) ] class NTPInfoMonitor1(Packet): """ Structure used for returning monitor data. """ name = "InfoMonitor1" fields_desc = [ IntField("lasttime", 0), IntField("firsttime", 0), IntField("lastdrop", 0), IntField("count", 0), IPField("addr", "0.0.0.0"), IPField("daddr", "0.0.0.0"), IntField("flags", 0), ShortField("port", 0), ByteField("mode", 0), ByteField("version", 0), IntField("v6_flag", 0), IntField("unused1", 0), IP6Field("addr6", "::"), IP6Field("daddr6", "::") ] class NTPInfoAuth(Packet): """ Structure used to return information concerning the authentication module. """ name = "info_auth" fields_desc = [ IntField("timereset", 0), IntField("numkeys", 0), IntField("numfreekeys", 0), IntField("keylookups", 0), IntField("keynotfound", 0), IntField("encryptions", 0), IntField("decryptions", 0), IntField("expired", 0), IntField("keyuncached", 0), ] class NTPConfTrap(Packet): """ Structure used to pass add/clear trap information to the client """ name = "conf_trap" fields_desc = [ IPField("local_address", "0.0.0.0"), IPField("trap_address", "0.0.0.0"), ShortField("trap_port", 0), ShortField("unused", 0), IntField("v6_flag", 0), IP6Field("local_address6", "::"), IP6Field("trap_address6", "::"), ] class NTPInfoControl(Packet): """ Structure used to return statistics from the control module. """ name = "info_control" fields_desc = [ IntField("ctltimereset", 0), IntField("numctlreq", 0), IntField("numctlbadpkts", 0), IntField("numctlresponses", 0), IntField("numctlfrags", 0), IntField("numctlerrors", 0), IntField("numctltooshort", 0), IntField("numctlinputresp", 0), IntField("numctlinputfrag", 0), IntField("numctlinputerr", 0), IntField("numctlbadoffset", 0), IntField("numctlbadversion", 0), IntField("numctldatatooshort", 0), IntField("numctlbadop", 0), IntField("numasyncmsgs", 0), ] # ntp_request.h _ntpd_private_errors = { 0: "no error", 1: "incompatible implementation number", 2: "unimplemented request code", 3: "format error (wrong data items, data size, packet size etc.)", 4: "no data available (e.g. request for details on unknown peer)", 5: "I don\"t know", 6: "I don\"t know", 7: "authentication failure (i.e. permission denied)", } # dict mapping request codes to the right response data class _private_data_objects = { 0: NTPInfoPeerList, # "REQ_PEER_LIST", 1: NTPInfoPeerSummary, # "REQ_PEER_LIST_SUM", 2: NTPInfoPeer, # "REQ_PEER_INFO", 3: NTPInfoPeerStats, # "REQ_PEER_STATS", 4: NTPInfoSys, # "REQ_SYS_INFO", 5: NTPInfoSysStats, # "REQ_SYS_STATS", 6: NTPInfoIOStats, # "REQ_IO_STATS", 7: NTPInfoMemStats, # "REQ_MEM_STATS", 8: NTPInfoLoop, # "REQ_LOOP_INFO", 9: NTPInfoTimerStats, # "REQ_TIMER_STATS", 10: NTPConfPeer, # "REQ_CONFIG", 11: NTPConfUnpeer, # "REQ_UNCONFIG", 28: NTPInfoAuth, # "REQ_AUTHINFO", 30: NTPConfTrap, # "REQ_ADD_TRAP", 34: NTPInfoControl, # "REQ_GET_CTLSTATS", 38: NTPInfoKernel, # "REQ_GET_KERNEL", 42: NTPInfoMonitor1, # "REQ_MON_GETLIST_1", } class NTPPrivateRespPacketListField(PacketListField): """ PacketListField handling the response data. """ def m2i(self, pkt, s): ret = None # info_if_stats if pkt.request_code == 44 or pkt.request_code == 45: is_v6 = struct.unpack("!I", s[48:52])[0] ret = NTPInfoIfStatsIPv6(s) if is_v6 else NTPInfoIfStatsIPv4(s) else: ret = _private_data_objects.get(pkt.request_code, conf.raw_layer)(s) # noqa: E501 return ret def getfield(self, pkt, s): lst = [] remain = s length = pkt.data_item_size if length > 0: item_counter = 0 # Response payloads can be placed in several packets while len(remain) >= pkt.data_item_size and item_counter < pkt.nb_items: # noqa: E501 current = remain[:length] remain = remain[length:] current_packet = self.m2i(pkt, current) lst.append(current_packet) item_counter += 1 return remain, lst class NTPPrivateReqPacket(Packet): """ Packet handling request data. """ name = "request data" fields_desc = [StrField("req_data", "")] _request_codes = { 0: "REQ_PEER_LIST", 1: "REQ_PEER_LIST_SUM", 2: "REQ_PEER_INFO", 3: "REQ_PEER_STATS", 4: "REQ_SYS_INFO", 5: "REQ_SYS_STATS", 6: "REQ_IO_STATS", 7: "REQ_MEM_STATS", 8: "REQ_LOOP_INFO", 9: "REQ_TIMER_STATS", 10: "REQ_CONFIG", 11: "REQ_UNCONFIG", 12: "REQ_SET_SYS_FLAG", 13: "REQ_CLR_SYS_FLAG", 14: "REQ_MONITOR", 15: "REQ_NOMONITOR", 16: "REQ_GET_RESTRICT", 17: "REQ_RESADDFLAGS", 18: "REQ_RESSUBFLAGS", 19: "REQ_UNRESTRICT", 20: "REQ_MON_GETLIST", 21: "REQ_RESET_STATS", 22: "REQ_RESET_PEER", 23: "REQ_REREAD_KEYS", 24: "REQ_DO_DIRTY_HACK", 25: "REQ_DONT_DIRTY_HACK", 26: "REQ_TRUSTKEY", 27: "REQ_UNTRUSTKEY", 28: "REQ_AUTHINFO", 29: "REQ_TRAPS", 30: "REQ_ADD_TRAP", 31: "REQ_CLR_TRAP", 32: "REQ_REQUEST_KEY", 33: "REQ_CONTROL_KEY", 34: "REQ_GET_CTLSTATS", 35: "REQ_GET_LEAPINFO", 36: "REQ_GET_CLOCKINFO", 37: "REQ_SET_CLKFUDGE", 38: "REQ_GET_KERNEL", 39: "REQ_GET_CLKBUGINFO", 41: "REQ_SET_PRECISION", 42: "REQ_MON_GETLIST_1", 43: "REQ_HOSTNAME_ASSOCID", 44: "REQ_IF_STATS", 45: "REQ_IF_RELOAD" } class NTPPrivateReqPacketListField(PacketListField): """ Handles specific request packets. """ # See ntpdc/ntpdc.c and ntpdc/ntpdc_ops.c def m2i(self, pkt, s): ret = None if pkt.request_code == 2 or pkt.request_code == 3: # REQ_PEER_INFO (see ntpdc/ntpdc_ops.c: showpeer()) # REQ_PEER_STATS (for request only) ret = NTPInfoPeerList(s) elif pkt.request_code == 10: # REQ_CONFIG ret = NTPConfPeer(s) elif pkt.request_code == 11: # REQ_CONFIG ret = NTPConfUnpeer(s) elif pkt.request_code == 17: # REQ_RESADDFLAGS ret = NTPConfRestrict(s) elif pkt.request_code == 18: # REQ_RESSUBFLAGS ret = NTPConfRestrict(s) elif pkt.request_code == 22: # REQ_RESET_PEER ret = NTPConfUnpeer(s) elif pkt.request_code == 30 or pkt.request_code == 31: # REQ_ADD_TRAP ret = NTPConfTrap(s) else: ret = NTPPrivateReqPacket(s) return ret def getfield(self, pkt, s): lst = [] remain = s length = pkt.data_item_size if length > 0: item_counter = 0 while len(remain) >= pkt.data_item_size * pkt.nb_items and item_counter < pkt.nb_items: # noqa: E501 current = remain[:length] remain = remain[length:] current_packet = self.m2i(pkt, current) lst.append(current_packet) item_counter += 1 # If "auth" bit is set, don't forget the padding bytes if pkt.auth: padding_end = len(remain) - _NTP_PRIVATE_REQ_PKT_TAIL_LEN current_packet = conf.raw_layer(remain[:padding_end]) lst.append(current_packet) remain = remain[padding_end:] return remain, lst class NTPPrivatePktTail(Packet): """ include/ntp_request.h The req_pkt_tail structure is used by ntpd to adjust for different packet sizes that may arrive. """ name = "req_pkt_tail" fields_desc = [ TimeStampField("tstamp", 0), IntField("key_id", 0), XStrFixedLenField( "dgst", "", length_from=lambda x: _NTP_AUTH_MD5_DGST_SIZE) ] class NTPPrivate(NTP): """ Packet handling the private (mode 7) messages. """ ######################################################################### # ntpd source code: ntp_request.h ######################################################################### # # A mode 7 packet is used exchanging data between an NTP server # and a client for purposes other than time synchronization, e.g. # monitoring, statistics gathering and configuration. A mode 7 # packet has the following format: # # 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 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # |R|M| VN | Mode|A| Sequence | Implementation| Req Code | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Err | Number of data items | MBZ | Size of data item | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # | Data (Minimum 0 octets, maximum 500 octets) | # | | # [...] | # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Encryption Keyid (when A bit set) | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | | # | Message Authentication Code (when A bit set) | # | | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # # where the fields are (note that the client sends requests, the server # responses): # # Response Bit: This packet is a response (if clear, packet is a request). # # More Bit: Set for all packets but the last in a response which # requires more than one packet. # # Version Number: 2 for current version # # Mode: Always 7 # # Authenticated bit: If set, this packet is authenticated. # # Sequence number: For a multipacket response, contains the sequence # number of this packet. 0 is the first in the sequence, # 127 (or less) is the last. The More Bit must be set in # all packets but the last. # # Implementation number: The number of the implementation this request code # is defined by. An implementation number of zero is used # for request codes/data formats which all implementations # agree on. Implementation number 255 is reserved (for # extensions, in case we run out). # # Request code: An implementation-specific code which specifies the # operation to be (which has been) performed and/or the # format and semantics of the data included in the packet. # # Err: Must be 0 for a request. For a response, holds an error # code relating to the request. If nonzero, the operation # requested wasn't performed. # # 0 - no error # 1 - incompatible implementation number # 2 - unimplemented request code # 3 - format error (wrong data items, data size, packet size etc.) # noqa: E501 # 4 - no data available (e.g. request for details on unknown peer) # noqa: E501 # 5-6 I don"t know # 7 - authentication failure (i.e. permission denied) # # Number of data items: number of data items in packet. 0 to 500 # # MBZ: A reserved data field, must be zero in requests and responses. # # Size of data item: size of each data item in packet. 0 to 500 # # Data: Variable sized area containing request/response data. For # requests and responses the size in octets must be greater # than or equal to the product of the number of data items # and the size of a data item. For requests the data area # must be exactly 40 octets in length. For responses the # data area may be any length between 0 and 500 octets # inclusive. # # Message Authentication Code: Same as NTP spec, in definition and function. # noqa: E501 # May optionally be included in requests which require # authentication, is never included in responses. # # The version number, mode and keyid have the same function and are # in the same location as a standard NTP packet. The request packet # is the same size as a standard NTP packet to ease receive buffer # management, and to allow the same encryption procedure to be used # both on mode 7 and standard NTP packets. The mac is included when # it is required that a request be authenticated, the keyid should be # zero in requests in which the mac is not included. # # The data format depends on the implementation number/request code pair # and whether the packet is a request or a response. The only requirement # is that data items start in the octet immediately following the size # word and that data items be concatenated without padding between (i.e. # if the data area is larger than data_items*size, all padding is at # the end). Padding is ignored, other than for encryption purposes. # Implementations using encryption might want to include a time stamp # or other data in the request packet padding. The key used for requests # is implementation defined, but key 15 is suggested as a default. ######################################################################### # name = "Private (mode 7)" match_subclass = True fields_desc = [ BitField("response", 0, 1), BitField("more", 0, 1), BitField("version", 2, 3), BitField("mode", 0, 3), BitField("auth", 0, 1), BitField("seq", 0, 7), ByteEnumField("implementation", 0, _implementations), ByteEnumField("request_code", 0, _request_codes), BitEnumField("err", 0, 4, _ntpd_private_errors), BitField("nb_items", 0, 12), BitField("mbz", 0, 4), BitField("data_item_size", 0, 12), ConditionalField( NTPPrivateReqPacketListField( "req_data", [], Packet, length_from=lambda p: p.data_item_size, count_from=lambda p: p.nb_items ), lambda p: p.response == 0 ), # Responses ConditionalField( NTPPrivateRespPacketListField( "data", [], Packet, length_from=lambda p: p.data_item_size, count_from=lambda p: p.nb_items ), lambda p: p.response == 1 ), # Responses are not supposed to be authenticated ConditionalField(PacketField("authenticator", "", NTPPrivatePktTail), lambda p: p.response == 0 and p.auth == 1), ] ############################################################################## # Layer bindings ############################################################################## bind_layers(UDP, NTP, {"sport": 123}) bind_layers(UDP, NTP, {"dport": 123}) bind_layers(UDP, NTP, {"sport": 123, "dport": 123})