Oddiy NTP mijozini yozish

Salom habrauzerlar. Bugun men o'zingizning oddiy NTP mijozingizni qanday yozish haqida gapirmoqchiman. Asosan, suhbat paketning tuzilishiga va NTP serveridan javob qanday qayta ishlanishiga aylanadi. Kod python-da yoziladi, chunki mening fikrimcha, bunday narsalar uchun yaxshiroq til yo'q. Biluvchilar kodning ntplib kodi bilan o'xshashligiga e'tibor berishadi - men undan "ilhomlanganman".

Shunday qilib, NTP nima? NTP - bu vaqt serverlari bilan aloqa qilish uchun protokol. Ushbu protokol ko'plab zamonaviy mashinalarda qo'llaniladi. Masalan, Windows-dagi w32tm xizmati.

Hammasi bo'lib NTP protokolining 5 ta versiyasi mavjud. Birinchi, 0-versiya (1985, RFC958) hozirda eskirgan deb hisoblanadi. Hozirda yangilari 1-(1988, RFC1059), 2-(1989, RFC1119), 3-(1992, RFC1305) va 4-(1996, RFC2030) ishlatiladi. 1-4 versiyalar bir-biriga mos keladi, ular faqat serverlarning algoritmlarida farqlanadi.

Paket formati

Oddiy NTP mijozini yozish

Sakrash ko'rsatkichi (tuzatish ko'rsatkichi) - ikkinchi sakrash ogohlantirishini ko'rsatadigan raqam. Ma'nosi:

  • 0 - tuzatish yo'q
  • 1 - kunning oxirgi daqiqasi 61 soniyani o'z ichiga oladi
  • 2 - kunning oxirgi daqiqasi 59 soniyani o'z ichiga oladi
  • 3 - serverning ishlamay qolishi (sinxronizatsiya vaqti)

Versiya raqami (versiya raqami) - NTP protokoli versiya raqami (1-4).

moda (rejim) β€” paket jo'natuvchining ishlash tartibi. Qiymat 0 dan 7 gacha, eng keng tarqalgan:

  • 3 - mijoz
  • 4 - server
  • 5 - eshittirish rejimi

Qatlam (qatlam darajasi) - server va mos yozuvlar soati o'rtasidagi oraliq qatlamlar soni (1 - server ma'lumotni to'g'ridan-to'g'ri mos yozuvlar soatidan oladi, 2 - server 1-darajali serverdan ma'lumotlarni oladi va hokazo).
basseyn ketma-ket xabarlar orasidagi maksimal intervalni ifodalovchi imzolangan butun son. NTP mijozi bu erda serverni so'rashni kutayotgan intervalni belgilaydi va NTP serveri so'rov o'tkazilishi kutilayotgan intervalni belgilaydi. Qiymat soniyalarning ikkilik logarifmiga teng.
aniqlik (aniqlik) - tizim soatining aniqligini ifodalovchi ishorali butun son. Qiymat soniyalarning ikkilik logarifmiga teng.
ildiz kechikishi (serverning kechikishi) soatning NTP serveriga yetib borishi uchun zarur bo'lgan vaqt, sobit nuqtali soniyalar soni.
ildizlarning tarqalishi (serverning tarqalishi) - NTP server soatining sobit nuqtali soniyalar soni sifatida tarqalishi.
Ref identifikatori (manba identifikatori) - tomosha identifikatori. Agar server 1-qatlamga ega bo'lsa, ref id atom soatining nomidir (4 ASCII belgisi). Agar server boshqa serverdan foydalansa, ref identifikatori ushbu server manzilini o'z ichiga oladi.
Oxirgi 4 ta maydon vaqtni ifodalaydi - 32 bit - butun son qismini, 32 bit - kasr qismini.
ma'lumotnoma - serverdagi so'nggi soat.
Kelib chiqishi - paket yuborilgan vaqt (server tomonidan to'ldirilgan - quyida batafsilroq).
Oling – paket server tomonidan qabul qilingan vaqt.
uzatish - paket serverdan mijozga yuborilgan vaqt (mijoz tomonidan to'ldirilgan, quyida batafsilroq).

Oxirgi ikkita maydon ko'rib chiqilmaydi.

Keling, paketimizni yozamiz:

Paket kodi

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

Serverga paket yuborish (va qabul qilish) uchun biz uni baytlar massiviga aylantira olishimiz kerak.
Ushbu (va teskari) operatsiya uchun biz ikkita funktsiyani yozamiz - pack() va unpack():

paket funktsiyasi

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

ochish funktsiyasi

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

Dangasa odamlar uchun dastur sifatida - paketni chiroyli qatorga aylantiradigan kod

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)

Paketni serverga yuborish

To'ldirilgan maydonlar bilan paketni serverga yuboring Version, moda ΠΈ uzatish. The uzatish mahalliy mashinada joriy vaqtni ko'rsatishingiz kerak (1 yil 1900 yanvardan boshlab soniyalar soni), versiya - har qanday 1-4, rejim - 3 (mijoz rejimi).

Server so'rovni qabul qilib, NTP paketidagi barcha maydonlarni maydonga nusxalash orqali to'ldiradi Kelib chiqishi dan qiymat uzatish, so'rovda kelgan. Nima uchun mijoz bu sohada o'z vaqtining qiymatini darhol to'ldira olmasligi men uchun sir Kelib chiqishi. Natijada, paket qaytib kelganida, mijoz 4 vaqt qiymatiga ega - so'rov yuborilgan vaqt (Kelib chiqishi), server so'rovni qabul qilgan vaqt (Oling), server javobni yuborgan vaqt (uzatish) va mijoz tomonidan javob olish vaqti - Keling (paketda emas). Ushbu qiymatlar bilan biz to'g'ri vaqtni belgilashimiz mumkin.

Paketni yuborish va qabul qilish kodi

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

Serverdan ma'lumotlarni qayta ishlash

Serverdan ma'lumotlarni qayta ishlash ingliz janobining Raymond M. Smallianning (1978 yil) eski muammosidagi harakatlariga o'xshaydi: "Bir kishining qo'l soati yo'q edi, lekin uyda aniq devor soati bor edi. ba'zan shamol qilishni unutib qo'yadi. Bir kuni u soatni qayta boshlashni unutib, dugonasinikiga borib, u bilan oqshomni o'tkazdi va uyga qaytganida soatni to'g'ri qo'yishga muvaffaq bo'ldi. Agar sayohat vaqti oldindan ma'lum bo'lmasa, u buni qanday amalga oshirdi? Javob: "Uydan chiqib, odam soatni aylantiradi va qo'llarning holatini eslaydi. Do'stiga kelib, mehmonlarni tark etib, u kelishi va ketishi vaqtini qayd etadi. Bu unga qancha vaqt ketganini bilish imkonini beradi. Uyga qaytib, soatga qarab, odam yo'qligining davomiyligini aniqlaydi. Shu vaqtdan tashrif buyurgan vaqtini ayirib, odam u erga va orqaga yo'lda o'tkazgan vaqtini bilib oladi. Mehmonlarni tark etish vaqtiga yo'lda o'tkaziladigan vaqtning yarmini qo'shib, u uyga kelish vaqtini bilish va soatining qo'llarini mos ravishda sozlash imkoniyatini qo'lga kiritadi.

Server so'rov ustida ishlagan vaqtni toping:

  1. Mijozdan serverga paketli sayohat vaqtini topish: ((Kelish - Kelib chiqish) - (O'tkazish - Qabul qilish)) / 2
  2. Mijoz va server vaqti o'rtasidagi farqni toping:
    Qabul qilish - Originate - ((Kelish - Originate) - (O'tkazish - Qabul qilish)) / 2 =
    2 * Qabul qilish - 2 * Kelib chiqish - Kelish + Kelib chiqish + O'tkazish - Qabul qilish =
    Qabul qilish - kelib chiqish - kelish + uzatish

Biz olingan qiymatni mahalliy vaqtga qo'shamiz va hayotdan zavqlanamiz.

Natija chiqishi

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)

Foydali aloqa.

Manba: www.habr.com

a Izoh qo'shish