Ysgrifennu cleient NTP syml

Helo, Habrausers. Heddiw, rwyf am siarad am sut i ysgrifennu eich cleient NTP syml eich hun. Yn y bôn, bydd y sgwrs yn troi at strwythur y pecyn a'r dull o brosesu'r ymateb gan y gweinydd NTP. Bydd y cod yn cael ei ysgrifennu yn Python, oherwydd mae'n ymddangos i mi nad oes iaith well ar gyfer pethau o'r fath. Bydd connoisseurs yn nodi tebygrwydd y cod â’r cod ntplib – cefais fy “ysbrydoli” ganddo.

Felly beth yn union yw NTP? Mae NTP yn brotocol ar gyfer rhyngweithio â gweinyddwyr union amser. Defnyddir y protocol hwn mewn llawer o beiriannau modern. Er enghraifft, y gwasanaeth w32tm mewn ffenestri.

Mae cyfanswm o 5 fersiwn o'r protocol NTP. Mae'r gyntaf, fersiwn 0 (1985, RFC958)), yn cael ei hystyried yn anarferedig ar hyn o bryd. Nawr defnyddir y rhai mwy newydd, 1af (1988, RFC1059), 2il (1989, RFC1119), 3ydd (1992, RFC1305) a 4ydd (1996, RFC2030). Mae fersiynau 1-4 yn gydnaws â'i gilydd; dim ond yn algorithmau gweithredu'r gweinydd y maent yn wahanol.

Fformat pecyn

Ysgrifennu cleient NTP syml

Dangosydd naid (dangosydd cywiro) - rhif sy'n nodi rhybudd am y cydlyniad yn ail. Ystyr:

  • 0 – dim cywiriad
  • 1 – mae munud olaf y dydd yn cynnwys 61 eiliad
  • 2 – mae munud olaf y dydd yn cynnwys 59 eiliad
  • 3 - camweithio gweinydd (nid yw amser wedi'i gysoni)

Rhif fersiwn (rhif fersiwn) – rhif fersiwn protocol NTP (1-4).

modd (modd) - modd gweithredu anfonwr y pecyn. Gwerth o 0 i 7, y mwyaf cyffredin:

  • 3 - cleient
  • 4 - gweinydd
  • 5 – modd darlledu

Stratwm (lefel haenu) - nifer yr haenau canolradd rhwng y gweinydd a'r cloc cyfeirio (1 - mae'r gweinydd yn cymryd data yn uniongyrchol o'r cloc cyfeirio, 2 - mae'r gweinydd yn cymryd data o weinydd gyda haen 1, ac ati).
pwll yn gyfanrif wedi'i lofnodi sy'n cynrychioli'r cyfnod hiraf rhwng negeseuon olynol. Mae'r cleient NTP yn nodi yma yr egwyl y mae'n disgwyl pleidleisio'r gweinydd, ac mae'r gweinydd NTP yn pennu'r cyfnod y mae'n disgwyl cael ei bleidleisio. Mae'r gwerth yn hafal i logarithm deuaidd eiliadau.
Precision (cywirdeb) yn gyfanrif wedi'i lofnodi sy'n cynrychioli cywirdeb cloc y system. Mae'r gwerth yn hafal i logarithm deuaidd eiliadau.
Oedi gwraidd (oedi gweinydd) – yr amser mae'n ei gymryd i ddarlleniadau'r cloc gyrraedd y gweinydd NTP, fel nifer pwynt sefydlog o eiliadau.
Gwasgariad gwreiddiau (lledaeniad gweinydd) - lledaeniad darlleniadau cloc gweinydd NTP fel nifer o eiliadau gyda phwynt sefydlog.
Cyf id (dynodwr ffynhonnell) – cloc id. Os oes gan y gweinydd haen 1, yna cyf id yw enw'r cloc atomig (4 nod ASCII). Os yw'r gweinydd yn defnyddio gweinydd arall, yna mae'r cyfeirnod yn cynnwys cyfeiriad y gweinydd hwn.
Mae'r 4 maes olaf yn cynrychioli'r amser - 32 did - y rhan gyfanrif, 32 did - y rhan ffracsiynol.
Cyfeirnod - y darlleniadau cloc diweddaraf ar y gweinydd.
Tarddiad – amser pan anfonwyd y pecyn (wedi'i lenwi gan y gweinydd - mwy am hyn isod).
Derbyn – amser derbyn y pecyn gan y gweinydd.
Trosglwyddo - amser anfon y pecyn o'r gweinydd i'r cleient (wedi'i lenwi gan y cleient, mwy am hyn isod).

Ni fyddwn yn ystyried y ddau faes olaf.

Gadewch i ni ysgrifennu ein pecyn:

Cod pecyn

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

Er mwyn anfon (a derbyn) pecyn i'r gweinydd, mae'n rhaid i ni allu ei droi'n arae beit.
Ar gyfer y gweithrediad hwn (a gwrthdroi), byddwn yn ysgrifennu dwy swyddogaeth - pecyn () a dadbacio ():

swyddogaeth pecyn

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

swyddogaeth dadbacio

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

Ar gyfer pobl ddiog, fel cais - cod sy'n troi pecyn yn llinyn hardd

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)

Anfon pecyn i'r gweinydd

Rhaid anfon pecyn gyda meysydd wedi'u llenwi i'r gweinydd fersiwn, modd и Trosglwyddo. Yn Trosglwyddo rhaid i chi nodi'r amser presennol ar y peiriant lleol (y nifer o eiliadau ers Ionawr 1, 1900), fersiwn - unrhyw un o 1-4, modd - 3 (modd cleient).

Mae'r gweinydd, ar ôl derbyn y cais, yn llenwi'r holl feysydd yn y pecyn NTP, gan gopïo i'r maes Tarddiad gwerth o Trosglwyddo, a ddaeth yn y cais. Mae'n ddirgelwch i mi pam na all y cleient lenwi ar unwaith werth ei amser yn y maes Tarddiad. O ganlyniad, pan ddaw'r pecyn yn ôl, mae gan y cleient 4 gwerth amser - yr amser yr anfonwyd y cais (Tarddiad), yr amser y derbyniodd y gweinydd y cais (Derbyn), amser anfonodd y gweinydd yr ymateb (Trosglwyddo) a'r amser y derbyniodd y cleient yr ymateb - Cyrraedd (ddim yn y pecyn). Gan ddefnyddio'r gwerthoedd hyn gallwn osod yr amser cywir.

Côd anfon a derbyn pecyn

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

Prosesu data o'r gweinydd

Mae prosesu data o'r gweinydd yn debyg i weithredoedd y gŵr bonheddig o Loegr o hen broblem Raymond M. Smullyan (1978): “Nid oedd gan un dyn oriawr arddwrn, ond roedd cloc wal cywir gartref, a anghofiodd weithiau i gwynt. Un diwrnod, wedi anghofio dirwyn ei oriawr eilwaith, aeth i ymweled â'i gyfaill, treuliodd y noson gydag ef, a phan ddychwelodd adref, llwyddodd i osod yr oriawr yn gywir. Sut llwyddodd i wneud hyn os nad oedd yr amser teithio yn hysbys ymlaen llaw? Yr ateb yw: “Wrth adael cartref, mae person yn dirwyn ei oriawr ac yn cofio ym mha safle mae’r dwylo. Wedi dod at ffrind a gadael y gwesteion, mae'n nodi amser ei gyrraedd a'i ymadawiad. Mae hyn yn caniatáu iddo ddarganfod pa mor hir yr oedd yn ymweld. Wrth ddychwelyd adref ac edrych ar y cloc, mae person yn pennu hyd ei absenoldeb. Trwy dynnu o'r amser hwn yr amser a dreuliodd yn ymweld, mae person yn darganfod yr amser a dreulir yn teithio yno ac yn ôl. Trwy ychwanegu hanner yr amser a dreulir ar y ffordd at amser gadael y gwesteion, mae’n cael y cyfle i ddarganfod yr amser cyrraedd adref ac addasu dwylo ei oriawr yn unol â hynny.”

Darganfyddwch yr amser mae'r gweinydd yn gweithio ar gais:

  1. Dewch o hyd i amser teithio'r pecyn o'r cleient i'r gweinydd: ((Cyrraedd – Tarddiad) – (Trosglwyddo – Derbyn)) / 2
  2. Darganfyddwch y gwahaniaeth rhwng amser cleient ac amser gweinydd:
    Derbyn - Tarddu - ((Cyrraedd - Tarddu) - (Trosglwyddo - Derbyn)) / 2 =
    2 * Derbyn - 2 * Tarddu - Cyrraedd + Tarddu + Trosglwyddo - Derbyn =
    Derbyn - Tarddu - Cyrraedd + Trosglwyddo

Rydym yn ychwanegu'r gwerth canlyniadol i'r amser lleol ac yn mwynhau bywyd.

Allbwn y canlyniad

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)

Defnyddiol cyswllt.

Ffynhonnell: hab.com

Ychwanegu sylw