blob: f8dbe16b45c95e9f6fe0c42152bfc2b25489b95e [file] [log] [blame]
# This file is part of Scapy
# See http://www.secdev.org/projects/scapy for more informations
# Copyright (C) Philippe Biondi <phil@secdev.org>
# 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 *
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))
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
# NTPHeader, NTPControl and NTPPrivate are NTP packets.
# This might help, for example when reading a pcap file.
def haslayer(self, cls):
"""Specific: NTPHeader().haslayer(NTP) should return True."""
if cls == "NTP":
if isinstance(self, NTP):
return True
elif issubclass(cls, NTP):
if isinstance(self, cls):
return True
return super(NTP, self).haslayer(cls)
def getlayer(self, cls, nb=1, _track=None, _subclass=True, **flt):
return super(NTP, self).getlayer(cls, nb=nb, _track=_track,
_subclass=True, **flt)
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"
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)",
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"
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)
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:
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:
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.)
# 4 - no data available (e.g. request for details on unknown peer)
# 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.
# 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)"
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})