Жөнөкөй NTP кардарын жазуу

Салам, Хабраузерлер. Бүгүн мен өзүңүздүн жөнөкөй NTP кардарыңызды кантип жазуу керектиги жөнүндө сүйлөшкүм келет. Негизинен баарлашуу пакеттин структурасына жана NTP серверинен жоопту иштетүү ыкмасына бурулат. Код Python тилинде жазылат, анткени мага мындай нерселер үчүн жакшы тил жок окшойт. Билгендер коддун ntplib коду менен окшоштугун белгилешет - мен аны "шыктандырдым".

Ошентип, NTP деген эмне? NTP так убакыт серверлери менен өз ара аракеттенүү үчүн протокол болуп саналат. Бул протокол көптөгөн заманбап машиналарда колдонулат. Мисалы, терезелердеги w32tm кызматы.

Жалпысынан NTP протоколунун 5 версиясы бар. Биринчи, версия 0 (1985, RFC958)), учурда эскирген деп эсептелет. Азыр жаңылары колдонулат, 1-(1988, RFC1059), 2-(1989, RFC1119), 3-(1992, RFC1305) жана 4-(1996, RFC2030). 1-4 версиялары бири-бирине шайкеш келет, алар сервердин иштөө алгоритмдеринде гана айырмаланат.

Пакет форматы

Жөнөкөй NTP кардарын жазуу

Секирик көрсөткүч (түзөтүү көрсөткүчү) - координация секундасы жөнүндө эскертүүнү көрсөтүүчү сан. Мааниси:

  • 0 - оңдоо жок
  • 1 – күндүн акыркы мүнөтү 61 секундду камтыйт
  • 2 – күндүн акыркы мүнөтү 59 секунданы камтыйт
  • 3 – сервердин бузулушу (убакыт синхрондоштурулган эмес)

версия номери (версия номери) – NTP протоколунун версия номери (1-4).

бычма (режим) — пакет жөнөтүүчүнүн иштөө режими. 0дөн 7ге чейинки маани, эң кеңири таралган:

  • 3 - кардар
  • 4 – сервер
  • 5 – берүү режими

Катмар (кабаттуулук деңгээли) – сервер менен эталондук сааттын ортосундагы аралык катмарлардын саны (1 – сервер маалыматтарды түз шилтеме саатынан алат, 2 – сервер 1-катмары бар серверден маалыматтарды алат ж.б.).
бассейн ырааттуу билдирүүлөрдүн ортосундагы максималдуу интервалды билдирген кол коюлган бүтүн сан. NTP кардары бул жерде серверди сурамжылоону күткөн интервалды белгилейт, ал эми NTP сервери сурамжылоону күткөн интервалды белгилейт. Маани секунданын бинардык логарифмине барабар.
тактык (так) - системалык сааттын тактыгын билдирген белги коюлган бүтүн сан. Маани секунданын бинардык логарифмине барабар.
Тамыр кечигүү (сервердин кечигүү) – сааттын көрсөткүчтөрү NTP серверине жеткенге чейин талап кылынган убакыт, секунданын белгиленген чекити катары.
Тамырдын дисперсиясы (сервердин жайылуусу) - NTP серверинин саатынын көрсөткүчтөрүнүн белгиленген чекити менен секунданын саны катары таралышы.
Ref id (булак идентификатору) - сааттын идентификатору. Эгерде серверде 1-кабат болсо, анда ref id атомдук сааттын аталышы (4 ASCII символу). Эгерде сервер башка серверди колдонсо, анда ref id бул сервердин дарегин камтыйт.
Акыркы 4 талаа убакытты билдирет - 32 бит - бүтүн бөлүгү, 32 бит - бөлчөк бөлүгү.
шилтеме — сервердеги акыркы сааттын көрсөткүчтөрү.
Origin – пакет жөнөтүлгөн убакыт (сервер толтурган – бул тууралуу төмөндө).
кабыл алуу – пакетти сервер кабыл алган убакыт.
берүү – пакетти серверден кардарга жөнөтүү убактысы (кардар тарабынан толтурулат, бул тууралуу төмөндө).

Биз акыркы эки талааны эске албайбыз.

Пакетибизди жазалы:

Пакет коду

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

Пакетти серверге жөнөтүү (жана алуу) үчүн биз аны байт массивине айланта алышыбыз керек.
Бул (жана тескери) операция үчүн биз эки функцияны жазабыз - pack() жана unpack():

пакет функциясы

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

таңгактан чыгаруу функциясы

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

Жалкоо адамдар үчүн, тиркеме катары - пакетти кооз сапка айландыруучу код

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)

Серверге пакет жөнөтүлүүдө

Толтурулган талаалары бар пакет серверге жөнөтүлүшү керек версия, бычма и берүү. The берүү локалдык машинадагы учурдагы убакытты (1-жылдын 1900-январынан берки секунданын саны) көрсөтүү керек, версия - 1-4түн каалаганы, режим - 3 (кардар режими).

Сервер суроо-талапты кабыл алып, NTP пакетиндеги бардык талааларды толтуруп, талаага көчүрөт. Origin тартып баасы берүү, өтүнүч менен келген. Эмне үчүн кардар талаадагы убактысынын баасын дароо толтура албаганы мен үчүн табышмак Origin. Натыйжада, пакет кайра келгенде, кардар 4 убакыт маанисине ээ - суроо-талап жөнөтүлгөн убакыт (Origin), сервер суроо-талапты кабыл алган убакыт (кабыл алуу), сервер жоопту жөнөткөн убакыт (берүү) жана кардар жооп алган убакыт - келүү (пакетте эмес). Бул баалуулуктарды колдонуу менен биз туура убакытты орното алабыз.

Пакетти жөнөтүү жана кабыл алуу коду

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

Серверден алынган маалыматтарды иштетүү

Серверден маалыматтарды иштеп чыгуу Раймонд М.Смулляндын (1978) эски проблемасындагы англиялык мырзанын аракетине окшош: «Бир кишинин кол сааты жок болчу, бирок үйдө так дубал сааты бар болчу, ал кээде унутуп да калган. шамалга. Күндөрдүн биринде саатын кайра ороону унутуп, досуна конокко барып, кечинде аны менен чогуу өткөрдү, үйгө келгенде саатты туура коюп алды. Жол убактысы алдын ала билинбесе, муну кантип ишке ашырды? Жооп: «Үйдөн чыгып баратканда адам саатын айландырып, колдун кандай абалда экенин эстейт. Бир досуна келип, конокторду таштап, анын келип-кетүү убактысын белгилейт. Бул анын канча убакыттан бери келгенин билүүгө мүмкүндүк берет. Үйгө кайтып келип, саатты карап, адам анын жок болуу мөөнөтүн аныктайт. Бул убакыттан зыярат кылган убактысын кемитүү менен адам ал жакка жана артка кеткен убакытты табат. Жолдо кеткен убакыттын жарымын конокторду калтыруу убактысына кошуу менен ал үйгө келүү убактысын билип, ошого жараша саатынын жебелерин тууралоо мүмкүнчүлүгүнө ээ болот».

Сервер сурам боюнча иштеп жаткан убакытты табыңыз:

  1. Пакеттин кардардан серверге чейинки жол убактысын табыңыз: ((Келүү – Келүү) – (Өтүрүү – Кабыл алуу)) / 2
  2. Кардар менен сервер убактысынын айырмасын табыңыз:
    Кабыл алуу - келип чыгуу - ((Келүү - келип чыгуу) - (Өтүрүү - алуу)) / 2 =
    2 * Кабыл алуу – 2 * Түзүү – Келүү + Түзүү + Өтүү – Кабыл алуу =
    Кабыл алуу - келип чыгуу - келүү + берүү

Биз жергиликтүү убакыттын маанисин кошуп, жашоодон ырахат алабыз.

Жыйынтык чыгаруу

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)

Пайдалуу байланыш.

Source: www.habr.com

Комментарий кошуу