Sadə bir NTP müştərisinin yazılması

Salam habrauzerlər. Bu gün mən öz sadə NTP müştərinizi necə yazacağınız barədə danışmaq istəyirəm. Əsasən, söhbət paketin strukturuna və NTP serverindən cavabın necə işlənməsinə yönələcək. Kod python dilində yazılacaq, çünki mənim fikrimcə, belə şeylər üçün daha yaxşı dil yoxdur. Bilənlər kodun ntplib kodu ilə oxşarlığına diqqət yetirəcəklər - mən ondan "ilham almışam".

Onsuz da NTP nədir? NTP dəqiq vaxt serverləri ilə qarşılıqlı əlaqə üçün protokoldur. Bu protokol bir çox müasir maşınlarda istifadə olunur. Məsələn, Windows-da w32tm xidməti.

Ümumilikdə NTP protokolunun 5 versiyası var. Birinci versiya 0 (1985, RFC958)), hazırda köhnəlmiş hesab olunur. Hal-hazırda daha yeniləri istifadə olunur, 1-ci (1988, RFC1059), 2-ci (1989, RFC1119), 3-cü (1992, RFC1305) və 4-cü (1996, RFC2030). 1-4-cü versiyalar bir-biri ilə uyğun gəlir, onlar yalnız serverlərin alqoritmlərində fərqlənirlər.

Paket Format

Sadə bir NTP müştərisinin yazılması

Sıçrayış göstəricisi (düzəliş göstəricisi) ikinci sıçrayış xəbərdarlığını göstərən rəqəmdir. Məna:

  • 0 - düzəliş yoxdur
  • 1 – günün son dəqiqəsi 61 saniyədən ibarətdir
  • 2 – günün son dəqiqəsi 59 saniyədən ibarətdir
  • 3 - server nasazlığı (sinxronizasiya vaxtı)

Sürüm nömrəsi (versiya nömrəsi) – NTP protokolunun versiya nömrəsi (1-4).

mode (rejim) — paket göndəricinin iş rejimi. 0-dan 7-yə qədər dəyər, ən ümumi:

  • 3 - müştəri
  • 4 - server
  • 5 – yayım rejimi

təbəqə (layinq səviyyəsi) - server və istinad saatı arasındakı ara təbəqələrin sayı (1 - server məlumatı birbaşa istinad saatından alır, 2 - server 1 səviyyəli serverdən məlumat alır və s.).
Pool ardıcıl mesajlar arasındakı maksimum intervalı təmsil edən işarələnmiş tam ədəddir. NTP müştərisi burada serveri sorğulamağı gözlədiyi intervalı, NTP serveri isə sorğulanacağını gözlədiyi intervalı müəyyən edir. Dəyər saniyələrin ikili loqarifminə bərabərdir.
Dəqiqlik (dəqiqlik) sistem saatının dəqiqliyini ifadə edən işarəli tam ədəddir. Dəyər saniyələrin ikili loqarifminə bərabərdir.
Kök gecikməsi (server gecikməsi) sabit nöqtə sayı kimi saatın NTP serverinə çatması üçün tələb olunan vaxtdır.
kök dispersiyası (server scatter) - NTP server saatının sabit nöqtə sayı kimi səpələnməsi.
Ref id (mənbə identifikatoru) – watch id. Əgər serverin 1-ci təbəqəsi varsa, ref id atom saatının adıdır (4 ASCII simvol). Əgər server başqa serverdən istifadə edirsə, onda ref id bu serverin ünvanını ehtiva edir.
Son 4 sahə zamandır - 32 bit - tam hissə, 32 bit - kəsr hissəsi.
arayış - serverdəki ən son saat.
Mənşə – paketin göndərildiyi vaxt (server tərəfindən doldurulur – bu barədə daha ətraflı aşağıda).
Almaq – paketin server tərəfindən qəbul edildiyi vaxt.
Ötürmək – paketin serverdən müştəriyə göndərildiyi vaxt (müştəri tərəfindən doldurulur, daha ətraflı aşağıda).

Son iki sahəni nəzərdən keçirməyəcəyik.

Paketimizi yazaq:

Paket kodu

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

Serverə paket göndərmək (və qəbul etmək) üçün onu bayt massivinə çevirə bilməliyik.
Bu (və tərs) əməliyyat üçün biz iki funksiya yazacağıq - pack() və unpack():

paket funksiyası

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

açmaq funksiyası

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

Tənbəl insanlar üçün, proqram kimi - paketi gözəl bir sətirə çevirən 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)

Paketin serverə göndərilməsi

Doldurulmuş sahələri olan paket serverə göndərilməlidir Versiya, mode и Ötürmək. Ilə Ötürmək yerli maşında cari vaxtı (1 yanvar 1900-cü ildən bəri saniyələrin sayı), versiyanı - 1-4-dən hər hansı birini, rejimi - 3 (müştəri rejimi) göstərməlisiniz.

Sorğunu qəbul edən server NTP paketindəki bütün sahələri dolduraraq sahəyə köçürür. Mənşə -dən dəyər Ötürmək, sorğuda gəldi. Müştərinin niyə sahədəki vaxtının dəyərini dərhal doldura bilmədiyi mənim üçün sirrdir Mənşə. Nəticədə, paket geri qayıtdıqda, müştərinin 4 vaxt dəyəri var - sorğunun göndərildiyi vaxt (Mənşə), serverin sorğunu qəbul etdiyi vaxt (Almaq), serverin cavabı göndərdiyi vaxt (Ötürmək) və müştəri tərəfindən cavabın alınma vaxtı - Gəlin (paketdə deyil). Bu dəyərlərdən istifadə edərək düzgün vaxtı təyin edə bilərik.

Paketin göndərilməsi və qəbulu kodu

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

Serverdən məlumatların işlənməsi

Serverdən məlumatların işlənməsi Raymond M. Smullyanın köhnə problemindən (1978) ingilis centlmeninin hərəkətlərinə bənzəyir: “Bir adamın qol saatı yox idi, ancaq evdə dəqiq divar saatı var idi, bəzən onu unudurdu. külək etmək. Bir gün saatını yenidən qurmağı unudub, dostuna baş çəkməyə gedib, axşamı onunla keçirib, evə qayıdanda isə saatı düzgün qurmağı bacarıb. Səyahət vaxtı əvvəlcədən bəlli olmasaydı, o bunu necə bacardı? Cavab belədir: “İnsan evdən çıxarkən qol saatını qurur və yadına salır ki, əllər hansı vəziyyətdədir. Dostuna gəlib qonaqları tərk edərkən gəliş-gediş vaxtını qeyd edir. Bu, ona nə qədər müddət uzaqda olduğunu öyrənməyə imkan verir. Evə qayıdıb saata baxan insan öz yoxluğunun müddətini müəyyən edir. Bu vaxtdan ziyarətə sərf etdiyi vaxtı çıxarmaqla insan ora və geriyə səyahətə sərf etdiyi vaxtı tapır. Yolda sərf olunan vaxtın yarısını qonaqları tərk etmə vaxtına əlavə etməklə evə çatma vaxtını öyrənmək və buna uyğun olaraq saatının əqrəblərini tənzimləmək imkanı əldə edir”.

Serverin sorğu üzərində işlədiyi vaxtı tapın:

  1. Paketin müştəridən serverə gediş vaxtını tapın: ((Gəlmək - Originate) - (Göndərmək - Qəbul etmək)) / 2
  2. Müştəri və server vaxtı arasındakı fərqi tapın:
    Qəbul - Mənşə - ((Gəlmək - Mənşə) - (Göndərmək - Qəbul etmək)) / 2 =
    2 * Qəbul - 2 * Mənşə - Gəlmə + Mənşə + Göndər - Qəbul =
    Qəbul - Originate - Gəlmə + Göndər

Yaranan dəyəri yerli vaxta əlavə edirik və həyatdan həzz alırıq.

Nəticə Çıxışı

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)

Faydalı əlaqə.

Mənbə: www.habr.com

Добавить комментарий