Kitba ta 'klijent NTP sempliċi

Hello, Habrausers. Illum irrid nitkellem dwar kif tikteb il-klijent NTP sempliċi tiegħek stess. Bażikament, il-konversazzjoni ser idur għall-istruttura tal-pakkett u l-metodu ta 'proċessar tar-rispons mis-server NTP. Il-kodiċi se jinkiteb f'Python, għax jidhirli li sempliċement m'hemm l-ebda lingwa aħjar għal affarijiet bħal dawn. Konnoisseurs se jinnotaw ix-xebh tal-kodiċi mal-kodiċi ntplib - I kien "ispirat" minnha.

Allura x'inhu eżattament NTP? NTP huwa protokoll għall-interazzjoni ma 'servers tal-ħin eżatt. Dan il-protokoll jintuża f'ħafna magni moderni. Per eżempju, is-servizz w32tm fit-twieqi.

B'kollox hemm 5 verżjonijiet tal-protokoll NTP. L-ewwel, il-verżjoni 0 (1985, RFC958)), bħalissa hija kkunsidrata skaduta. Issa jintużaw l-aktar ġodda, l-1 (1988, RFC1059), it-2 (1989, RFC1119), it-3 (1992, RFC1305) u r-4 (1996, RFC2030). Verżjonijiet 1-4 huma kompatibbli ma 'xulxin; huma differenti biss fl-algoritmi tat-tħaddim tas-server.

Format tal-pakkett

Kitba ta 'klijent NTP sempliċi

Indikatur tal-qabża (indikatur tal-korrezzjoni) - numru li jindika twissija dwar it-tieni koordinazzjoni. Tifsira:

  • 0 – l-ebda korrezzjoni
  • 1 – l-aħħar minuta tal-ġurnata fiha 61 sekonda
  • 2 – l-aħħar minuta tal-ġurnata fiha 59 sekonda
  • 3 – malfunzjoni tas-server (il-ħin mhux sinkronizzat)

Numru tal-verżjoni (numru tal-verżjoni) – Numru tal-verżjoni tal-protokoll NTP (1-4).

mod (mode) — mod operattiv ta' min jibgħat il-pakkett. Valur minn 0 sa 7, l-aktar komuni:

  • 3 – klijent
  • 4 – server
  • 5 - modalità ta 'xandir

stratum (livell ta’ saffi) – in-numru ta’ saffi intermedji bejn is-server u l-arloġġ ta’ referenza (1 – is-server jieħu data direttament mill-arloġġ ta’ referenza, 2 – is-server jieħu data minn server b’saff 1, eċċ.).
Pool huwa numru sħiħ iffirmat li jirrappreżenta l-intervall massimu bejn messaġġi konsekuttivi. Il-klijent NTP jispeċifika hawn l-intervall li fih jistenna li jivvota lis-server, u s-server NTP jispeċifika l-intervall li fih jistenna li jiġi mistħarreġ. Il-valur huwa ugwali għal-logaritmu binarju ta' sekondi.
Preċiżjoni (eżattezza) huwa numru sħiħ iffirmat li jirrappreżenta l-eżattezza tal-arloġġ tas-sistema. Il-valur huwa ugwali għal-logaritmu binarju ta' sekondi.
Dewmien mill-għeruq (dewmien tas-server) – il-ħin li jieħu biex il-qari tal-arloġġ jilħaq is-server NTP, bħala numru ta' sekondi b'punt fiss.
Dispersjoni tal-għeruq (tixrid tas-server) - tixrid tal-qari tal-arloġġ tas-server NTP bħala numru ta' sekondi b'punt fiss.
Ref id (identifikatur tas-sors) – id-arloġġ. Jekk is-server għandu stratum 1, allura ref id huwa l-isem tal-arloġġ atomiku (4 karattri ASCII). Jekk is-server juża server ieħor, allura r-ref id fiha l-indirizz ta 'dan is-server.
L-aħħar 4 oqsma jirrappreżentaw il-ħin - 32 bit - il-parti sħiħa, 32 bit - il-parti frazzjonali.
Referenza — l-aħħar qari tal-arloġġ fuq is-server.
Joriġinaw – ħin meta ntbagħat il-pakkett (mimli mis-server - aktar dwar dan hawn taħt).
Irċievi – il-ħin li rċeviet il-pakkett mis-server.
Jittrasmettu – ħin li jintbagħat il-pakkett mis-server lill-klijent (mimli mill-klijent, aktar dwar dan hawn taħt).

Mhux se nikkunsidraw l-aħħar żewġ oqsma.

Ejja niktbu l-pakkett tagħna:

Kodiċi tal-pakkett

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

Biex nibagħtu (u nirċievu) pakkett lis-server, irridu nkunu kapaċi nibdluh f'firxa ta' byte.
Għal din l-operazzjoni (u reverse), aħna se niktbu żewġ funzjonijiet - pack() u unpack():

funzjoni tal-pakkett

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

funzjoni tal-ispakkjar

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

Għal nies għażżien, bħala applikazzjoni - kodiċi li jibdel pakkett fi spag sabiħ

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)

Tibgħat pakkett lis-server

Għandu jintbagħat pakkett b'oqsma mimlija lis-server verżjoni, mod и Jittrasmettu. Fil Jittrasmettu trid tispeċifika l-ħin kurrenti fuq il-magna lokali (in-numru ta 'sekondi mill-1 ta' Jannar 1900), verżjoni - kwalunkwe 1-4, mod - 3 (mod klijent).

Is-server, wara li aċċetta t-talba, jimla l-oqsma kollha fil-pakkett NTP, jikkopja fil-qasam Joriġinaw valur minn Jittrasmettu, li daħlet fit-talba. Huwa misteru għalija għaliex il-klijent ma jistax jimla immedjatament il-valur tal-ħin tiegħu fil-qasam Joriġinaw. Bħala riżultat, meta l-pakkett jiġi lura, il-klijent għandu 4 valuri tal-ħin - il-ħin li ntbagħtet it-talba (Joriġinaw), ħin li s-server irċieva t-talba (Irċievi), ħin li s-server bagħat ir-rispons (Jittrasmettu) u l-ħin li l-klijent irċieva t-tweġiba - Jasal (mhux fil-pakkett). Billi nużaw dawn il-valuri nistgħu nissettjaw il-ħin korrett.

Pakkett li jibgħat u jirċievi kodiċi

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

Ipproċessar data mis-server

Id-dejta tal-ipproċessar mis-server hija simili għall-azzjonijiet tal-gentleman Ingliż mill-problema l-antika ta 'Raymond M. Smullyan (1978): "Bniedem wieħed ma kellux arloġġ tal-polz, iżda kien hemm arloġġ tal-ħajt preċiż fid-dar, li xi drabi nesa. għar-riħ. Ġurnata waħda, wara li nesa jerġa’ jdawwal l-arloġġ, mar iżur lil ħabibu, qatta’ l-lejla miegħu, u meta mar lura d-dar, irnexxielu jpoġġi l-arloġġ b’mod korrett. Kif irnexxielu jagħmel dan jekk il-ħin tal-ivvjaġġar ma kienx magħruf minn qabel? It-​tweġiba hija: “Meta titlaq mid-​dar, bniedem idawwar l-​arloġġ u jiftakar f’liema pożizzjoni jkunu l-​idejn. Wara li wasal għand ħabib u telaq lill-mistednin, jinnota l-ħin tal-wasla u t-tluq tiegħu. Dan jippermettilu jiskopri kemm kien ilu jżur. Jirritornaw id-dar u tħares lejn l-arloġġ, persuna tiddetermina t-tul ta 'l-assenza tagħha. Billi tnaqqas minn dan iż-żmien iż-żmien li qattgħet iżur, persuna ssir taf il-ħin li qattgħu tivvjaġġa 'l hemm u lura. Billi żżid nofs il-ħin imqatta’ fit-triq mal-ħin li jħalli l-mistednin, hu jieħu l-opportunità li jsib il-ħin tal-wasla d-dar u jaġġusta l-idejn tal-għassa tiegħu kif xieraq.”

Sib il-ħin li fih is-server qed jaħdem fuq talba:

  1. Sib il-ħin tal-ivvjaġġar tal-pakkett mill-klijent għas-server: ((Jaslu – Oriġinaw) – (Trasmetti – Irċievi)) / 2
  2. Sib id-differenza bejn il-ħin tal-klijent u tas-server:
    Irċievi - Oriġina - ((Jasal - Oriġina) - (Trasmetti - Irċievi)) / 2 =
    2 * Irċievi – 2 * Oriġinaw – Jaslu + Oriġinaw + Trasmetti – Irċievi =
    Irċievi - Oriġinaw - Jaslu + Trasmetti

Aħna nżidu l-valur li jirriżulta mal-ħin lokali u ngawdu l-ħajja.

Output tar-riżultat

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)

Utli rabta.

Sors: www.habr.com

Żid kumment