NTP bezero soil bat idaztea

Kaixo, Habrausers. Gaur zure NTP bezero sinplea nola idatzi hitz egin nahi dut. Funtsean, elkarrizketa paketearen egitura eta NTP zerbitzariaren erantzuna prozesatzeko metodora joko du. Kodea Python-en idatziko da, iruditzen baitzait ez dagoela hizkuntza hoberik horrelako gauzetarako. Jakintsuek kodearen antzekotasuna nabarituko dute ntplib kodearekin - "inspiratu" nintzen.

Beraz, zer da zehazki NTP? NTP ordu zehatzeko zerbitzariekin elkarreragiteko protokoloa da. Protokolo hau makina moderno askotan erabiltzen da. Adibidez, w32tm zerbitzua windows-en.

NTP protokoloaren 5 bertsio daude guztira. Lehena, 0 bertsioa (1985, RFC958)), gaur egun zaharkituta dago. Orain berriak erabiltzen dira, 1. (1988, RFC1059), 2. (1989, RFC1119), 3. (1992, RFC1305) eta 4. (1996, RFC2030). 1-4 bertsioak elkarren artean bateragarriak dira; zerbitzariaren funtzionamendu-algoritmoetan soilik desberdinak dira.

Pakete formatua

NTP bezero soil bat idaztea

Jauzi adierazlea (zuzenketa adierazlea) - koordinazio-segundoari buruzko abisua adierazten duen zenbakia. Esanahia:

  • 0 - ez dago zuzenketarik
  • 1 - eguneko azken minutuak 61 segundo ditu
  • 2 - eguneko azken minutuak 59 segundo ditu
  • 3 - zerbitzariaren matxura (ordua ez dago sinkronizatuta)

Bertsio zenbakia (bertsio-zenbakia) – NTP protokoloaren bertsio-zenbakia (1-4).

modua (modea) β€” pakete igorlearen funtzionamendu modua. 0tik 7ra arteko balioa, ohikoena:

  • 3 - bezeroa
  • 4 – zerbitzaria
  • 5 - Igorpen modua

Estratua (geruza maila) – zerbitzariaren eta erreferentziako erlojuaren arteko tarteko geruzen kopurua (1 – zerbitzariak zuzenean erreferentziako erlojutik hartzen ditu datuak, 2 – zerbitzariak 1. geruza duen zerbitzari batetik datuak hartzen ditu, etab.).
Igerilekua ondoz ondoko mezuen arteko gehienezko tartea adierazten duen zenbaki oso sinatua da. NTP bezeroak zerbitzaria galdeketa egitea espero duen tartea zehazten du hemen, eta NTP zerbitzariak galdeketa egin nahi duen tartea zehazten du. Balioa segundoko logaritmo bitarraren berdina da.
Zehaztasun (zehaztasuna) sistemako erlojuaren zehaztasuna adierazten duen zenbaki oso sinatua da. Balioa segundoko logaritmo bitarraren berdina da.
Erro atzerapena (zerbitzariaren atzerapena) - erlojuaren irakurketak NTP zerbitzarira iristeko behar duen denbora, segundoko puntu finko gisa.
Erroen sakabanaketa (zerbitzariaren hedapena) - NTP zerbitzariaren erlojuaren irakurketak segundotan hedatzea puntu finko batekin.
Erref id (iturriaren identifikatzailea) – erlojuaren id. Zerbitzariak 1. estratoa badu, ref id erloju atomikoaren izena da (4 ASCII karaktere). Zerbitzariak beste zerbitzari bat erabiltzen badu, erref idak zerbitzari honen helbidea dauka.
Azken 4 eremuek denbora adierazten dute - 32 bit - zati osoa, 32 bit - zati zatikia.
Erreferentzia β€” zerbitzariko erlojuaren azken irakurketak.
Jatorria – paketea bidali zeneko ordua (zerbitzariak beteta - behean honi buruzko informazio gehiago).
Jaso – zerbitzariak paketea jaso duen ordua.
Transmititu – paketea zerbitzaritik bezeroari bidaltzeko ordua (bezeroak beteta, honetaz gehiago behean).

Ez ditugu azken bi eremuak kontuan hartuko.

Idatz dezagun gure paketea:

Paketearen kodea

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

Zerbitzariari pakete bat bidaltzeko (eta jasotzeko), byte array bihurtzeko gai izan behar dugu.
Eragiketa honetarako (eta alderantzizkoa) bi funtzio idatziko ditugu: pack() eta unpack():

pakete funtzioa

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

deskonprimitu funtzioa

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

Alferrarentzat, aplikazio gisa - pakete bat kate eder bihurtzen duen kodea

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)

Zerbitzariari pakete bat bidaltzea

Eremu beteak dituen pakete bat zerbitzarira bidali behar da Bertsio, modua ΠΈ Transmititu. Urtean Transmititu tokiko makinan uneko ordua zehaztu behar duzu (1ko urtarrilaren 1900etik segundu kopurua), bertsioa - 1-4 edozein, modua - 3 (bezero modua).

Zerbitzariak, eskaera onartu ondoren, NTP paketearen eremu guztiak betetzen ditu, eremuan kopiatuz. Jatorria baliotik Transmititu, eskaeran etorri zena. Niretzat misterio bat da zergatik bezeroak ezin duen berehala bete eremuan duen denboraren balioa Jatorria. Ondorioz, paketea itzultzen denean, bezeroak 4 denbora balio ditu - eskaera bidali zen ordua (Jatorria), zerbitzariak eskaera jaso duen ordua (Jaso), zerbitzariak erantzuna bidali duen ordua (Transmititu) eta bezeroak erantzuna jaso zuen ordua - iritsiko (ez paketean). Balio hauek erabiliz ordu egokia ezarri dezakegu.

Paketeak bidaltzeko eta jasotzeko kodea

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

Zerbitzaritik datuak prozesatzea

Zerbitzariaren datuak prozesatzea Raymond M. Smullyan-en (1978) arazo zaharreko jaun ingelesaren ekintzen antzekoa da: β€œGizon batek ez zuen eskumuturreko erlojurik, baina horma-erloju zehatz bat zegoen etxean, batzuetan ahaztu egiten zuena. haizeari. Egun batean, erlojua berriro haizea ematea ahaztuta, bere laguna bisitatzera joan zen, arratsaldea berarekin pasa zuen eta etxera itzuli zenean, erlojua behar bezala jartzea lortu zuen. Nola lortu zuen hori egitea bidaia-denbora aldez aurretik ezagutzen ez bazen? Erantzuna hauxe da: β€œEtxetik irtetean, pertsona batek bere erlojua haizea ematen du eta eskuak zein posiziotan dauden gogoratzen du. Lagun batengana etorri eta gonbidatuak utzita, iritsi eta irteteko ordua jasotzen du. Horri esker, zenbat denbora egon zen bisitan jakiteko. Etxera itzuli eta erlojuari begira, pertsona batek bere absentziaren iraupena zehazten du. Denbora horri bisitan eman zuen denbora kenduz gero, pertsona batek hara eta itzulera bidaiatzen emandako denbora jakiten du. Gonbidatuak irteteko denborari errepidean emandako denboraren erdia gehituz, etxera iristeko ordua jakiteko eta bere erlojuaren orratzak egokitzeko aukera duΒ».

Aurkitu zerbitzariak eskaera batean lan egiten duen ordua:

  1. Aurkitu paketearen bidaia-denbora bezerotik zerbitzarira: ((Iritsi – Sortu) – (Igorri – Jaso)) / 2
  2. Aurkitu bezeroaren eta zerbitzariaren denboraren arteko aldea:
    Jaso - Sortu - ((Iritsi - Sortu) - (Igorri - Jaso)) / 2 =
    2 * Jaso - 2 * Sortu - Iritsi + Sortu + Igorri - Jaso =
    Jaso – Sortu – Iritsi + Igorri

Bertako orduari ematen diogun balioa gehitzen diogu eta bizitzaz gozatzen dugu.

Emaitzaren irteera

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)

Baliagarria link.

Iturria: www.habr.com

Gehitu iruzkin berria