[Git][NTPsec/ntpsec][packettesting] 6 commits: Documentation polishing.
Ian Bruene
gitlab at mg.gitlab.com
Thu Aug 17 14:05:17 UTC 2017
Ian Bruene pushed to branch packettesting at NTPsec / ntpsec
Commits:
5d2b8d6c by Eric S. Raymond at 2017-08-16T22:57:22-04:00
Documentation polishing.
- - - - -
e2467632 by Eric S. Raymond at 2017-08-17T06:39:48-04:00
Document no-flag case of unrestrict.
- - - - -
33ddffb3 by Eric S. Raymond at 2017-08-17T06:51:27-04:00
Documentation polishing.
- - - - -
3705a499 by Eric S. Raymond at 2017-08-17T09:32:38-04:00
Partially address Gitlab issue #270: Loss of precision in step_systime()
Change system residual and time-step variable to a new doubletime_t type
(long double) so these variables can handle the full range of l_fp 64-bit
time values. See the comment on the doubletime_t typedef in
include/ntp_types.h for extended discussion.
- - - - -
2ea53625 by Matt Selsky at 2017-08-17T09:43:45-04:00
Remove repeated words in comment
- - - - -
8d2c65a9 by Ian Bruene at 2017-08-17T14:04:36+00:00
Added tests for packet.py/ControlSession()
Also a couple tweaks for ControlSession to make it more amenable to tests.
- - - - -
10 changed files:
- NEWS
- docs/includes/access-commands.txt
- docs/ntpsec.txt
- include/ntp_fp.h
- include/ntp_types.h
- libntp/pymodule.c
- libntp/systime.c
- ntpfrob/jitter.c
- pylib/packet.py
- + tests/pylib/test_packet.py
Changes:
=====================================
NEWS
=====================================
--- a/NEWS
+++ b/NEWS
@@ -14,8 +14,11 @@ The code size has been further reduced, to 56KLOC.
ntpmon now reports units on time figures.
You can now turn off restriction flags with an _unrestrict_ statement
-that takes arguments exactly like a _restrict_. This is expected to be
-useful mainly with the "ntpq config" command.
+that takes arguments exactly like a _restrict_, except that with no
+argument flags it removes any filter rule associated with the
+address/mask (as opposed to creating one with unrestricted
+access). This is expected to be useful mainly with the "ntpq config"
+command.
Builds are fully reproducible; see SOURCE_DATE_EPOCH and BUILD_EPOCH.
=====================================
docs/includes/access-commands.txt
=====================================
--- a/docs/includes/access-commands.txt
+++ b/docs/includes/access-commands.txt
@@ -110,9 +110,10 @@ unrestricted).
[[restrict]]
+unrestrict+ _address_[/_cidr_] [+mask+ _mask_] [+flag+ +...+]::
- Like a +restrict+ command, but turns off the specified flags
- rather than turning them on (expected to be useful mainly with ntpq
- config). Use only on an address/mask or CIDR-format address
- mentioned in a previous +restrict+ statement.
+ Like a +restrict+ command, but turns off the specified flags rather
+ than turning them on (expected to be useful mainly with ntpq
+ :config). An unrestrict with no flags specified removes any rule
+ with matching address and mask. Use only on an address/mask or
+ CIDR-format address mentioned in a previous +restrict+ statement.
// end
=====================================
docs/ntpsec.txt
=====================================
--- a/docs/ntpsec.txt
+++ b/docs/ntpsec.txt
@@ -221,8 +221,10 @@ codebase has been outright removed, with less than 5% new code added.
notation rather than as an address/mask pair.
* You can now turn off restriction flags with an _unrestrict_
- statement that takes arguments exactly like a _restrict_. This
- is expected to be useful mainly with the "ntpq config" command.
+ statement that takes arguments exactly like a _restrict_. With
+ no aruments it removes any rule matching the address mask
+ entirely. This is expected to be useful mainly with the "ntpq
+ :config" command.
* The includefile directive now evaluates relative pathnames not with
respect to the current working directory but with respect to the
=====================================
include/ntp_fp.h
=====================================
--- a/include/ntp_fp.h
+++ b/include/ntp_fp.h
@@ -133,27 +133,23 @@ typedef struct {
*/
#define FRAC 4294967296.0 /* 2^32 as a double */
-#include <math.h> /* ldexp() */
+#include <math.h> /* ldexpl() */
-static inline l_fp dtolfp(double d)
-/* double to l_fp
+static inline l_fp dtolfp(doubletime_t d)
+/* long double to l_fp
* assumes signed l_fp, i.e. a time offset
* undefined return if d in NaN
*/
{
-/* Please check issue 264 before changing this to use llround
- * https://gitlab.com/NTPsec/ntpsec/issues/264
- * llround is broken on NetBSD, 2017-May-05
- */
- return (l_fp)(int64_t)(ldexp(d, 32));
+ return (l_fp)(int64_t)(ldexpl(d, 32));
}
-static inline double lfptod(l_fp r)
-/* l_fp to double
+static inline doubletime_t lfptod(l_fp r)
+/* l_fp to long double
* assumes signed l_fp, i.e. a time offset
*/
{
- return ldexp((double)((int64_t)r), -32);
+ return ldexpl((double)((int64_t)r), -32);
}
/*
@@ -171,7 +167,7 @@ extern char * rfc3339time (time_t);
extern void set_sys_fuzz (double);
extern void get_systime (l_fp *);
-extern bool step_systime (double, int (*settime)(struct timespec *));
+extern bool step_systime (doubletime_t, int (*settime)(struct timespec *));
extern bool adj_systime (double, int (*adjtime)(const struct timeval *, struct timeval *));
#define lfptoa(fpv, ndec) mfptoa((fpv), (ndec))
=====================================
include/ntp_types.h
=====================================
--- a/include/ntp_types.h
+++ b/include/ntp_types.h
@@ -63,6 +63,27 @@ typedef uint32_t keyid_t; /* cryptographic key ID */
#define KEYID_T_MAX (0xffffffff)
/*
+ * Ordinary double has only 53 bits of precision in IEEE754. But l_fp
+ * needs 64 bits of precision, arguably 65 bits after 2026. Thus, to have
+ * a float type coextensive with it, we need one with at least 65 bits of
+ * precision.
+ *
+ * The C standard does not specify the precision of a double. C99
+ * Annex F makes IEEE754 compliance optional and very few C compilers
+ * are fully IEEE754 compliant. C doubles may be 24 bits, 53 bits, or something
+ * else. Only rarely would a C double be able to hold a 65 bit number without
+ * loss of precision.
+ *
+ * This matters because initial steps may be large, such as when a host
+ * has no valid RTC and thinks the current time is 1Jan70. Thus truncating
+ * an l_fp conversion to double after 2026 risks loss of precision.
+ *
+ * On the other hand, long double is usually at least 80 bits.
+ * See https://en.wikipedia.org/wiki/Long_double for discussion and caveats.
+ */
+typedef long double doubletime_t;
+
+/*
* Cloning malloc()'s behavior of always returning pointers suitably
* aligned for the strictest alignment requirement of any type is not
* easy to do portably, as the maximum alignment required is not
=====================================
libntp/pymodule.c
=====================================
--- a/libntp/pymodule.c
+++ b/libntp/pymodule.c
@@ -127,11 +127,19 @@ static PyObject *
ntpc_step_systime(PyObject *self, PyObject *args)
{
double adjustment;
+ doubletime_t full_adjustment;
UNUSED_ARG(self);
+ /*
+ * What we really want is for Python to parse a long double.
+ * As this is, it's a potential source of problems in the Python
+ * utilties if and when the time difference between the Unix epoch
+ * and now exceeds the range of a double.
+ */
if (!PyArg_ParseTuple(args, "d", &adjustment))
return NULL;
- return Py_BuildValue("d", step_systime(adjustment, ntp_set_tod));
+ full_adjustment = adjustment;
+ return Py_BuildValue("d", step_systime(full_adjustment, ntp_set_tod));
}
int32_t ntp_random(void)
=====================================
libntp/systime.c
=====================================
--- a/libntp/systime.c
+++ b/libntp/systime.c
@@ -65,8 +65,8 @@ double sys_fuzz = 0; /* min. time to read the clock (s) */
bool trunc_os_clock; /* sys_tick > measured_tick */
time_stepped_callback step_callback;
-static double sys_residual = 0; /* adjustment residue (s) */
-static long sys_fuzz_nsec = 0; /* min. time to read the clock (ns) */
+static doubletime_t sys_residual = 0; /* adjustment residue (s) */
+static long sys_fuzz_nsec = 0; /* minimum time to read clock (ns) */
/* perlinger at ntp.org: As 'get_systime()' does its own check for clock
* backstepping, this could probably become a local variable in
@@ -236,7 +236,7 @@ adj_systime(
struct timeval adjtv; /* new adjustment */
struct timeval oadjtv; /* residual adjustment */
double quant; /* quantize to multiples of */
- double dtemp;
+ doubletime_t dtemp;
long ticks;
bool isneg = false;
@@ -315,7 +315,7 @@ adj_systime(
bool
step_systime(
- double step,
+ doubletime_t step,
int (*settime)(struct timespec *)
)
{
=====================================
ntpfrob/jitter.c
=====================================
--- a/ntpfrob/jitter.c
+++ b/ntpfrob/jitter.c
@@ -25,8 +25,8 @@
#define NBUF 800002
#define NSAMPLES 10
-static double sys_residual;
-static double average;
+static doubletime_t sys_residual;
+static doubletime_t average;
/*
* get_clocktime - return system time in NTP timestamp format.
@@ -36,7 +36,7 @@ get_clocktime(
l_fp *now /* system time */
)
{
- double dtemp;
+ doubletime_t dtemp;
struct timespec ts; /* seconds and nanoseconds */
@@ -107,7 +107,7 @@ void jitter(const iomode mode)
qsort(gtod, NBUF, sizeof(gtod[0]), doublecmp);
average = average / (NBUF - 2);
if (mode == json) {
- fprintf(stdout, "{\"Average\":%13.9f,", average);
+ fprintf(stdout, "{\"Average\":%13.9Lf,", average);
fprintf(stdout, "\"First rank\":[");
for (i = 0; i < NSAMPLES; i++) {
fprintf(stdout, "%13.9f", gtod[i]);
@@ -125,7 +125,7 @@ void jitter(const iomode mode)
}
else if (mode != raw)
{
- fprintf(stdout, "Average %13.9f\n", average);
+ fprintf(stdout, "Average %13.9Lf\n", average);
fprintf(stdout, "First rank\n");
for (i = 0; i < NSAMPLES; i++)
fprintf(stdout, "%2d %13.9f\n", i, gtod[i]);
=====================================
pylib/packet.py
=====================================
--- a/pylib/packet.py
+++ b/pylib/packet.py
@@ -878,7 +878,7 @@ class ControlSession:
# C implementation didn't use multiple responses, so we don't either
(family, socktype, protocol, canonname, sockaddr) = res[0]
if canonname is None:
- self.hostname = sockaddr.inet_ntop(sockaddr[0], family)
+ self.hostname = socket.inet_ntop(sockaddr[0], family)
self.isnum = True
else:
self.hostname = canonname or hname
@@ -1088,7 +1088,7 @@ class ControlSession:
# Check opcode and sequence number for a match.
# Could be old data getting to us.
# =======
- # These had the continues inside a if debug block. Probably
+ # These had the continues inside an if debug block. Probably
# shouldn't have been there, but if there is a problem move
# them back.
if rpkt.sequence != self.sequence:
@@ -1341,7 +1341,7 @@ class ControlSession:
Ask for, and get, a nonce that can be replayed.
This combats source address spoofing
"""
- for i in range(3):
+ for i in range(4):
# retry 4 times
self.doquery(opcode=ntp.control.CTL_OP_REQ_NONCE)
self.nonce_xmit = time.time()
@@ -1351,7 +1351,7 @@ This combats source address spoofing
# uh, oh, no nonce seen
# this print probably never can be seen...
- print("## Nonce expected: %s" % self.response)
+ self.logfp.write("## Nonce expected: %s" % self.response)
raise ControlException(SERR_BADNONCE)
def mrulist(self, variables=None, rawhook=None, direct=None):
@@ -1529,6 +1529,7 @@ This combats source address spoofing
if idx != curidx:
# This makes duplicates
curidx = idx
+
if mru:
# Can't have partial slots on list
# or printing crashes after ^C
=====================================
tests/pylib/test_packet.py
=====================================
--- /dev/null
+++ b/tests/pylib/test_packet.py
@@ -0,0 +1,842 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from __future__ import print_function, division
+
+import unittest
+import ntp.packet
+import ntp.control
+import ntp.util
+import socket
+import select
+import sys
+import getpass
+
+odict = ntp.util.OrderedDict
+
+
+class FileJig:
+ def __init__(self):
+ self.data = []
+ self.flushed = False
+ self.readline_return = ""
+
+ def write(self, data):
+ self.data.append(data)
+ self.flushed = False
+
+ def flush(self):
+ self.flushed = True
+
+ def readline(self):
+ return self.readline_return
+
+
+class SocketJig:
+ def __init__(self):
+ self.data = []
+ self.return_data = []
+ self.closed = False
+ self.connected = None
+ self.fail_connect = False
+
+ def sendall(self, data):
+ self.data.append(data)
+
+ def close(self):
+ self.closed = True
+
+ def connect(self, addr):
+ if self.fail_connect is True:
+ err = socket.error()
+ err.strerror = "socket!"
+ err.errno = 16
+ raise err
+ self.connected = addr
+
+ def recv(self, bytecount):
+ if len(self.return_data) > 0:
+ x = len(self.return_data)
+ current = self.return_data.pop(0)
+ if len(current) > bytecount:
+ ret = current[:bytecount]
+ current = current[bytecount:]
+ self.return_data.insert(0, current) # push unwanted data
+ return ret
+ else:
+ return current
+ return None
+
+
+class ControlPacketJig:
+ HEADER_LEN = ntp.packet.ControlPacket.HEADER_LEN
+ def __init__(self, session, opcode, associd, data):
+ self.session = session
+ self.opcode = opcode
+ self.associd = associd
+ self.extension = data
+ self.sequence = None
+ self.send_call_count = 0
+
+ def send(self):
+ self.send_call_count += 1
+ return self
+
+ def flatten(self):
+ return self.extension
+
+
+class SocketModuleJig:
+ error = socket.error
+ gaierror = socket._socket.gaierror
+ SOCK_DGRAM = socket.SOCK_DGRAM
+ IPPROTO_UDP = socket.IPPROTO_UDP
+ AF_UNSPEC = socket.AF_UNSPEC
+ AI_NUMERICHOST = socket.AI_NUMERICHOST
+ AI_CANONNAME = socket.AI_CANONNAME
+ EAI_NONAME = socket.EAI_NONAME
+ EAI_NODATA = socket.EAI_NODATA
+
+ def __init__(self):
+ self.gai_calls = []
+ self.gai_error_count = 0
+ self.socket_calls = []
+ self.socket_fail = False
+ self.socket_fail_connect = False
+ self.socketsReturned = []
+ self.inet_ntop_calls = []
+
+ def getaddrinfo(self, host, port, family=None, socktype=None,
+ proto=None, flags=None):
+ self.gai_calls.append((host, port, family, socktype, proto, flags))
+ if self.gai_error_count > 0:
+ self.gai_error_count -= 1
+ err = self.gaierror("blah")
+ err.errno = socket.EAI_NONAME
+ raise err
+ return 42
+
+ def socket(self, family, socktype, protocol):
+ self.socket_calls.append((family, socktype, protocol))
+ if self.socket_fail is True:
+ err = self.error()
+ err.strerror = "error!"
+ err.errno = 23
+ raise err
+ sock = SocketJig()
+ if self.socket_fail_connect is True:
+ sock.fail_connect = True
+ self.socketsReturned.append(sock)
+ return sock
+
+ def inet_ntop(self, addr, family):
+ self.inet_ntop_calls.append((addr, family))
+ return "canon.com"
+
+
+class GetpassModuleJig:
+ def __init__(self):
+ self.getpass_calls = []
+
+ def getpass(self, prompt, stream=None):
+ self.getpass_calls.append((prompt, stream))
+ return "xyzzy"
+
+
+class SelectModuleJig:
+ error = select.error
+
+ def __init__(self):
+ self.select_calls = []
+
+ def select(self, ins, outs, excepts, timeout=0):
+ self.select_calls.append((ins, outs, excepts, timeout))
+ return (ins, [], [])
+
+
+class AuthenticatorJig:
+ compute_mac_calls = []
+ def __init__(self):
+ self.control_call_count = 0
+ self.fail_getitem = False
+ self.compute_mac_calls = []
+
+ def __getitem__(self, key):
+ if self.fail_getitem is True:
+ raise IndexError
+ return ("passtype", "pass")
+
+ def control(self):
+ self.control_call_count += 1
+ return (23, "keytype", "miranda")
+
+ @staticmethod
+ def compute_mac(flatpkt, keyid, keytype, passwd):
+ AuthenticatorJig.compute_mac_calls.append((flatpkt, keyid,
+ keytype, passwd))
+ return "mac"
+
+
+class TestControlSession(unittest.TestCase):
+ target = ntp.packet.ControlSession
+ def test___init__(self):
+ # Test
+ cls = self.target()
+ self.assertEqual(cls.debug, 0)
+ self.assertEqual(cls.ai_family, socket.AF_UNSPEC)
+ self.assertEqual(cls.primary_timeout, ntp.packet.DEFTIMEOUT)
+ self.assertEqual(cls.secondary_timeout, ntp.packet.DEFSTIMEOUT)
+ self.assertEqual(cls.pktversion, ntp.magic.NTP_OLDVERSION + 1)
+ self.assertEqual(cls.always_auth, False)
+ self.assertEqual(cls.keytype, "MD5")
+ self.assertEqual(cls.keyid, None)
+ self.assertEqual(cls.passwd, None)
+ self.assertEqual(cls.auth, None)
+ self.assertEqual(cls.hostname, None)
+ self.assertEqual(cls.isnum, False)
+ self.assertEqual(cls.sock, None)
+ self.assertEqual(cls.port, 0)
+ self.assertEqual(cls.sequence, 0)
+ self.assertEqual(cls.response, "")
+ self.assertEqual(cls.rstatus, 0)
+ self.assertEqual(cls.ntpd_row_limit, self.target.MRU_ROW_LIMIT)
+ self.assertEqual(cls.logfp, sys.stdout)
+ self.assertEqual(cls.nonce_xmit, 0)
+
+
+ def test_close(self):
+ # Init
+ sockjig = SocketJig()
+ cls = self.target()
+ cls.sock = sockjig
+ # Test
+ cls.close()
+ self.assertEqual(sockjig.closed, True)
+ self.assertEqual(cls.sock, None)
+
+ def test_havehost(self):
+ # Init
+ cls = self.target()
+ # Test empty
+ self.assertEqual(cls.havehost(), False)
+ # Test full
+ cls.sock = True
+ self.assertEqual(cls.havehost(), True)
+
+ def test___lookuphost(self):
+ logjig = FileJig()
+ try:
+ fakesockmod = SocketModuleJig()
+ ntp.packet.socket = fakesockmod
+ # Init
+ cls = self.target()
+ cls.debug = 3
+ cls.logfp = logjig
+ # Test first type
+ result = cls._ControlSession__lookuphost("blah.com", "family")
+ self.assertEqual(result, 42)
+ self.assertEqual(fakesockmod.gai_calls,
+ [("blah.com", "ntp", cls.ai_family,
+ socket.SOCK_DGRAM, socket.IPPROTO_UDP,
+ socket.AI_NUMERICHOST)])
+ self.assertEqual(logjig.data, [])
+ # Test second type
+ logjig.__init__() # reset
+ fakesockmod.__init__()
+ fakesockmod.gai_error_count = 1
+ result = cls._ControlSession__lookuphost("blah.com", "family")
+ self.assertEqual(result, 42)
+ self.assertEqual(logjig.data,
+ ["ntpq: numeric-mode lookup of blah.com "
+ "failed, None\n"])
+ self.assertEqual(fakesockmod.gai_calls,
+ [("blah.com", "ntp", cls.ai_family,
+ socket.SOCK_DGRAM, socket.IPPROTO_UDP,
+ socket.AI_NUMERICHOST),
+ ("blah.com", "ntp", cls.ai_family,
+ socket.SOCK_DGRAM, socket.IPPROTO_UDP, 0)])
+ # Test third type
+ logjig.__init__() # reset
+ fakesockmod.__init__()
+ fakesockmod.gai_error_count = 2
+ result = cls._ControlSession__lookuphost("blah.com", "family")
+ self.assertEqual(result, 42)
+ self.assertEqual(logjig.data,
+ ["ntpq: numeric-mode lookup of blah.com "
+ "failed, None\n",
+ "ntpq: standard-mode lookup of blah.com "
+ "failed, None\n"])
+ self.assertEqual(fakesockmod.gai_calls,
+ [("blah.com", "ntp", cls.ai_family,
+ socket.SOCK_DGRAM, socket.IPPROTO_UDP,
+ socket.AI_NUMERICHOST),
+ ("blah.com", "ntp", cls.ai_family,
+ socket.SOCK_DGRAM, socket.IPPROTO_UDP, 0),
+ ("blah.com", "ntp", cls.ai_family,
+ socket.SOCK_DGRAM, socket.IPPROTO_UDP, 0)])
+ finally:
+ ntp.packet.socket = socket
+
+ def test_openhost(self):
+ lookups = []
+ returnNothing = True
+ noCanon = False
+ def lookup_jig(hostname, family):
+ lookups.append((hostname, family))
+ if returnNothing is True:
+ return None
+ elif noCanon is True:
+ return [("family", "socktype", "protocol", None,
+ ("1.2.3.4", 80)),]
+ else:
+ return [("family", "socktype", "protocol", "canon",
+ ("1.2.3.4", 80)),]
+ logjig = FileJig()
+ try:
+ fakesockmod = SocketModuleJig()
+ ntp.packet.socket = fakesockmod
+ # Init
+ cls = self.target()
+ cls.debug = 3
+ cls.logfp = logjig
+ cls._ControlSession__lookuphost = lookup_jig
+ # Test, lookup failure
+ result = cls.openhost("foo.org")
+ self.assertEqual(result, False)
+ self.assertEqual(lookups, [("foo.org", socket.AF_UNSPEC)])
+ # Test, with canon, and success
+ returnNothing = False
+ lookups = []
+ result = cls.openhost("foo.org")
+ self.assertEqual(result, True)
+ self.assertEqual(logjig.data, ["Opening host canon\n"])
+ self.assertEqual(lookups, [("foo.org", socket.AF_UNSPEC)])
+ self.assertEqual(fakesockmod.socket_calls,
+ [("family", "socktype", "protocol")])
+ sock = fakesockmod.socketsReturned[0]
+ self.assertEqual(sock.connected, ("1.2.3.4", 80))
+ self.assertEqual(cls.hostname, "canon")
+ self.assertEqual(cls.isnum, False)
+ self.assertEqual(cls.port, 80)
+ self.assertEqual(cls.sock, sock)
+ # Test, without canon, and success
+ noCanon = True
+ lookups = []
+ logjig.__init__()
+ fakesockmod.__init__()
+ cls.sock = None
+ result = cls.openhost("foo.org")
+ self.assertEqual(result, True)
+ self.assertEqual(logjig.data, ["Opening host canon.com\n"])
+ self.assertEqual(lookups, [("foo.org", socket.AF_UNSPEC)])
+ self.assertEqual(fakesockmod.socket_calls,
+ [("family", "socktype", "protocol")])
+ sock = fakesockmod.socketsReturned[0]
+ self.assertEqual(sock.connected, ("1.2.3.4", 80))
+ self.assertEqual(cls.hostname, "canon.com")
+ self.assertEqual(cls.isnum, True)
+ self.assertEqual(cls.port, 80)
+ self.assertEqual(cls.sock, sock)
+ # Test, with canon, and socket creation failure
+ noCanon = False
+ cls.sock = None
+ fakesockmod.socket_fail = True
+ try:
+ result = cls.openhost("foo.org")
+ errored = False
+ except ntp.packet.ControlException as e:
+ errored = e
+ self.assertEqual(errored.message,
+ "Error opening foo.org: error! [23]")
+ # Test, with canon, and socket connection failure
+ fakesockmod.socket_fail = False
+ fakesockmod.socket_fail_connect = True
+ cls.sock = None
+ try:
+ result = cls.openhost("foo.org")
+ errored = False
+ except ntp.packet.ControlException as e:
+ errored = e
+ self.assertEqual(errored.message,
+ "Error connecting to foo.org: socket! [16]")
+ finally:
+ ntp.packet.socket = socket
+
+ def test_password(self):
+ iojig = FileJig()
+ fakegetpmod = GetpassModuleJig()
+ # Init
+ cls = self.target()
+ try:
+ tempauth = ntp.packet.Authenticator()
+ ntp.packet.Authenticator = AuthenticatorJig
+ ntp.packet.getpass = fakegetpmod
+ tempstdin = sys.stdin
+ sys.stdin = iojig
+ tempstdout = sys.stdout
+ sys.stdout = iojig
+ # Test, with nothing
+ iojig.readline_return = "1\n"
+ cls.password()
+ self.assertEqual(isinstance(cls.auth, AuthenticatorJig), True)
+ self.assertEqual(cls.keyid, 1)
+ self.assertEqual(cls.passwd, "pass")
+ # Test, with auth and localhost
+ cls.keyid = None
+ cls.passwd = None
+ cls.hostname = "localhost"
+ cls.password()
+ self.assertEqual(cls.keyid, 23)
+ self.assertEqual(cls.keytype, "keytype")
+ self.assertEqual(cls.passwd, "miranda")
+ # Test, with all but password
+ cls.passwd = None
+ cls.auth.fail_getitem = True
+ cls.password()
+ self.assertEqual(fakegetpmod.getpass_calls,
+ [("keytype Password: ", None)])
+ self.assertEqual(cls.passwd, "xyzzy")
+ finally:
+ ntp.packet.Authenticator = tempauth
+ ntp.packet.getpass = getpass
+ sys.stdin = tempstdin
+ sys.stdout = tempstdout
+
+ def test_sendpkt(self):
+ logjig = FileJig()
+ sockjig = SocketJig()
+
+ # Init
+ cls = self.target()
+ cls.logfp = logjig
+ cls.sock = sockjig
+ cls.debug = 3
+ # Test
+ res = cls.sendpkt("blahfoo")
+ self.assertEqual(res, 0)
+ self.assertEqual(len(logjig.data), 1)
+ self.assertEqual(logjig.data[0], "Sending 8 octets. seq=0\n")
+ self.assertEqual(len(sockjig.data), 1)
+ self.assertEqual(sockjig.data[0], "blahfoo\x00")
+
+ def test_sendrequest(self):
+ logjig = FileJig()
+ try:
+ tempcpkt = ntp.packet.ControlPacket
+ ntp.packet.ControlPacket = ControlPacketJig
+ tempauth = ntp.packet.Authenticator
+ ntp.packet.Authenticator = AuthenticatorJig
+ cls = self.target()
+ cls.logfp = logjig
+ cls.debug = 3
+ # Test oversize data
+ datalen = ntp.control.CTL_MAX_DATA_LEN + 1
+ data = "a" * datalen
+ result = cls.sendrequest(1, 2, data)
+ self.assertEqual(result, -1)
+ self.assertEqual(logjig.data,
+ ["\n",
+ "sendrequest: opcode=1, associd=2, "
+ "qdata=" + data + "\n",
+ "***Internal error! Data too large "
+ "(" + str(len(data)) + ")\n"])
+ # Test no auth
+ result = cls.sendrequest(1, 2, "foo")
+ self.assertEqual(result.sequence, 1)
+ self.assertEqual(result.extension, "foo\x00")
+ # Test with auth
+ cls.keyid = 1
+ cls.passwd = "qwerty"
+ result = cls.sendrequest(1, 2, "foo", True)
+ self.assertEqual(result.sequence, 2)
+ self.assertEqual(result.extension, "foo\x00mac")
+ # Test with auth keyid / password failure
+ cls.keyid = None
+ try:
+ cls.sendrequest(1, 2, "foo", True)
+ errored = False
+ except ntp.packet.ControlException:
+ errored = True
+ self.assertEqual(errored, True)
+ finally:
+ ntp.packet.ControlPacket = tempcpkt
+ ntp.packet.Authenticator = tempauth
+
+ def test_getresponse(self):
+ logjig = FileJig()
+ sockjig = SocketJig()
+ fakeselectmod = SelectModuleJig()
+ # Init
+ cls = self.target()
+ cls.debug = 3
+ cls.logfp = logjig
+ cls.sock = sockjig
+ try:
+ ntp.packet.select = fakeselectmod
+ # Test empty
+ sockjig.return_data = [
+ "\x0E\x81\x00\x00\x00\x03\x00\x02\x00\x00\x00\x00"]
+ cls.getresponse(1, 2, True)
+ self.assertEqual(cls.response, "")
+ # Test with data
+ sockjig.return_data = [
+ "\x0E\xA1\x00\x01\x00\x02\x00\x03\x00\x00\x00\x09"
+ "foo=4223,\x00\x00\x00",
+ "\x0E\xA1\x00\x01\x00\x02\x00\x03\x00\x09\x00\x0E"
+ "blah=248,x=23,\x00\x00",
+ "\x0E\x81\x00\x01\x00\x02\x00\x03\x00\x17\x00\x06"
+ "quux=1\x00\x00"]
+ cls.sequence = 1
+ cls.getresponse(1, 3, True)
+ self.assertEqual(cls.response, "foo=4223,blah=248,x=23,quux=1")
+ finally:
+ ntp.packet.select = select
+
+ def test_doquery(self):
+ sends = []
+ def sendrequest_jig(opcode, associd, qdata, auth):
+ sends.append((opcode, associd, qdata, auth))
+ gets = []
+ doerror = [False]
+ def getresponse_jig(opcode, associd, retry):
+ gets.append((opcode, associd, retry))
+ if doerror[0]:
+ doerror[0] = False
+ raise ntp.packet.ControlException(ntp.packet.SERR_TIMEOUT)
+ return "flax!"
+ # Init
+ cls = self.target()
+ cls.sendrequest = sendrequest_jig
+ cls.getresponse = getresponse_jig
+ cls.sock = True # to fool havehost()
+ # Test no retry
+ result = cls.doquery(1, 2, "blah")
+ self.assertEqual(result, "flax!")
+ self.assertEqual(len(sends), 1)
+ self.assertEqual(sends[0], (1, 2, "blah", False))
+ self.assertEqual(len(gets), 1)
+ self.assertEqual(gets[0], (1, 2, False))
+ # Reset
+ sends = []
+ gets = []
+ doerror[0] = True
+ # Test retry
+ result = cls.doquery(1, 2, "quux")
+ self.assertEqual(result, "flax!")
+ self.assertEqual(len(sends), 2)
+ self.assertEqual(sends, [(1, 2, "quux", False),
+ (1, 2, "quux", False)])
+ self.assertEqual(len(gets), 2)
+ self.assertEqual(gets, [(1, 2, False), (1, 2, True)])
+
+ def test_readstat(self):
+ # Init
+ queries = []
+ def doquery_jig(opcode, associd=0, qdata="", auth=False):
+ queries.append((opcode, associd, qdata, auth))
+ cls = self.target()
+ cls.doquery = doquery_jig
+ # Test empty
+ cls.response = ""
+ idlist = cls.readstat(42)
+ self.assertEqual(idlist, [])
+ self.assertEqual(queries, [(ntp.control.CTL_OP_READSTAT,
+ 42, "", False)])
+ # Test normal
+ queries = []
+ cls.response = "\xDE\xAD\xF0\x0D"
+ idlist = cls.readstat()
+ self.assertEqual(len(idlist), 1)
+ self.assertEqual(isinstance(idlist[0], ntp.packet.Peer), True)
+ self.assertEqual(idlist[0].associd, 0xDEAD)
+ self.assertEqual(idlist[0].status, 0xF00D)
+ self.assertEqual(queries, [(ntp.control.CTL_OP_READSTAT,
+ 0, "", False)])
+ # Test incorrect response
+ cls.response = "foo"
+ try:
+ cls.readstat()
+ errored = False
+ except ntp.packet.ControlException as e:
+ errored = True
+ self.assertEqual(errored, True)
+
+ def test___parse_varlist(self):
+ # Init
+ cls = self.target()
+ cls.response = 'srcadr=0.0.0.0, srcport=0, srchost="0.ubuntu.pool.ntp.org",\r\ndstadr=0.0.0.0, dstport=0, leap=3, stratum=16, precision=-22,\r\nrootdelay=0.000, rootdisp=0.000, refid=POOL,\r\nreftime=0x00000000.00000000, rec=0x00000000.00000000, reach=0x0,\r\nunreach=0, hmode=3, pmode=0, hpoll=6, ppoll=10, headway=0, flash=0x1600,\r\nkeyid=0, offset=0.000, delay=0.000, dispersion=16000.000, jitter=0.000,\r\nfiltdelay= 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00,\r\nfiltoffset= 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00,\r\nfiltdisp= 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00 16000.00\r\n'
+ # Test with basic packet
+ self.assertEqual(cls._ControlSession__parse_varlist(),
+ odict((("srcadr", "0.0.0.0"), ("srcport", 0),
+ ("srchost", "0.ubuntu.pool.ntp.org"),
+ ("dstadr", "0.0.0.0"), ("dstport", 0),
+ ("leap", 3), ("stratum", 16),
+ ("precision", -22), ("rootdelay", 0.0),
+ ("rootdisp", 0.0), ("refid", "POOL"),
+ ("reftime", "0x00000000.00000000"),
+ ("rec", "0x00000000.00000000"),
+ ("reach", 0), ("unreach", 0), ("hmode", 3),
+ ("pmode", 0), ("hpoll", 6), ("ppoll", 10),
+ ("headway", 0), ("flash", 5632), ("keyid", 0),
+ ("offset", 0.0), ("delay-s", "0.000"),
+ ("delay", 0.0), ("dispersion", 16000.0),
+ ("jitter", 0.0),
+ ("filtdelay",
+ "0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00"),
+ ("filtoffset",
+ "0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00"),
+ ("filtdisp",
+ "16000.00 16000.00 16000.00 16000.00 "
+ "16000.00 16000.00 16000.00 16000.00"))))
+ # Test with basic packet, raw mode
+ self.assertEqual(cls._ControlSession__parse_varlist(raw=True),
+ odict((("srcadr", ("0.0.0.0", "0.0.0.0")),
+ ("srcport", (0, "0")),
+ ("srchost", ("0.ubuntu.pool.ntp.org",
+ "0.ubuntu.pool.ntp.org")),
+ ("dstadr", ("0.0.0.0", "0.0.0.0")),
+ ("dstport", (0, "0")),
+ ("leap", (3, "3")), ("stratum", (16, "16")),
+ ("precision", (-22, "-22")),
+ ("rootdelay", (0.0, "0.000")),
+ ("rootdisp", (0.0, "0.000")),
+ ("refid", ("POOL", "POOL")),
+ ("reftime", ("0x00000000.00000000",
+ "0x00000000.00000000")),
+ ("rec", ("0x00000000.00000000",
+ "0x00000000.00000000")),
+ ("reach", (0, "0x0")), ("unreach", (0, "0")),
+ ("hmode", (3, "3")), ("pmode", (0, "0")),
+ ("hpoll", (6, "6")), ("ppoll", (10, "10")),
+ ("headway", (0, "0")),
+ ("flash", (5632, "0x1600")),
+ ("keyid", (0, "0")),
+ ("offset", (0.0, "0.000")),
+ ("delay", (0.0, "0.000")),
+ ("dispersion", (16000.0, "16000.000")),
+ ("jitter", (0.0, "0.000")),
+ ("filtdelay",
+ ("0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00",
+ "0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00")),
+ ("filtoffset",
+ ("0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00",
+ "0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00")),
+ ("filtdisp",
+ ("16000.00 16000.00 16000.00 16000.00 "
+ "16000.00 16000.00 16000.00 16000.00",
+ "16000.00 16000.00 16000.00 16000.00 "
+ "16000.00 16000.00 16000.00 16000.00")))))
+
+ def test_readvar(self):
+ queries = []
+ def doquery_jig(opcode, associd=0, qdata="", auth=False):
+ queries.append((opcode, associd, qdata, auth))
+ # Init
+ cls = self.target()
+ cls.doquery = doquery_jig
+ cls.response = "foo=bar, murphy=42"
+ # Test basic
+ result = cls.readvar()
+ self.assertEqual(result, odict((("foo", "bar"), ("murphy", 42))))
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_READVAR, 0, "", False)])
+ # Test raw
+ queries = []
+ result = cls.readvar(raw=True)
+ self.assertEqual(result, odict((("foo", ("bar", "bar")),
+ ("murphy", (42, "42")))))
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_READVAR, 0, "", False)])
+ # Test with varlist
+ queries = []
+ result = cls.readvar(varlist=("foo", "bar", "quux"))
+ self.assertEqual(result, odict((("foo", "bar"), ("murphy", 42))))
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_READVAR,
+ 0, "foo,bar,quux", False)])
+
+ def test_config(self):
+ queries = []
+ def doquery_jig(opcode, associd=0, qdata="", auth=False):
+ queries.append((opcode, associd, qdata, auth))
+ # Init
+ cls = self.target()
+ cls.doquery = doquery_jig
+ cls.response = "Config Succeeded \n \x00 blah blah"
+ # Test success
+ result = cls.config("Boo!")
+ self.assertEqual(result, True)
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_CONFIGURE, 0, "Boo!", True)])
+ # Test failure
+ queries = []
+ cls.response = "whatever man..."
+ result = cls.config("Boo!")
+ self.assertEqual(result, False)
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_CONFIGURE, 0, "Boo!", True)])
+
+ def test_fetch_nonce(self):
+ queries = []
+ def doquery_jig(opcode, associd=0, qdata="", auth=False):
+ queries.append((opcode, associd, qdata, auth))
+ # Init
+ filefp = FileJig()
+ cls = self.target()
+ cls.doquery = doquery_jig
+ # Test success
+ cls.response = "nonce=blah blah "
+ result = cls.fetch_nonce()
+ self.assertEqual(result, "nonce=blah blah")
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_REQ_NONCE, 0, "", False)])
+ # Test failure
+ queries = []
+ cls.logfp = filefp
+ cls.response = "blah blah"
+ try:
+ result = cls.fetch_nonce()
+ errored = False
+ except ntp.packet.ControlException:
+ errored = True
+ self.assertEqual(errored, True)
+ self.assertEqual(filefp.data, ["## Nonce expected: blah blah"])
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_REQ_NONCE, 0, "", False),
+ (ntp.control.CTL_OP_REQ_NONCE, 0, "", False),
+ (ntp.control.CTL_OP_REQ_NONCE, 0, "", False),
+ (ntp.control.CTL_OP_REQ_NONCE, 0, "", False)])
+
+ def test_mrulist(self):
+ def setresponse(data): # needed for doquery_jig
+ cls.response = data
+ nonce_fetch_count = [0]
+ def fetch_nonce_jig():
+ nonce_fetch_count[0] += 1
+ return "nonce=foo"
+ queries = []
+ qrm = ["addr.1=1.2.3.4:23,last.1=40,first.1=23,ct.1=1,mv.1=2,rs.1=3",
+ "addr.2=1.2.3.4:23,last.2=41,first.2=23,ct.2=1,mv.2=2,rs.2=3",
+ "addr.1=10.20.30.40:23,last.1=42,first.1=23,ct.1=1,mv.1=2,rs.1=3",
+ "now=0x00000000.00000000"]
+ query_results = qrm[:] # qrm == query results master
+ def doquery_jig(opcode, associd=0, qdata="", auth=False):
+ queries.append((opcode, associd, qdata, auth))
+ if len(query_results) > 0:
+ setresponse(query_results.pop(0))
+ logjig = FileJig()
+ # Init
+ cls = self.target()
+ cls.fetch_nonce = fetch_nonce_jig
+ cls.doquery = doquery_jig
+ cls.logfp = logjig
+ # Test empty varlist
+ result = cls.mrulist()
+ self.assertEqual(nonce_fetch_count, [4])
+ self.assertEqual(queries,
+ [(10, 0, "nonce=foo, frags=32", False),
+ (10, 0,
+ "nonce=foo, frags=32, addr.0=1.2.3.4:23, last.0=40",
+ False),
+ (10, 0,
+ "nonce=foo, frags=32, addr.0=1.2.3.4:23, last.0=41, "
+ "addr.1=1.2.3.4:23, last.1=40", False),
+ (10, 0,
+ "nonce=foo, frags=32, addr.0=10.20.30.40:23, "
+ "last.0=42, addr.1=1.2.3.4:23, last.1=41, "
+ "addr.2=1.2.3.4:23, last.2=40", False)])
+ self.assertEqual(isinstance(result, ntp.packet.MRUList), True)
+ self.assertEqual(len(result.entries), 2)
+ mru = result.entries[0]
+ self.assertEqual(mru.addr, "1.2.3.4:23")
+ self.assertEqual(mru.last, 41)
+ self.assertEqual(mru.first, 23)
+ self.assertEqual(mru.ct, 1)
+ self.assertEqual(mru.mv, 2)
+ self.assertEqual(mru.rs, 3)
+ mru = result.entries[1]
+ self.assertEqual(mru.addr, "10.20.30.40:23")
+ self.assertEqual(mru.last, 42)
+ self.assertEqual(mru.first, 23)
+ self.assertEqual(mru.ct, 1)
+ self.assertEqual(mru.mv, 2)
+ self.assertEqual(mru.rs, 3)
+ # Test with sort and frags (also test frag increment)
+ nonce_fetch_count = [0]
+ query_results = qrm[:]
+ queries = []
+ result = cls.mrulist(variables={"sort":"addr", "frags":24})
+ self.assertEqual(nonce_fetch_count, [4])
+ self.assertEqual(queries,
+ [(10, 0, "nonce=foo, frags=24", False),
+ (10, 0,
+ "nonce=foo, frags=25, addr.0=1.2.3.4:23, last.0=40",
+ False),
+ (10, 0,
+ "nonce=foo, frags=26, addr.0=1.2.3.4:23, last.0=41, "
+ "addr.1=1.2.3.4:23, last.1=40", False),
+ (10, 0,
+ "nonce=foo, frags=27, addr.0=10.20.30.40:23, "
+ "last.0=42, addr.1=1.2.3.4:23, last.1=41, "
+ "addr.2=1.2.3.4:23, last.2=40", False)])
+ self.assertEqual(isinstance(result, ntp.packet.MRUList), True)
+ self.assertEqual(len(result.entries), 2)
+ mru = result.entries[0]
+ self.assertEqual(mru.addr, "10.20.30.40:23")
+ self.assertEqual(mru.last, 42)
+ self.assertEqual(mru.first, 23)
+ self.assertEqual(mru.ct, 1)
+ self.assertEqual(mru.mv, 2)
+ self.assertEqual(mru.rs, 3)
+ mru = result.entries[1]
+ self.assertEqual(mru.addr, "1.2.3.4:23")
+ self.assertEqual(mru.last, 41)
+ self.assertEqual(mru.first, 23)
+ self.assertEqual(mru.ct, 1)
+ self.assertEqual(mru.mv, 2)
+ self.assertEqual(mru.rs, 3)
+
+ def test___ordlist(self):
+ queries = []
+ def doquery_jig(opcode, associd=0, qdata="", auth=False):
+ queries.append((opcode, associd, qdata, auth))
+ # Init
+ cls = self.target()
+ cls.doquery = doquery_jig
+ # Test
+ cls.response = "foo.0=42, bar.2=songbird"
+ result = cls._ControlSession__ordlist("blah")
+ self.assertEqual(result, [odict((("foo", 42),)), odict(),
+ odict((("bar", "songbird"),))])
+ self.assertEqual(queries,
+ [(ntp.control.CTL_OP_READ_ORDLIST_A,
+ 0, "blah", True)])
+
+ def test_reslist(self):
+ ords = []
+ def ordlist_jig(listtype):
+ ords.append(listtype)
+ return 23
+ # Init
+ cls = self.target()
+ cls._ControlSession__ordlist = ordlist_jig
+ # Test
+ result = cls.reslist()
+ self.assertEqual(result, 23)
+ self.assertEqual(ords, ["addr_restrictions"])
+
+ def test_ifstats(self):
+ ords = []
+ def ordlist_jig(listtype):
+ ords.append(listtype)
+ return 23
+ # Init
+ cls = self.target()
+ cls._ControlSession__ordlist = ordlist_jig
+ # Test
+ result = cls.ifstats()
+ self.assertEqual(result, 23)
+ self.assertEqual(ords, ["ifstats"])
+
+if __name__ == "__main__":
+ unittest.main()
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/64fdd108bfe7896c54ad7c1d31703876f3596979...8d2c65a9d34231252ceb24f62dc038a7eddb268b
---
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/64fdd108bfe7896c54ad7c1d31703876f3596979...8d2c65a9d34231252ceb24f62dc038a7eddb268b
You're receiving this email because of your account on gitlab.com.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20170817/90638ae6/attachment.html>
More information about the vc
mailing list