Kirjutame lihtsa NTP kliendi

Tere, Habr kasutajad. TĂ€na tahan rÀÀkida, kuidas kirjutada oma lihtne NTP klient. Peamiselt rÀÀgime paketi struktuurist ja vastuse töötlemise viisist NTP serverilt. Kood kirjutatakse Pythonis, sest minu arvates ei leia selliste asjade jaoks paremat keelt. Teadjad panevad tĂ€hele, et kood sarnaneb ntplib koodiga — ma olen just sellest "inspireeritud".

Nii, mis asi on NTP? NTP on protokoll, mis suhtleb tÀpsusaja serveritega. Seda protokolli kasutatakse paljudes kaasaegsetes masinates. NÀiteks, teenus w32tm Windowsis.

Kokku on olemas 5 NTP protokolli versiooni. Esimene, 0. versioon (1985, RFC958), loetakse praegu aegunuks. Praegu kasutatakse uuemaid versioone: 1. (1988, RFC1059), 2. (1989, RFC1119), 3. (1992, RFC1305) ja 4. (1996, RFC2030). Versioonid 1-4 on omavahel ĂŒhilduvad, need erinevad vaid tööalgoritmide poolest. serverid.

Paketi formaat

Kirjutame lihtsa NTP kliendi

Leap indicator (korrektsiooni indikaator) — number, mis nĂ€itab koordineeritud tunni hoiatust. VÀÀrtus:

  • 0 – korrektsiooni ei ole
  • 1 – pĂ€eva viimane minut sisaldab 61 sekundit
  • 2 – pĂ€eva viimane minut sisaldab 59 sekundit
  • 3 – serveri rike (aeg ei ole sĂŒnkroniseeritud)

Version number (versiooninumber) – NTP protokolli versiooninumber (1-4).

Mode (reĆŸiim) — paketi saatja tööreĆŸiim. VÀÀrtused 0 kuni 7, kĂ”ige sagedasemad:

  • 3 – klient
  • 4 – server
  • 5 – laialdane reĆŸiim

Stratum (kihistumise tase) – kihi vahemaa arv serveri ja viidatud kellade vahel (1 – server vĂ”tab andmeid vahetult viidatud kelladelt, 2 – server vĂ”tab andmeid 1. taseme serverilt jne).
Pool — mĂ€rgitud tĂ€isarv, mis esindab maksimaalset intervalli jĂ€rjestikuste sĂ”numite vahel. NTP klient mÀÀrab siin intervalli, millest ta eeldab serveri kĂŒsitlemist, ning NTP server mÀÀrab intervalli, milles ta eeldab, et teda kĂŒsitakse. VÀÀrtus on vĂ”rdne sekundite binaarse logaritmi vÀÀrtusega.
Precision (tĂ€psus) — mĂ€rgitud tĂ€isarv, mis esindab sĂŒsteemi kellade tĂ€psust. VÀÀrtus on sekundite binaarse logaritmi vÀÀrtus.
Root delay (serveri viivitus) – aeg, mille jooksul kellade nĂ€idud jĂ”uavad NTP serverisse, fixed-point sekundi arvuna.
Root dispersion (serveri nĂ€itude hajumine) — NTP serveri kellade nĂ€itude hajumine fixed-point sekundi arvuna.
Ref id (allika identifikaator) – id kella. Kui serveril on stratum 1, siis ref id – aatomkellade nimi (4 ASCII mĂ€rki). Kui server kasutab teist serverit, siis ref id-s on selles serveris asukoht.
Viimased 4 vĂ€ljad tĂ€hendavad aega – 32 bitti – tĂ€isarv, 32 bitti – murdarv.
Viitega — serveri viimased kella nĂ€idud.
Algus – aeg, mil pakett saadeti (tĂ€idetakse serveri poolt – sellest allpool).
Saada – aeg, mil server pakketi sai.
Edastamine – aeg, mil pakett serverist kliendile saadeti (tĂ€idetakse kliendi poolt, sellest ka allpool).

Kahte viimast vÀlja me ei kÀsitle.

Kirjutame meie paketi:

Paketi kood

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

    def __init__(self, version_number=2, mode=3, transmit=0):
        # Vajalik leap second (2 bitti)
        self.leap_indicator = 0
        # Protokolli versioon (3 bitti)
        self.version_number = version_number
        # Saaja reĆŸiim (3 bitti)
        self.mode = mode
        # Kihi „kihtimise” taseme lugemine (1 bait)
        self.stratum = 0
        # Taotluste vahe (1 bait)
        self.pool = 0
        # TĂ€psus (log2) (1 bait)
        self.precision = 0
        # Aeg, mis on vajalik NTP serveri juurde jÔudmiseks (4 baiti)
        self.root_delay = 0
        # NTP-serveri kella hajutamine (4 baiti)
        self.root_dispersion = 0
        # Kellade indikaator (4 baiti)
        self.ref_id = 0
        # Viimane uuendamise aeg serveris (8 baiti)
        self.reference = 0
        # Aeg, mil pakett saadetakse kohalikult masinalt (8 baiti)
        self.originate = 0
        # Aeg, mil server paketi sai (8 baiti)
        self.receive = 0
        # Aeg, mil vastus serverist saadeti (8 baiti)
        self.transmit = transmit

Pakketi saatmiseks (ja vastuvÔtmiseks) peame oskama seda massiiviks transformeerida.
Selle (ja tagasitee) toimingu jaoks kirjutame kaks funktsiooni – pack() ja unpack():

Funktsioon pack

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))

Funktsioon 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

Laiskade jaoks, rakenduse vormis – kood, mis muudab paketi ilusaks stringiks

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)

Paketi saatmine serverisse

Serverisse tuleb saata pakett, mille vĂ€ljad on tĂ€idetud Version, Mode ja Edastamine. Failis Edastamine on vajalik mĂ€rkida kohaliku masina praegune kellaaeg (sekundite arv alates 1. jaanuarist 1900), versioon – ĂŒkskĂ”ik milline vahemik 1-4, reĆŸiim – 3 (kliendi reĆŸiim).

Server, kes saab nĂ”ude, tĂ€idab NTP-paketis kĂ”ik vĂ€ljad, kopeerides vĂ€lja Algus vaĂ€rtuse, mis tuli Edastamine, mis saabus nĂ”udes. Minu jaoks on mĂ”istatus, miks klient ei saa kohe tĂ€ita oma aja vÀÀrtust vĂ€ljale Algus. LĂ”ppkokkuvĂ”ttes, kui pakett tagasi tuleb, on kliendil 4 ajavÀÀrtust – nĂ”ude saatmise aeg (Algus), serveri poolt nĂ”ude saamise aeg (Saada), serveri poolt vastuse saatmise aeg (Edastamine) ja vastuse saabumise aeg kliendile – Saabub (paketis pole). Nende vÀÀrtuste abil saame mÀÀrata Ă”ige aja.

Paketist saatmise ja vastuvÔtmise kood

# 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)

Andmete töötlemine serverist

Andmete töötlemine serverist on sarnane inglise hĂ€rrasmehe tegudele Raymond M. Smullyani vanas ĂŒlesandes (1978): „Ühel inimesel ei olnud kĂ€ekella, kuid kodus riputas tĂ€pne seinakell, mille ta mĂ”nikord unustas maha keerata. Ühel pĂ€eval, unustades jĂ€lle kella maha keerata, lĂ€ks ta sĂ”bra juurde kĂŒlla, veetis seal Ă”htu ja tagasi koju tulles suutis ta kella Ă”igesti seadistada. Kuidas ta seda tegi, kui tee pikkus ei olnud ette teada?” Vastus on: „VĂ€ljudes kodust, keerab inimene kella ĂŒles ja mĂ€letab, millises asendis on kĂ€ed. SĂ”bra juures olles ja kĂŒlast lahkudes mĂ€rkab ta oma saabumise ja lahkumise kellaaegu. See vĂ”imaldab tal teada, kui kaua ta kĂŒlas oli. Tagasi koju jĂ”udes ja kellale vaadates, mÀÀrab ta oma puudumise kestuse. Lahutades sellest ajast kĂŒlas oldud aja, saab ta teada, kui kaua kulus teekonnale edasi ja tagasi. Lisades lahkumise ajale poole teekonna kestusest, saab ta teada, millal koju jĂ”udis, ja saab oma kella vastavalt seadistada.”

Leidke serveri tööaeg pÀringu jaoks:

  1. Leidke paketi teekonna aeg kliendi ja serveri vahel: ((Saabumine – Algus) — (Edastus – VastuvĂ”tt)) / 2
  2. Leidke erinevus kliendi ja serveri aja vahel:
    VastuvĂ”tt — Algus — ((Saabumine – Algus) — (Edastus – VastuvĂ”tt)) / 2 =
    2 * VastuvĂ”tt — 2 * Algus – Saabumine + Algus + Edastus – VastuvĂ”tt =
    VastuvĂ”tt – Algus – Saabumine + Edastus

Lisame saadud vÀÀrtuse kohalikku aega ja naudime elu.

Tulemuse vÀljastamine

time_different = answer.get_time_different(arrive_time)
result = "Aja erinevus: {}nServeri aeg: {}n{}".format(
    time_different,
    datetime.datetime.fromtimestamp(time.time() + time_different).strftime("%c"),
    answer.to_display())
print(result)

Kasulik link.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster