Request for code & logic review

Eric S. Raymond esr at thyrsus.com
Thu Nov 24 15:04:28 UTC 2016


The Python port of ntpdig is almost ready to land.  But there is one last
little bit of it I'm not sure I understand correctly.  I'm requesting review
of my code and assumptions.

Presently the adjustment and synch distance are calculated this way:

    def delta(self):
        return self.root_delay
    def epsilon(self):
        return self.root_dispersion
    def synchd(self):
        "Synchronization distance, estimates worst-case error in seconds"
        # This is "lambda" in NTP-speak, but that's a Python keyword 
        return abs(self.delta() - self.epsilon())
    def adjust(self):
        "Adjustment implied by this packet."
        return self.received - self.transmit_timestamp

where self.received is the receipt time of the response packet.  I do
it this way for no better reason than that some examples of Python SNTP
clients I found on the web use this formula.  But the C ntpdig code
suggests this is wrong.

Here's the C for the offset/synch-distance calculation:

--------------------------------------------------------------------------------
	/* Convert timestamps from network to host byte order */
	p_rdly = NTOHS_FP(rpkt->rootdelay);
	p_rdsp = NTOHS_FP(rpkt->rootdisp);
	NTOHL_FP(&rpkt->reftime, &p_ref);
	NTOHL_FP(&rpkt->org, &p_org);
	NTOHL_FP(&rpkt->rec, &p_rec);
	NTOHL_FP(&rpkt->xmt, &p_xmt);

	*precision = LOGTOD(rpkt->precision);

	TRACE(3, ("offset_calculation: LOGTOD(rpkt->precision): %f\n", *precision));

	/* Compute offset etc. */
	tmp = p_rec;
	L_SUB(&tmp, &p_org);
	LFPTOD(&tmp, t21);
	TVTOTS(tv_dst, &dst);
	dst.l_ui += JAN_1970;
	tmp = p_xmt;
	L_SUB(&tmp, &dst);
	LFPTOD(&tmp, t34);
	*offset = (t21 + t34) / 2.;
	delta = t21 - t34;

	// synch_distance is:
	// (peer->delay + peer->rootdelay) / 2 + peer->disp
	// + peer->rootdisp + clock_phi * (current_time - peer->update)
	// + peer->jitter;
	//
	// and peer->delay = fabs(peer->offset - p_offset) * 2;
	// and peer->offset needs history, so we're left with
	// p_offset = (t21 + t34) / 2.;
	// peer->disp = 0; (we have no history to augment this)
	// clock_phi = 15e-6; 
	// peer->jitter = LOGTOD(sys_precision); (we have no history to augment this)
	// and ntp_proto.c:set_sys_tick_precision() should get us sys_precision.
	//
	// so our answer seems to be:
	//
	// (fabs(t21 + t34) + peer->rootdelay) / 3.
	// + 0 (peer->disp)
	// + peer->rootdisp
	// + 15e-6 (clock_phi)
	// + LOGTOD(sys_precision)

	INSIST( FPTOD(p_rdly) >= 0. );
#if 1
	*synch_distance = (fabs(t21 + t34) + FPTOD(p_rdly)) / 3.
		+ 0.
		+ FPTOD(p_rdsp)
		+ 15e-6
		+ 0.	/* LOGTOD(sys_precision) when we can get it */
		;
	INSIST( *synch_distance >= 0. );
#else
	*synch_distance = (FPTOD(p_rdly) + FPTOD(p_rdsp))/2.0;
#endif
--------------------------------------------------------------------------------

That is rather nasty and in my opinion sufficient reason to shoot the C
version through the head.  Now I'm going to simplify it into pseudocode,
ignoring issues about endianness and timestamp epochs.

--------------------------------------------------------------------------------
	precision = log2(rpkt->precision);

	/* Compute offset etc. */
	t21 = rpkt->rec - rpkt->org
	t34 = now - rpkt->xmt
	offset = (t21 + t34) / 2.;

#if 1
	synch_distance = (fabs(t21 + t34) + rpkt->rdly) / 3.
		+ 0.
		+ rpkt->rdsp
		+ 15e-6
		+ 0.	/* LOGTOD(sys_precision) when we can get it */
		;
#else
	synch_distance = (pkt->rdly + pkt->p_rdsp)/2.0;
#endif
--------------------------------------------------------------------------------

I've omitted the delta calculation because it's only used in a diagnostic;
offset is what is applied to the local clock.

Note that the synch distance formula that is conditioned out is the one
I'm using.  Somebody must have thought it was correct at one time.  But if
I'm to believe the C above, the Python should read thus:

    def synchd(self):
        "Synchronization distance, estimates worst-case error in seconds"
        (self.receive_timestamp - self.origin_timestamp) \
		+ (now - self.transmit_timestamp) + self.rootdelay) / 3 \
		+ self.root_dispersion + 15e-6
    def adjust(self):
        "Adjustment implied by this packet."
        return ((self.receive_timestamp - self.origin_timestamp)
		+ (now - self.transmit_timestamp)) / 2

My requests to Daniel and anyone else who might understand these formulas:

(1) Check my logic and C translation.  A bug here would be bad.

(2) What is the authority for these formulas?  What RFC chapter and
    verse can we cite?
-- 
		<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>

"Among the many misdeeds of British rule in India, history will look
upon the Act depriving a whole nation of arms as the blackest."
        -- Mohandas Gandhi, An Autobiography, pg 446


More information about the devel mailing list