Scrivite un client NTP simplice

Ciao abbitanti. Oghje vogliu parlà di cumu scrive u vostru propiu cliente NTP simplice. In fondu, a cunversazione hà da vultà à a struttura di u pacchettu è cumu si tratta a risposta da u servitore NTP. U codice serà scrittu in python, perchè, in my opinion, ùn ci hè simplicemente micca una lingua megliu per tali cose. Connoisseurs attente à a similitudine di u codice cù u codice ntplib - Eru "inspiratu" da questu.

Allora chì hè NTP in ogni casu? NTP hè un protokollu per cumunicà cù i servitori di u tempu. Stu protokollu hè utilizatu in parechje macchine muderni. Per esempiu, u serviziu w32tm in Windows.

Ci hè 5 versioni di u protocolu NTP in totale. A prima, a versione 0 (1985, RFC958) hè attualmente cunsiderata obsoleta. I più novi sò attualmente usati, 1u (1988, RFC1059), 2u (1989, RFC1119), 3u (1992, RFC1305) è 4u (1996, RFC2030). E versioni 1-4 sò cumpatibili cù l'altri, sò diffirenti solu in l'algoritmi di i servitori.

Format di pacchettu

Scrivite un client NTP simplice

Indicatore di saltu (indicatore di currezzione) hè un numeru chì indica l'avvertimentu di a seconda salta. Sensu:

  • 0 - senza correzione
  • 1 - l'ultimu minutu di u ghjornu cuntene 61 seconde
  • 2 - l'ultimu minutu di u ghjornu cuntene 59 seconde
  • 3 - fallimentu di u servitore (tempu fora di sincronia)

Versione in numeru (numeru di versione) - numeru di versione di u protocolu NTP (1-4).

moda (mode) - modu di funziunamentu di u mittente di u pacchettu. Valore da 0 à 7, u più cumuni:

  • 3 - cliente
  • 4 - servore
  • 5 - modu di trasmissione

stratu (livellu di stratificazione) - u numeru di strati intermedi trà u servitore è u clock di riferimentu (1 - u servitore piglia dati direttamente da u clock di riferimentu, 2 - u servitore piglia dati da u servitore cù u nivellu 1, etc.).
piscine hè un interu signatu chì rapprisenta l'intervallu massimu trà missaghji consecutivi. U cliente NTP specifica quì l'intervallu à quale s'aspitta à pollu u servitore, è u servitore NTP specifica l'intervallu à quale s'aspitta à esse polled. U valore hè uguali à u logaritmu binariu di seconde.
pricisioni (precisione) hè un interu signatu chì rapprisenta a precisione di u clock di u sistema. U valore hè uguali à u logaritmu binariu di seconde.
ritardu di radica (latenza di u servitore) hè u tempu chì ci vole à u clock per ghjunghje à u servitore NTP, cum'è un numeru di punti fissu di seconde.
dispersione di a radica (server scatter) - A scatter di u clock di u servitore NTP cum'è un numeru di punti fissu di seconde.
Ref id (id surghjente) - watch id. Se u servitore hà u stratu 1, allora ref id hè u nome di l'orologio atomicu (4 caratteri ASCII). Se u servitore usa un altru servitore, allora u ref id cuntene l'indirizzu di stu servitore.
L'ultimi 4 campi sò u tempu - 32 bits - a parte intera, 32 bits - a parte fraccionaria.
didáctica - l'ultimu clock in u servitore.
Urigine - tempu quandu u pacchettu hè statu mandatu (pienu da u servitore - più nantu à quì sottu).
Richiesta - tempu quandu u pacchettu hè statu ricevutu da u servitore.
trasmèttala - tempu quandu u pacchettu hè statu mandatu da u servitore à u cliente (pienu da u cliente, più nantu à quì sottu).

L'ultimi dui campi ùn saranu micca cunsiderati.

Scrivemu u nostru pacchettu:

Codice di pacchettu

class NTPPacket:
    _FORMAT = "!B B b b 11I"

    def __init__(self, version_number=2, mode=3, transmit=0):
        # Necessary of enter leap second (2 bits)
        self.leap_indicator = 0
        # Version of protocol (3 bits)
        self.version_number = version_number
        # Mode of sender (3 bits)
        self.mode = mode
        # The level of "layering" reading time (1 byte)
        self.stratum = 0
        # Interval between requests (1 byte)
        self.pool = 0
        # Precision (log2) (1 byte)
        self.precision = 0
        # Interval for the clock reach NTP server (4 bytes)
        self.root_delay = 0
        # Scatter the clock NTP-server (4 bytes)
        self.root_dispersion = 0
        # Indicator of clocks (4 bytes)
        self.ref_id = 0
        # Last update time on server (8 bytes)
        self.reference = 0
        # Time of sending packet from local machine (8 bytes)
        self.originate = 0
        # Time of receipt on server (8 bytes)
        self.receive = 0
        # Time of sending answer from server (8 bytes)
        self.transmit = transmit

Per mandà (è riceve) un pacchettu à u servitore, duvemu esse capace di trasfurmà in un array di bytes.
Per questa operazione (è inversa), scriveremu duie funzioni - pack () è unpack ():

funzione di pacchettu

def pack(self):
        return struct.pack(NTPPacket._FORMAT,
                (self.leap_indicator << 6) + 
                    (self.version_number << 3) + self.mode,
                self.stratum,
                self.pool,
                self.precision,
                int(self.root_delay) + get_fraction(self.root_delay, 16),
                int(self.root_dispersion) + 
                    get_fraction(self.root_dispersion, 16),
                self.ref_id,
                int(self.reference),
                get_fraction(self.reference, 32),
                int(self.originate),
                get_fraction(self.originate, 32),
                int(self.receive),
                get_fraction(self.receive, 32),
                int(self.transmit),
                get_fraction(self.transmit, 32))

funzione unpack

def unpack(self, data: bytes):
        unpacked_data = struct.unpack(NTPPacket._FORMAT, data)

        self.leap_indicator = unpacked_data[0] >> 6  # 2 bits
        self.version_number = unpacked_data[0] >> 3 & 0b111  # 3 bits
        self.mode = unpacked_data[0] & 0b111  # 3 bits

        self.stratum = unpacked_data[1]  # 1 byte
        self.pool = unpacked_data[2]  # 1 byte
        self.precision = unpacked_data[3]  # 1 byte

        # 2 bytes | 2 bytes
        self.root_delay = (unpacked_data[4] >> 16) +
            (unpacked_data[4] & 0xFFFF) / 2 ** 16
         # 2 bytes | 2 bytes
        self.root_dispersion = (unpacked_data[5] >> 16) +
            (unpacked_data[5] & 0xFFFF) / 2 ** 16 

        # 4 bytes
        self.ref_id = str((unpacked_data[6] >> 24) & 0xFF) + " " + 
                      str((unpacked_data[6] >> 16) & 0xFF) + " " +  
                      str((unpacked_data[6] >> 8) & 0xFF) + " " +  
                      str(unpacked_data[6] & 0xFF)

        self.reference = unpacked_data[7] + unpacked_data[8] / 2 ** 32  # 8 bytes
        self.originate = unpacked_data[9] + unpacked_data[10] / 2 ** 32  # 8 bytes
        self.receive = unpacked_data[11] + unpacked_data[12] / 2 ** 32  # 8 bytes
        self.transmit = unpacked_data[13] + unpacked_data[14] / 2 ** 32  # 8 bytes

        return self

Per i pigri, cum'è una applicazione - codice chì trasforma u pacchettu in una bella stringa

def to_display(self):
        return "Leap indicator: {0.leap_indicator}n" 
                "Version number: {0.version_number}n" 
                "Mode: {0.mode}n" 
                "Stratum: {0.stratum}n" 
                "Pool: {0.pool}n" 
                "Precision: {0.precision}n" 
                "Root delay: {0.root_delay}n" 
                "Root dispersion: {0.root_dispersion}n" 
                "Ref id: {0.ref_id}n" 
                "Reference: {0.reference}n" 
                "Originate: {0.originate}n" 
                "Receive: {0.receive}n" 
                "Transmit: {0.transmit}"
                .format(self)

Mandendu un pacchettu à u servitore

Mandate un pacchettu cù campi pienu à u servitore Version, moda и trasmèttala. L' trasmèttala duvete specificà l'ora attuale nantu à a macchina lucale (numaru di seconde da u 1 di ghjennaghju di u 1900), versione - qualsiasi di 1-4, modu - 3 (modu client).

U servitore, avè ricevutu a dumanda, riempie tutti i campi in u pacchettu NTP, cupiendu in u campu Urigine valore da trasmèttala, chì hè ghjuntu in a dumanda. Hè un misteru per mè perchè u cliente ùn pò micca cumpiendu immediatamente u valore di u so tempu in u campu Urigine. In u risultatu, quandu u pacchettu torna, u cliente hà 4 valori di tempu - u tempu chì a dumanda hè stata mandata (Urigine), l'ora chì u servitore hà ricevutu a dumanda (Richiesta), u tempu chì u servitore hà mandatu a risposta (trasmèttala) è u tempu di ricezione di una risposta da u cliente - ghjunghja (micca in u pacchettu). Cù questi valori pudemu stabilisce u tempu currettu.

Pacchettu invià è riceve codice

# Time difference between 1970 and 1900, seconds
FORMAT_DIFF = (datetime.date(1970, 1, 1) - datetime.date(1900, 1, 1)).days * 24 * 3600
# Waiting time for recv (seconds)
WAITING_TIME = 5
server = "pool.ntp.org"
port = 123
    
packet = NTPPacket(version_number=2, mode=3, transmit=time.time() + FORMAT_DIFF)
answer = NTPPacket()
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.settimeout(WAITING_TIME)
    s.sendto(packet.pack(), (server, port))
    data = s.recv(48)
    arrive_time = time.time() + FORMAT_DIFF
    answer.unpack(data)

Trattamentu di dati da u servitore

U prucessu di dati da u servitore hè simile à l'azzioni di u signore inglese da u vechju prublema di Raymond M. Smallian (1978): "Una persona ùn hà micca un orologio, ma ci era un clock di muru precisu in casa, chì ellu hà. certe volte si scurdava di ventu. Un ghjornu, scurdatu di ripiglià u clock, andò à visità u so amicu, passò a sera cun ellu, è, tornatu in casa, hà sappiutu à mette u clock currettamente. Cumu hà sappiutu di fà questu se u tempu di viaghju ùn era micca cunnisciutu in anticipu? A risposta hè: "Lassendu a casa, una persona s'arricorda di u clock è ricorda a pusizione di e mani. Venendu à un amicu è abbandunà l'invitati, nota l'ora di a so ghjunta è a partenza. Questu li permette di sapè quantu tempu era luntanu. Riturnendu in casa è fighjendu u clock, una persona determina a durata di a so assenza. Sustrattu da questu tempu u tempu chì hà passatu à visità, a persona scopre u tempu passatu nantu à a strada è torna. Aghjunghjendu a mità di u tempu passatu nantu à a strada à u tempu di abbandunà l'invitati, hà l'uppurtunità di scopre l'ora di l'arrivu in casa è aghjustà e mani di u so clock in cunseguenza.

Truvate u tempu chì u servitore hà travagliatu nantu à a dumanda:

  1. Truvà u tempu di viaghju di u pacchettu da u cliente à u servitore: ((Arrivu - Originà) - (Trasmetti - Riceve)) / 2
  2. Truvate a diffarenza trà u tempu di u cliente è u servitore:
    Riceve - Originate - ((Arriva - Originate) - (Trasmetti - Riceve)) / 2 =
    2 * Riceve - 2 * Originate - Arrivate + Originate + Trasmettite - Riceve =
    Receive - Originate - Arrive + Transmit

Aghjunghjemu u valore ricivutu à l'ora locale è gode di a vita.

Output di u risultatu

time_different = answer.get_time_different(arrive_time)
result = "Time difference: {}nServer time: {}n{}".format(
    time_different,
    datetime.datetime.fromtimestamp(time.time() + time_different).strftime("%c"),
    answer.to_display())
print(result)

Utile ссылка.

Source: www.habr.com

Add a comment