Nulis klien NTP basajan

Halo, Habrausers. Dinten ieu abdi hoyong ngobrol ngeunaan kumaha carana nulis klien NTP basajan anjeun sorangan. Dasarna, paguneman bakal giliran struktur pakét sareng metode ngolah réspon ti server NTP. Kode bakal ditulis dina Python, sabab sigana mah ngan saukur aya basa hadé pikeun hal saperti. Connoisseurs bakal nyatet kasaruaan kode sareng kode ntplib - kuring "diideuan" ku éta.

Jadi naon sabenerna NTP? NTP mangrupikeun protokol pikeun interaksi sareng server waktos pasti. Protokol ieu dianggo dina seueur mesin modern. Contona, layanan w32tm dina windows.

Aya 5 versi tina protokol NTP dina total. Kahiji, versi 0 (1985, RFC958)), ayeuna dianggap luntur. Ayeuna anu langkung énggal dianggo, ka-1 (1988, RFC1059), ka-2 (1989, RFC1119), ka-3 (1992, RFC1305) sareng ka-4 (1996, RFC2030). Versi 1-4 cocog sareng anu sanés; aranjeunna ngan ukur béda dina algoritma operasi server.

Format pakét

Nulis klien NTP basajan

Indikator kabisat (indikator koreksi) - angka nunjukkeun peringatan ngeunaan koordinasi kadua. hartina:

  • 0 - euweuh koreksi
  • 1 - menit panungtungan poé ngandung 61 detik
  • 2 - menit panungtungan poé ngandung 59 detik
  • 3 - gangguan server (waktos teu nyingkronkeun)

Nomer versi (nomer versi) - angka versi protokol NTP (1-4).

mode (modus) - mode operasi pangirim pakét. Nilai ti 0 nepi ka 7, paling umum:

  • 3 - klien
  • 4 - server
  • 5 - modeu siaran

Lapisan (tingkat layering) - Jumlah lapisan panengah antara server jeung jam rujukan (1 - server nyokot data langsung tina jam rujukan, 2 - server nyokot data ti server kalawan lapisan 1, jeung sajabana).
balong kojay mangrupakeun integer ditandatanganan ngalambangkeun interval maksimum antara pesen padeukeut. Klien NTP netepkeun di dieu interval dimana éta ngarepkeun polling server, sareng server NTP netepkeun interval dimana éta bakal dijajal. Nilaina sarua jeung logaritma binér detik.
katalitian (akurasi) mangrupakeun integer ditandatanganan ngalambangkeun akurasi jam sistem. Nilaina sarua jeung logaritma binér detik.
Akar reureuh (delay server) - waktu nu diperlukeun pikeun maca jam nepi ka server NTP, salaku angka tetep-titik detik.
Panyebaran akar (Server sumebar) - sumebarna maca jam server NTP sakumaha sababaraha detik kalayan titik tetep.
Ref id (identifier sumber) - id jam. Upami server ngagaduhan stratum 1, maka ref id mangrupikeun nami jam atom (4 karakter ASCII). Upami server nganggo server anu sanés, maka ref id ngandung alamat server ieu.
4 widang panungtungan ngagambarkeun waktu - 32 bit - bagian integer, 32 bit - bagian fractional.
rujukan - bacaan jam panganyarna dina server.
Asalna - waktos nalika pakét dikirim (dieusian ku server - langkung seueur ngeunaan ieu di handap).
narima - waktos pakét ditampi ku server.
ngirimkeun - waktos ngirim pakét ti server ka klien (dieusian ku klien, langkung seueur ngeunaan ieu di handap).

Kami moal nganggap dua widang anu terakhir.

Hayu urang nyerat pakét kami:

Kode pakét

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

Pikeun ngirim (sareng nampi) pakét ka server, urang kedah tiasa ngahurungkeunana kana susunan bait.
Pikeun operasi ieu (sareng sabalikna), kami bakal nyerat dua fungsi - pak () sareng ngabongkar ():

fungsi pakét

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

fungsi ngabongkar

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

Pikeun jalma anu puguh, salaku aplikasi - kode anu ngajantenkeun pakét janten senar anu saé

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)

Ngirim pakét ka server

A pakét kalayan widang dieusian kudu dikirim ka server Vérsi, mode и ngirimkeun. The ngirimkeun Anjeun kudu nangtukeun waktu ayeuna dina mesin lokal (jumlah detik saprak 1 Januari 1900), versi - salah sahiji 1-4, mode - 3 (mode klien).

Server, saatos nampi pamenta, ngeusian sadaya widang dina pakét NTP, nyalin kana lapangan. Asalna nilai ti ngirimkeun, nu datang dina pamundut. Éta misteri keur kuring naha klien teu bisa langsung ngeusian nilai waktu na di lapangan Asalna. Hasilna, nalika pakét sumping deui, klien ngagaduhan 4 nilai waktos - waktos pamundut dikirim (Asalna), waktos server nampi pamundut (narima), waktos server ngirim respon (ngirimkeun) sareng waktos klien nampi réspon - Datangna (teu aya dina bungkusan). Nganggo nilai-nilai ieu urang tiasa nyetél waktos anu leres.

Paket ngirim sareng nampi kode

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

Ngolah data ti server

Ngolah data ti server téh sarupa jeung lampah gentleman Inggris ti masalah heubeul Raymond M. Smullyan (1978): "Hiji lalaki teu boga arloji, tapi aya hiji jam témbok akurat di imah, nu anjeunna kadang poho. ka angin. Hiji poé, sanggeus poho pikeun balik jam na, manéhna indit ka nganjang ka babaturanana, méakkeun peuting jeung manéhna, jeung balik ka imah, manéhna junun nyetel arloji bener. Kumaha anjeunna tiasa ngalakukeun ieu upami waktos perjalanan henteu dipikanyaho sateuacanna? Jawabanna nyaéta: "Nalika kaluar ti bumi, saurang jalma ngagulungkeun arlojina sareng émut kana posisi naon leungeun éta. Sanggeus datang ka babaturan jeung ninggalkeun tamu, manéhna nyatet waktu datangna jeung miang. Hal ieu ngamungkinkeun anjeunna pikeun manggihan sabaraha lila anjeunna didatangan. Balik ka bumi sareng ningali jam, hiji jalma nangtukeun lilana henteuna. Ku ngirangan tina waktos ieu waktos anjeunna nganjang, hiji jalma mendakan waktos anu dianggo pikeun iinditan ka ditu sareng ka tukang. Ku nambahkeun satengah waktu di jalan ka waktu ninggalkeun tamu, manéhna meunang kasempetan pikeun manggihan waktu kadatangan imah jeung nyaluyukeun leungeun arloji na sasuai.

Pilarian waktos pangladén damel dina pamundut:

  1. Milarian waktos perjalanan pakét ti klien ka server: ((Anjog – Asal) – (Kirim – Narima)) / 2
  2. Pilarian bédana antara waktos klien sareng server:
    Narima - Asalna - ((Anjog - Asalna) - (Kirimkeun - Narima)) / 2 =
    2 * Narima – 2 * Asal – Anjog + Asal + Kirim – Narima =
    Narima - Asal - Anjog + Kirim

Urang tambahkeun nilai hasilna kana waktu lokal jeung ngarasakeun hirup.

Kaluaran hasilna

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)

Mangpaat link.

sumber: www.habr.com

Tambahkeun komentar