ஒரு எளிய NTP கிளையண்டை எழுதுதல்

வணக்கம் ஹேப்ராசர்ஸ். உங்கள் சொந்த எளிய NTP கிளையண்டை எவ்வாறு எழுதுவது என்பது பற்றி இன்று நான் பேச விரும்புகிறேன். அடிப்படையில், உரையாடல் பாக்கெட்டின் கட்டமைப்பிற்கு மாறும் மற்றும் 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 சர்வர் கடிகாரத்தின் சிதறல் வினாடிகளின் நிலையான புள்ளி எண்.
ரெஃப் ஐடி (மூல ஐடி) - வாட்ச் ஐடி. சர்வரில் அடுக்கு 1 இருந்தால், ref ஐடி என்பது அணு கடிகாரத்தின் பெயர் (4 ASCII எழுத்துக்கள்). சேவையகம் வேறொரு சேவையகத்தைப் பயன்படுத்தினால், ref ஐடியில் இந்த சேவையகத்தின் முகவரி இருக்கும்.
கடைசி 4 புலங்கள் நேரம் - 32 பிட்கள் - முழு எண் பகுதி, 32 பிட்கள் - பின்ன பகுதி.
குறிப்பு - சர்வரில் சமீபத்திய கடிகாரம்.
தொடங்குகிறது - பாக்கெட் அனுப்பப்பட்ட நேரம் (சர்வரால் நிரப்பப்பட்டது - கீழே மேலும்).
பெறுக - சேவையகத்தால் பாக்கெட் பெறப்பட்ட நேரம்.
பரிமாற்றத்திற்கு - சேவையகத்திலிருந்து கிளையண்டிற்கு பாக்கெட்டை அனுப்பும் நேரம் (கிளையண்டால் நிரப்பப்பட்டது, மேலும் இது கீழே உள்ளது).

கடைசி இரண்டு துறைகள் கருதப்படாது.

எங்கள் தொகுப்பை எழுதுவோம்:

தொகுப்பு குறியீடு

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

சேவையகத்திற்கு ஒரு பாக்கெட்டை அனுப்ப (மற்றும் பெற), நாம் அதை பைட்டுகளின் வரிசையாக மாற்ற வேண்டும்.
இந்த (மற்றும் தலைகீழ்) செயல்பாட்டிற்கு, நாங்கள் இரண்டு செயல்பாடுகளை எழுதுவோம் - பேக்() மற்றும் அன்பேக்():

பேக் செயல்பாடு

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)

சேவையகத்திற்கு ஒரு தொகுப்பை அனுப்புகிறது

நிரப்பப்பட்ட புலங்களைக் கொண்ட ஒரு பாக்கெட் சேவையகத்திற்கு அனுப்பப்பட வேண்டும் பதிப்பு, முறையில் и பரிமாற்றத்திற்கு. தி பரிமாற்றத்திற்கு உள்ளூர் கணினியில் தற்போதைய நேரத்தை நீங்கள் குறிப்பிட வேண்டும் (ஜனவரி 1, 1900 முதல் வினாடிகளின் எண்ணிக்கை), பதிப்பு - 1-4 இல் ஏதேனும், பயன்முறை - 3 (கிளையன்ட் பயன்முறை).

சேவையகம், கோரிக்கையைப் பெற்று, NTP பாக்கெட்டில் உள்ள அனைத்து புலங்களையும் நிரப்புகிறது, புலத்தில் நகலெடுக்கிறது தொடங்குகிறது இருந்து மதிப்பு பரிமாற்றத்திற்கு, கோரிக்கையில் வந்தது. வாடிக்கையாளரால் ஏன் துறையில் தனது நேரத்தின் மதிப்பை உடனடியாக நிரப்ப முடியாது என்பது எனக்கு ஒரு புதிராக உள்ளது தொடங்குகிறது. இதன் விளைவாக, பாக்கெட் திரும்பி வரும்போது, ​​கிளையண்டிற்கு 4 நேர மதிப்புகள் உள்ளன - கோரிக்கை அனுப்பப்பட்ட நேரம் (தொடங்குகிறது), சேவையகம் கோரிக்கையைப் பெற்ற நேரம் (பெறுக), சேவையகம் பதிலை அனுப்பிய நேரம் (பரிமாற்றத்திற்கு) மற்றும் வாடிக்கையாளரின் பதிலைப் பெறும் நேரம் - சேரும் (தொகுப்பில் இல்லை). இந்த மதிப்புகளைப் பயன்படுத்தி சரியான நேரத்தை அமைக்கலாம்.

தொகுப்பு குறியீடு அனுப்புதல் மற்றும் பெறுதல்

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

பயனுள்ள ссылка.

ஆதாரம்: www.habr.com

கருத்தைச் சேர்