Nulis klien NTP sing prasaja

Halo, Habrausers. Dina iki aku arep ngomong babagan carane nulis klien NTP sing prasaja dhewe. Sejatine, obrolan bakal nguripake menyang struktur paket lan carane respon saka server NTP diproses. Kode kasebut bakal ditulis nganggo python, amarga, miturut pendapatku, ora ana basa sing luwih apik kanggo perkara kasebut. Connoisseurs bakal mbayar manungsa waΓ© kanggo mirip kode karo kode ntplib - Aku iki "inspirasi" dening iku.

Dadi apa sejatine NTP? NTP minangka protokol kanggo interaksi karo server wektu sing tepat. Protokol iki digunakake ing akeh mesin modern. Contone, layanan w32tm ing windows.

Ana 5 versi protokol NTP kanthi total. Pisanan, versi 0 (1985, RFC958)), saiki dianggep lungse. Saiki sing luwih anyar digunakake, 1st (1988, RFC1059), 2nd (1989, RFC1119), 3rd (1992, RFC1305) lan 4th (1996, RFC2030). Versi 1-4 kompatibel karo saben liyane; mung beda-beda ing algoritma operasi server.

Format paket

Nulis klien NTP sing prasaja

Indikator lompat (indikator koreksi) - nomer sing nuduhake bebaya babagan koordinasi kapindho. Artine:

  • 0 - ora ana koreksi
  • 1 - menit pungkasan dina ngemot 61 detik
  • 2 - menit pungkasan dina ngemot 59 detik
  • 3 - malfungsi server (wektu ora disinkronake)

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

mode (mode) - mode operasi saka pangirim paket. Nilai saka 0 nganti 7, paling umum:

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

lapisan (tingkat lapisan) - jumlah lapisan penengah antarane server lan jam referensi (1 - server njupuk data langsung saka jam referensi, 2 - server njupuk data saka server karo tingkat 1, etc.).
Pool iku integer mlebu makili interval maksimum antarane pesen consecutive. Klien NTP nemtokake ing kene interval sing dikarepake kanggo polling server, lan server NTP nemtokake interval sing dikarepake kanggo polling. Nilai kasebut padha karo logaritma binar detik.
Precision (precision) punika integer mlebu makili akurasi jam sistem. Nilai kasebut padha karo logaritma binar detik.
Root tundha (tundha server) - wektu sing dibutuhake kanggo maca jam tekan server NTP, minangka sawetara detik titik tetep.
Dispersi akar (panyebaran server) - panyebaran maca jam server NTP minangka sawetara detik kanthi titik tetep.
Ref id (identifikasi sumber) - id jam. Yen server duwe stratum 1, banjur ref id minangka jeneng jam atom (4 karakter ASCII). Yen server nggunakake server liyane, banjur ref id ngemot alamat server iki.
4 kolom pungkasan makili wektu - 32 bit - bagean integer, 32 bit - bagean pecahan.
reference - maca jam paling anyar ing server.
Asal - wektu nalika paket dikirim (diisi dening server - liyane ing ngisor iki).
nampa - wektu paket ditampa dening server.
Ngirimaken - wektu ngirim paket saka server menyang klien (isi dening klien, liyane ing ngisor iki).

Kita ora bakal nimbang rong lapangan pungkasan.

Ayo nulis paket kita:

Kode paket

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

Kanggo ngirim (lan nampa) paket menyang server, kita kudu bisa ngowahi menyang Uploaded byte.
Kanggo operasi iki (lan mbalikke), kita bakal nulis rong fungsi - pack () lan unpack ():

fungsi paket

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

Kanggo wong kesed, minangka aplikasi - kode sing ngowahi paket dadi senar sing apik

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 paket menyang server

Paket sing diisi kolom kudu dikirim menyang server versi, mode ΠΈ Ngirimaken. ing Ngirimaken sampeyan kudu nemtokake wektu saiki ing mesin lokal (jumlah detik wiwit 1 Januari 1900), versi - sembarang 1-4, mode - 3 (mode klien).

Server, sawise nampa panjaluk kasebut, ngisi kabeh kolom ing paket NTP, nyalin menyang lapangan Asal nilai saka Ngirimaken, sing teka ing panyuwunan. Iku misteri kanggo kula kenapa klien ora bisa langsung ngisi regane wektu ing lapangan Asal. AkibatΓ©, nalika paket bali, klien duwe 4 nilai wektu - wektu panjalukan dikirim (Asal), wektu server nampa panjalukan (nampa), wektu server ngirim respon (Ngirimaken) lan wektu klien nampa respon - Tiba (ora ing paket). Nggunakake nilai kasebut, kita bisa nyetel wektu sing bener.

Paket ngirim lan nampa 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 saka server

Pangolahan data saka server padha karo tumindake wong Inggris saka masalah lawas Raymond M. Smallian (1978): "Sawijining wong ora duwe jam tangan, nanging ana jam tembok sing akurat ing omah, sing dheweke ora duwe jam tangan. kadang lali angin. Sawijining dina, lali miwiti jam maneh, dheweke lunga nemoni kancane, nginep ing wayah sore, lan nalika mulih, dheweke bisa nyetel jam kanthi bener. Kepiye carane dheweke bisa nindakake iki yen wektu lelungan ora dingerteni sadurunge? Wangsulane: "Ninggalake omah, ana wong sing nggulung jam lan ngelingi posisi tangan. Teka kanca lan ninggalake tamu, dheweke nyathet wektu tekan lan budhal. Iki ngidini dheweke ngerteni suwene dheweke lunga. Bali menyang omah lan ndeleng jam, wong nemtokake suwene ora ana. Dikurangi saka wektu iki wektu sing ditindakake kanggo ngunjungi, wong kasebut nemokake wektu sing ditindakake ing dalan kono lan bali. Kanthi nambahake setengah wektu ing dalan menyang wektu ninggalake tamu, dheweke entuk kesempatan kanggo ngerteni wektu tekan omah lan nyetel jam tangane.

Temokake wektu server nggarap panyuwunan:

  1. Nemokake wektu perjalanan paket saka klien menyang server: ((Tekan – Asal) – (Kirim – Nampa)) / 2
  2. Temokake prabΓ©dan antarane wektu klien lan server:
    Nampa - Asal - ((Tekan - Asal) - (Kirim - Nampa)) / 2 =
    2 * Nampa – 2 * Asal – Tekan + Asal + Kirim – Nampa =
    Nampa - Asal - Teka + Kirim

Kita nambah nilai asil kanggo wektu lokal lan seneng urip.

Output saka asil

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)

Migunani pranala.

Source: www.habr.com

Add a comment