Nivîsandina xerîdarek NTP ya hêsan

Silav, Habrausers. Îro ez dixwazim biaxivim ka meriv çawa xerîdar NTP-ya xweya hêsan dinivîse. Di bingeh de, danûstendin dê li avahiya pakêtê û awayê hilanîna bersivê ji servera NTP vegere. Kod dê di Python de were nivîsandin, ji ber ku ji min re xuya dike ku ji bo tiştên weha zimanek çêtir tune. Zanyar dê wekheviya kodê bi koda ntplib-ê re bibînin - ez jê "îlhama" bûm.

Ji ber vê yekê bi rastî NTP çi ye? NTP protokolek ji bo danûstendina bi serverên wextê rast e. Ev protokol di gelek makîneyên nûjen de tê bikar anîn. Mînakî, karûbarê w32tm di windows de.

Bi tevahî 5 guhertoyên protokola NTP hene. Ya yekem, guhertoya 0 (1985, RFC958)), niha wekî kevnar tê hesibandin. Naha yên nûtir têne bikar anîn, 1mîn (1988, RFC1059), 2mîn (1989, RFC1119), 3mîn (1992, RFC1305) û 4emîn (1996, RFC2030). Guhertoyên 1-4 bi hevûdu re hevaheng in; ew tenê di algorîtmayên xebata serverê de cûda dibin.

Forma pakêtê

Nivîsandina xerîdarek NTP ya hêsan

Leap nîşana (nîşana rastkirinê) - hejmarek ku hişyariyek li ser hevrêziya duyemîn nîşan dide. Mane:

  • 0 - ne rastkirin
  • 1 – Deqeya dawî ya rojê 61 saniye dihewîne
  • 2 – Deqeya dawî ya rojê 59 saniye dihewîne
  • 3 - xeletiya serverê (dem nayê hevdem kirin)

Hejmara guhertoyê (hejmara guhertoyê) - Hejmara guhertoya protokola NTP (1-4).

fashion (mode) - moda xebitandinê ya şanderê pakêtê. Nirx ji 0 heta 7, herî gelemperî:

  • 3 - xerîdar
  • 4 - server
  • 5 - moda weşanê

stratum (asta qatkirinê) - hejmara qatên navîn di navbera server û demjimêra referansê de (1 - pêşkêşker daneyan rasterast ji demjimêra referansê digire, 2 - server daneyan ji serverek bi qata 1, hwd.) digire.
Hezê avjenî jimareyek bi îmzakirî ye ku navbera herî zêde ya di navbera peyamên li pey hev de temsîl dike. Muwekîlê NTP-ê li vir navbera ku li bendê ye ku serverê rapirsî bike diyar dike, û servera NTP-ê navbera ku li bendê ye ku lê were pirsîn diyar dike. Nirx bi logarîtma binar ya saniyeyan re wekhev e.
tamî (rastbûn) jimareyek bi îmzakirî ye ku rastbûna demjimêra pergalê temsîl dike. Nirx bi logarîtma binar ya saniyeyan re wekhev e.
Derengiya root (derengiya pêşkêşkarê) - dema ku ew xwendinên demjimêrê digihîje servera NTP-ê, wekî jimareyek saniyeyê ya sabît.
Belavbûna root (belavbûna serverê) - belavkirina xwendinên demjimêra servera NTP-ê wekî hejmarek saniyeyan bi xalek sabît.
Ref id (nasnameya çavkaniyê) - nasnameya demjimêrê. Ger server strûma 1 hebe, wê hingê ref id navê demjimêra atomê ye (4 tîpên ASCII). Ger server serverek din bikar bîne, wê hingê ref id navnîşana vê serverê digire.
4 qadên dawîn demê temsîl dikin - 32 bit - beşa jimare, 32 bit - beşa perçe.
Balkêşî - xwendina demjimêra herî dawî ya li ser serverê.
Originate - dema ku pakêt hate şandin (ji hêla serverê ve hatî dagirtin - bêtir li ser vê jêrîn).
Bistînin - dema ku pakêt ji hêla serverê ve hatî wergirtin.
Weşandin - dema şandina pakêtê ji serverê ji xerîdar re (ji hêla xerîdar ve hatî dagirtin, li ser vê yekê bêtir li jêr).

Em ê du qadên dawî nebînin.

Ka em pakêta xwe binivîsin:

Koda pakêtê

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

Ji bo şandina (û wergirtina) pakêtê ji serverê re, divê em karibin wê veguherînin rêzek byte.
Ji bo vê operasyonê (û berevajî), em ê du fonksiyonan binivîsin - pack() û unpack():

fonksiyona pakkirinê

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

fonksiyonê vekin

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

Ji bo mirovên tembel, wekî serîlêdanek - kodek ku pakêtek vediguherîne rêzek bedew

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)

Ji serverê re pakêtek dişîne

Pêdivî ye ku pakêtek bi qadên dagirtî ji serverê re were şandin Awa, fashion и Weşandin. ew Weşandin divê hûn dema niha li ser makîneya herêmî diyar bikin (hejmara saniyeyan ji 1-ê Rêbendana 1900-an vir ve), guhertoyek - yek ji 1-4, moda - 3 (moda xerîdar).

Pêşkêşkar, ku daxwaz qebûl kir, hemî qadên pakêta NTP-ê dadigire, li qadê kopî dike. Originate nirx ji Weşandin, ku di daxwazê ​​de hat. Ji min re sir e ku çima xerîdar nikare tavilê nirxa dema xwe ya li zeviyê dagire Originate. Wekî encamek, dema ku pakêt vedigere, xerîdar 4 nirxên demê hene - dema ku daxwaz hatî şandin (Originate), wextê ku server daxwaz wergirt (Bistînin), dema ku server bersiv şand (Weşandin) û dema ku xerîdar bersiv wergirt - Gihîştin (ne di pakêtê de). Bi karanîna van nirxan em dikarin dema rast destnîşan bikin.

Koda şandin û wergirtina pakêtê

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

Daneyên pêvajoyê ji serverê

Çêkirina daneyan ji serverê dişibihe kirinên mîrzayê îngilîz ji pirsgirêka kevn a Raymond M. Smullyan (1978): "Zilamek demjimêra desta tune bû, lê li malê demjimêrek dîwarek rastîn hebû, ku wî carinan ji bîr dikir. bayê. Rojekê ji bîr kir ku saeta xwe dîsa ba bike, çû seredana hevalê xwe, êvarê pê re derbas kir û dema vegeriya malê, saetê rast saz kir. Ger wextê rêwîtiyê ji berê de nedihat zanîn, wî çawa ev yek kir? Bersiv ev e: “Dema ku mirov ji malê derdikeve, saeta xwe li ba dike û tîne bîra xwe ku dest di çi pozîsyonê de ne. Dema tê cem hevalekî xwe û ji mêvanan derdikeve, dema hatin û çûyîna xwe dihesibîne. Ev dihêle ku ew fêr bibe ka ew çiqas dem çû serdana. Mirov dema vedigere malê û li saetê dinêre, dema nebûna xwe diyar dike. Mirov dema ku ji vê demê di serdanê re derbas kiriye, dema ku li wir derbas kiriye û vedigere dibîne. Bi zêdekirina nîvê dema ku di rê de maye li dema derketina mêvanan, fersendê distîne ku dema hatina malê bibîne û li gorî vê yekê destikên saeta xwe rast bike.”

Wextê ku server li ser daxwazek dixebite bibînin:

  1. Dema rêwîtiya pakêtê ji xerîdar heya serverê bibînin: ((Gehîştin – Çêbûn) – (Ragihandin – Wergirtin)) / 2
  2. Cûdahiya di navbera dema xerîdar û serverê de bibînin:
    Bistîne - Bistîne - ((Gehîn - Bistîne) - (Rêvebike - Bistîne)) / 2 =
    2 * Wergirtin - 2 * Eslî - Gihîştin + Destpêk + Veguheztin - Wergirtin =
    Wergirtin - Destpêk - Gihîştin + Veguheztin

Em nirxa encam li dema herêmî zêde dikin û ji jiyanê kêfxweş dibin.

Derketina encam

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)

Bikartê pirtûk.

Source: www.habr.com

Add a comment