Að skrifa einfaldan NTP viðskiptavin

Halló, Habrausers. Í dag vil ég tala um hvernig á að skrifa þinn eigin einfalda NTP viðskiptavin. Í grundvallaratriðum mun samtalið snúast að uppbyggingu pakkans og aðferð við að vinna úr svarinu frá NTP netþjóninum. Kóðinn verður skrifaður í Python, því mér sýnist að það sé einfaldlega ekkert betra tungumál fyrir slíka hluti. Sérfræðingar munu taka eftir líkingu kóðans og ntplib kóðanum - ég var "innblásinn" af honum.

Svo hvað nákvæmlega er NTP? NTP er samskiptareglur fyrir samskipti við nákvæma tímaþjóna. Þessi samskiptaregla er notuð í mörgum nútíma vélum. Til dæmis, w32tm þjónustan í Windows.

Alls eru til 5 útgáfur af NTP samskiptareglunum. Sú fyrsta, útgáfa 0 (1985, RFC958)), er nú talin úrelt. Nú eru þeir nýrri notaðir, 1. (1988, RFC1059), 2. (1989, RFC1119), 3. (1992, RFC1305) og 4. (1996, RFC2030). Útgáfur 1-4 eru samhæfðar hver við aðra; þær eru aðeins frábrugðnar í reikniritum fyrir rekstri netþjónsins.

Pakkasnið

Að skrifa einfaldan NTP viðskiptavin

Stökkvísir (leiðréttingarvísir) - tala sem gefur til kynna viðvörun um samhæfingu sekúndu. Merking:

  • 0 - engin leiðrétting
  • 1 – síðasta mínúta dagsins inniheldur 61 sekúndu
  • 2 – síðasta mínúta dagsins inniheldur 59 sekúndur
  • 3 - miðlara bilun (tíminn er ekki samstilltur)

Útgáfunúmer (útgáfunúmer) – Útgáfunúmer NTP samskiptareglur (1-4).

Mode (hamur) — rekstrarhamur sendanda pakka. Gildi frá 0 til 7, algengast:

  • 3 - viðskiptavinur
  • 4 - þjónn
  • 5 - útsendingarstilling

Jarðlag (lagskipting) – fjöldi millilaga milli þjóns og viðmiðunarklukku (1 – þjónninn tekur gögn beint úr viðmiðunarklukkunni, 2 – þjónninn tekur gögn frá netþjóni með lag 1 o.s.frv.).
Laug er heiltala sem táknar hámarksbil milli samfelldra skilaboða. NTP biðlarinn tilgreinir hér millibilið sem hann býst við að skoða netþjóninn á og NTP miðlarinn tilgreinir það bil sem hann býst við að vera kannaður á. Gildið er jafnt og tvíundir logaritma sekúndna.
Nákvæmni (nákvæmni) er heiltala sem táknar nákvæmni kerfisklukkunnar. Gildið er jafnt og tvíundir logaritma sekúndna.
Rótartöf (töf á netþjóni) – tíminn sem það tekur klukkumælingar að ná til NTP netþjónsins, sem fastur punktur í sekúndum.
Rótdreifing (dreifing netþjóns) - dreifing á klukkumælingum NTP miðlara sem fjölda sekúndna með föstum punkti.
Ref id (uppspretta auðkenni) – auðkenni klukku. Ef þjónninn er með stratum 1, þá er ref id nafnið á atómklukkunni (4 ASCII stafir). Ef þjónninn notar annan netþjón, þá inniheldur ref id heimilisfang þessa þjóns.
Síðustu 4 reitirnir tákna tímann - 32 bitar - heiltöluhlutinn, 32 bitar - brotahlutinn.
Tilvísun — nýjustu klukkumælingar á þjóninum.
Uppruni - tíma þegar pakkinn var sendur (fyllt út af þjóninum - meira um þetta hér að neðan).
- hvenær pakkinn barst þjóninum.
Senda - tími sendingar pakkans frá þjóninum til viðskiptavinarins (fyllt út af viðskiptavininum, meira um þetta hér að neðan).

Við munum ekki íhuga síðustu tvo reitina.

Við skulum skrifa pakkann okkar:

Pakkakóði

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

Til að senda (og taka á móti) pakka til netþjónsins verðum við að geta breytt honum í bætafylki.
Fyrir þessa (og öfugri) aðgerð munum við skrifa tvær aðgerðir - pakka() og afpakka():

pakkaaðgerð

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

unpack aðgerð

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

Fyrir lata, sem forrit - kóða sem breytir pakka í fallegan streng

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)

Sendir pakka á netþjóninn

Senda þarf pakka með útfylltum reitum á netþjóninn útgáfa, Mode и Senda. Í Senda þú verður að tilgreina núverandi tíma á staðbundinni vél (fjöldi sekúndna frá 1. janúar 1900), útgáfu - eitthvað af 1-4, ham - 3 (viðskiptavinastilling).

Miðlarinn, eftir að hafa samþykkt beiðnina, fyllir út alla reiti í NTP pakkanum og afritar inn í reitinn Uppruni verðmæti frá Senda, sem kom í beiðninni. Það er mér hulin ráðgáta hvers vegna viðskiptavinurinn getur ekki strax fyllt út verðmæti tíma síns á sviði Uppruni. Þar af leiðandi, þegar pakkinn kemur aftur, hefur viðskiptavinurinn 4 tímagildi - tíminn sem beiðnin var send (Uppruni), þegar þjónninn fékk beiðnina (), þegar þjónninn sendi svarið (Senda) og tímann sem viðskiptavinurinn fékk svarið – Komið (ekki í pakkanum). Með því að nota þessi gildi getum við stillt réttan tíma.

Kóði fyrir sendingu og móttöku pakka

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

Vinnsla gagna frá þjóninum

Vinnsla gagna frá netþjóninum er svipuð aðgerðum enska heiðursmannsins úr gamla vandamálinu Raymond M. Smullyan (1978): „Einn maður átti ekki armbandsúr, en það var nákvæm veggklukka heima, sem hann gleymdi stundum. að vinda. Einn daginn, þegar hann hafði gleymt að vinda úrinu aftur, fór hann að heimsækja vin sinn, eyddi kvöldinu með honum og þegar hann kom heim tókst honum að stilla úrið rétt. Hvernig tókst honum þetta ef ferðatíminn var ekki þekktur fyrirfram? Svarið er: „Þegar farið er að heiman vindur maður úrinu og man í hvaða stöðu vísurnar eru. Eftir að hafa komið til vinar síns og skilið eftir gestina, bendir hann á komu- og brottfarartímann. Þetta gerir honum kleift að komast að því hversu lengi hann var í heimsókn. Þegar maður kemur heim og horfir á klukkuna ákveður maður lengd fjarveru sinnar. Með því að draga frá þessum tíma þann tíma sem hann eyddi í heimsókn kemst maður að tímanum sem fór í ferðalög þangað og til baka. Með því að bæta helmingi þess tíma sem fer í ferðina við þann tíma sem hann yfirgefur gestina fær hann tækifæri til að komast að því hvenær heim er komið og stilla vísurnar á úrið sitt í samræmi við það.“

Finndu tímann sem þjónninn vinnur að beiðni:

  1. Finndu ferðatíma pakkans frá biðlara til netþjóns: ((Koma – Uppruna) – (Senda – Móttaka)) / 2
  2. Finndu muninn á tíma biðlara og netþjóns:
    Móttaka - Uppruna - ((Koma - Uppruna) - (Senda - Móttaka)) / 2 =
    2 * Móttaka – 2 * Uppruna – Koma + Uppruna + Senda – Móttaka =
    Móttaka - Uppruna - Koma + Senda

Við bætum verðmætinu sem af þessu leiðir við staðartímann og njótum lífsins.

Úttak niðurstöðunnar

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)

Nothæft tengill.

Heimild: www.habr.com

Bæta við athugasemd