viernes, enero 28

Python NTP client

Después de buscar un poco como funcionaba el protocolo NTP (Network Time Protocol), no encontré una implementación que fuera simple de entender, así que decidí hacerme una propia. Necesitaba entenderla para implementarla en un "Single board computer" (un microcontrolador con periféricos) Encontré una implementación básica en ActiveState, pero no me respondía a que correspondía cada campo del paquete.

Continuando con la búsqueda, encontré la implementación de OpenBSD de su cliente NTP y en el archivo ntp.h hay una buena descripción de que es cada campo. También ayudaron algunas páginas como ésta y ésta.

Finalmente, llegué a una implementación, simple pero a la vez. La dejo a continuación.


#!/usr/bin/python
# NTP client class for python, based on OpenBSD's ntp C code and ActiveState code recipe 117211
# v 0.1 (2011-01-28) Initial release
# Features:
# Conversion from/to NTP timestamp
# Ability to query NTP servers
# Calculation of delay and offset

import socket
import struct
import time
TIME1970 = 2208988800L # seconds from 1900

def NTPtimestamp2time(ts):
# print '%016X' % ts
# print '%016X' % (ts >>32)
ia = (ts >> 32) - TIME1970
fa = ts & 0x0000FFFF
return float(ia)+float(fa)/2**32
def time2NTPtimestamp(time):
ia = int(time)
fa = int((time-ia)*2**32)
return (ia<<32 | fa)+(TIME1970<<32)

class ntpclient:
def __init__(self):
self.sockt = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
self.server = None
self.port = 123
self.data = '\x1b' + 47* '\0'
#packet fields
self.leapIndicator = None
self.versionNumber = None
self.mode = None
self.stratum = None
self.pollInterval = None
self.precision = None
self.rootDelay = None
self.rootDispersion = None
self.referenceIdentifier = None
self.referenceTimestamp = None
self.originateTimestamp = None
self.recieveTimestamp = None
self.transmitTimestamp = None
self.destinationTimestamp = None
self.keyIdentifier = None
self.messageDigest = None
self.keyAlgorithmIdentifier = None
self.messageHash = None
def poll(self,server=None):
if server == None:
return
self.server = server
self.sockt.sendto(self.makeReqPackt(),(self.server,self.port))
data,address = self.sockt.recvfrom(1024)
self.destinationTimestamp = time2NTPtimestamp(time.time())
# print len(data)
if data and len(data) == 60:
smallfields, \
self.stratum, \
self.pollInterval, \
self.precision, \
self.rootDelay, \
self.rootDispersion, \
self.referenceIdentifier, \
self.referenceTimestamp, \
self.originateTimestamp, \
self.recieveTimestamp, \
self.transmitTimestamp, \
self.keyIdentifier, \
self.messageDigest, \
self.keyAlgorithmIdentifier, \
self.messageHash = \
struct.unpack('!BBBBIIIQQQQI8s',data)

self.leapIndicator = smallfields & 0b11000000 >> 6
self.versionNumber = smallfields & 0b00111000 >> 3
self.mode = smallfields & 0b00000111

if data and len(data) == 48:
smallfields, \
self.stratum, \
self.pollInterval, \
self.precision, \
self.rootDelay, \
self.rootDispersion, \
self.referenceIdentifier, \
self.referenceTimestamp, \
self.originateTimestamp, \
self.recieveTimestamp, \
self.transmitTimestamp = \
struct.unpack('!BBBBIIIQQQQ',data)

self.leapIndicator = smallfields & 0b11000000 >> 6
self.versionNumber = smallfields & 0b00111000 >> 3
self.mode = smallfields & 0b00000111
def toCtime(mytime):
return time.ctime((mytime>>32)-TIME1970)

def offset(self):
# t1 = self.referenceTimestamp
# t2 = self.originateTimestamp
# t3 = self.recieveTimestamp
# t4 = self.transmitTimestamp

t1 = NTPtimestamp2time(self.originateTimestamp)
t2 = NTPtimestamp2time(self.recieveTimestamp)
t3 = NTPtimestamp2time(self.transmitTimestamp)
t4 = NTPtimestamp2time(self.destinationTimestamp)

return ((t2-t1)+(t3-t4))/2

def delay(self):

t1 = NTPtimestamp2time(self.originateTimestamp)
t2 = NTPtimestamp2time(self.recieveTimestamp)
t3 = NTPtimestamp2time(self.transmitTimestamp)
t4 = NTPtimestamp2time(self.destinationTimestamp)

return ((t4-t1)-(t2-t3))

def makeReqPackt(self):
leapind = 0b00
version = 0b100 # NTP V4
mode = 0b011 # client mode

# a = time.time()
# ia = int(a)
# fa = int((a-ia)*2**32)
# tt = (ia<<32 | fa)+(TIME1970<<32)
tt = time2NTPtimestamp(time.time())
#print '%16X' % TIME1970
#print '%16X' % tt

return struct.pack('!BBBBIIIQQQQ',
leapind <<6 | version <<3 | mode,
0, #unspecified stratum
0, #poll interval
0, # local clock precision
0, # root delay
0, # root dispersion
0, # reference clock identifier
0, #reference timestamp
0, #originating timestamp
0, #recieve timestamp
tt #transmit timestamp
)

if __name__ == '__main__':
from sys import argv
c = ntpclient()
# print repr(c.makeReqPackt())
# c.poll('time.tigo.cl')
c.poll(argv[1])
# print c.versionNumber
# print c.stratum
# print '%016X'%(c.referenceTimestamp)
# print '%016X'%(c.originateTimestamp)
# print '%016X'%(c.recieveTimestamp)
# print '%016X'%(c.transmitTimestamp)
# print '%016X'%(c.destinationTimestamp)
# print c.delay()>>32, c.offset()>>32
print c.delay(), c.offset()


Espero que le sirva a alguien.