Halo, habrayuzery. Hari ini saya ingin berbicara tentang cara menulis klien NTP sederhana saya. Pada dasarnya, kita akan berbicara tentang struktur paket dan bagaimana memproses respons dari server NTP. Kode akan ditulis dalam python, karena, menurut saya, bahasa terbaik untuk hal-hal seperti itu tidak dapat ditemukan. Penikmat akan memperhatikan kesamaan kode dengan kode ntplib - saya “terinspirasi” olehnya.
Jadi apa sebenarnya NTP? NTP - protokol interaksi dengan server waktu yang tepat. Protokol ini digunakan di banyak mesin modern.
Misalnya, layanan w32tm di windows.Ada 5 versi protokol NTP secara total. Yang pertama, versi 0 (1985, RFC958)) saat ini dianggap usang. Sekarang yang lebih baru digunakan: 1 (1988, RFC1059), 2 (1989, RFC1119), 3 (1992, RFC1305) dan 4 (1996, RFC2030). 1-4 versi kompatibel satu sama lain, mereka hanya berbeda dalam algoritma operasi server.
Format paket
Leap indicator (
indikator koreksi) - angka yang menunjukkan peringatan tentang koordinasi kedua. Nilai:
- 0 - tidak ada koreksi
- 1 - menit terakhir dalam sehari berisi 61 detik
- 2 - menit terakhir dalam sehari berisi 59 detik
- 3 - kerusakan server (waktu tidak disinkronkan)
Nomor versi - Nomor versi protokol NTP (1-4).
Mode - mode operasi pengirim paket. Nilai dari 0 hingga 7, yang paling umum:
- 3 - klien
- 4 - server
- 5 - mode siaran
Stratum (level pelapisan) - jumlah lapisan menengah antara server dan jam referensi (1 - server mengambil data langsung dari jam referensi, 2 - server mengambil data dari server dengan level 1, dll.).
Poll adalah bilangan bulat yang ditandatangani yang mewakili interval maksimum antara pesan berurutan. Di sini, klien NTP menunjukkan interval di mana ia mengharapkan untuk polling server, dan server NTP menunjukkan interval di mana ia mengharapkan untuk polling. Nilainya adalah logaritma biner detik.
Precision adalah integer bertanda tangan yang menunjukkan keakuratan jam sistem. Nilainya adalah logaritma biner detik.
Root delay (server delay) - waktu di mana jam mencapai server NTP, sebagai jumlah detik dengan titik tetap.
Root dispersi - sebaran jam server NTP sebagai jumlah detik dengan titik tetap.
Id ref (pengidentifikasi sumber) - id jam tangan. Jika server memiliki strata 1, maka ref id adalah nama jam atom (4 karakter ASCII). Jika server menggunakan server lain, maka alamat server ini ditulis dalam id ref.
4 bidang terakhir adalah waktu - 32 bit - bagian integer, 32 bit - bagian fraksional.
Referensi - jam terbaru di server.
Berasal - waktu ketika paket dikirim (diisi oleh server - lebih lanjut tentang itu di bawah).
Terima - waktu paket diterima oleh server.
Mengirimkan - waktu paket dikirim dari server ke klien (diisi oleh klien, lebih lanjut tentang itu di bawah).
Kami tidak akan mempertimbangkan dua bidang terakhir.
Mari menulis paket kami:
Kode paketclass NTPPacket: _FORMAT = "!BB bb 11I" def __init__(self, version_number=2, mode=3, transmit=0):
Untuk mengirim (dan menerima) paket ke server, kita harus dapat mengubahnya menjadi array byte.
Untuk operasi ini (dan membalikkan), kami akan menulis dua fungsi - pack () dan 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))
Untuk memilih bagian fraksional dari nomor untuk ditulis ke paket, kita memerlukan fungsi get_fraction ():
get_fraction () def get_fraction(number, precision): return int((number - int(number)) * 2 ** precision)
Buka paket fungsi def unpack(self, data: bytes): unpacked_data = struct.unpack(NTPPacket._FORMAT, data) self.leap_indicator = unpacked_data[0] >> 6
Untuk orang malas, sebagai aplikasi - kode yang mengubah paket menjadi string yang indah 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)
Mengirim paket ke server
Penting untuk mengirim paket ke server dengan kolom
Version ,
Mode dan
Transmit yang terisi. Dalam
Transmit, Anda perlu menentukan waktu saat ini pada mesin lokal (jumlah detik sejak 1 Januari 1900), versi - semua 1-4, mode - 3 (mode klien).
Setelah menerima permintaan, server mengisi semua bidang dalam paket NTP dengan menyalin nilai
Transmit dari permintaan ke bidang
Originate . Ini adalah misteri bagi saya mengapa klien tidak dapat segera mengisi nilai waktunya di bidang
Originate . Akibatnya, ketika paket kembali, klien memiliki 4 kali - waktu permintaan dikirim (
Berasal ), waktu server menerima permintaan (
Menerima ), waktu server mengirim respons (
Kirim ), dan waktu klien menerima respons -
Tiba (tidak ada dalam paket). Dengan menggunakan nilai-nilai ini, kita dapat mengatur waktu yang benar.
Paket mengirim dan menerima kode Memproses data dari server
Pemrosesan data dari server mirip dengan tindakan pria Inggris dari tugas lama Raymond M. Sullian (1978): “Satu orang tidak memiliki arloji, tetapi di sisi lain ada jam dinding yang tepat yang terkadang dia lupa untuk mulai. Suatu kali, setelah lupa menyalakan arloji lagi, ia pergi mengunjungi temannya, menghabiskan malam di tempat itu, dan ketika kembali ke rumah, ia berhasil mengatur jam dengan benar. Bagaimana dia bisa melakukan ini jika waktu perjalanan tidak diketahui sebelumnya? " Jawabannya adalah: “Meninggalkan rumah, seseorang memulai arloji dan mengingat di posisi apa tangan itu berada. Datang ke seorang teman dan meninggalkan para tamu, ia mencatat waktu kedatangan dan keberangkatannya. Ini memungkinkan dia untuk mengetahui berapa banyak yang dia kunjungi. Setelah kembali ke rumah dan melihat arloji, seseorang menentukan durasi ketidakhadirannya. Mengurangkan dari waktu ini waktu yang dia habiskan untuk berkunjung, seseorang belajar waktu yang dihabiskan untuk perjalanan bolak-balik. Setelah menambahkan separuh waktu yang dihabiskan dalam perjalanan ke waktu meninggalkan para tamu, ia mendapat kesempatan untuk mengetahui waktu kedatangan di rumah dan menerjemahkan jarum jam dengan tepat. ”
Kami menemukan waktu kerja server berdasarkan permintaan:
- Kami menemukan waktu jalur paket dari klien ke server: ((Tiba - Berasal) - (Kirim - Terima)) / 2
- Temukan perbedaan antara waktu klien dan server:
Terima - Berasal - ((Tiba - Berasal) - (Kirim - Terima)) / 2 =
2 * Terima - 2 * Berasal - Tiba + Berasal + Kirim - Terima =
Terima - Berasal - Tiba + Kirim
Tambahkan nilai yang diperoleh ke waktu setempat dan nikmati hidup.
Hasil keluaran 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)
Tautan yang bermanfaat.