Hei habrabrukere. I dag vil jeg snakke om hvordan du skriver din egen enkle NTP-klient. I utgangspunktet vil samtalen dreie seg om strukturen til pakken og hvordan svaret fra NTP-serveren behandles. Koden vil bli skrevet i python, fordi det etter min mening rett og slett ikke finnes noe bedre sprÄk for slike ting. Kjennere vil ta hensyn til likheten mellom koden og ntplib-koden - jeg ble "inspirert" av den.
SÄ hva er NTP egentlig? NTP er en protokoll for kommunikasjon med tidsservere. Denne protokollen brukes i mange moderne maskiner. For eksempel w32tm-tjenesten pÄ Windows.
Det finnes totalt 5 versjoner av NTP-protokollen. Den fĂžrste, versjon 0 (1985, RFC958), regnes for tiden som foreldet. Nyere versjoner er for tiden i bruk: versjon 1 (1988, RFC1059), versjon 2 (1989, RFC1119), versjon 3 (1992, RFC1305) og versjon 4 (1996, RFC2030). Versjon 1â4 er kompatible med hverandre; de ââskiller seg bare i driftsalgoritmene. servere.
Pakkeformat

Sprangindikator (korreksjonsindikator) er et tall som indikerer sprangsekundvarselet. Betydning:
- 0 - ingen korreksjon
- 1 - dagens siste minutt inneholder 61 sekunder
- 2 - dagens siste minutt inneholder 59 sekunder
- 3 - serverfeil (tid ute av synkronisering)
Versjonsnummer (versjonsnummer) â NTP-protokollens versjonsnummer (1-4).
Mote (modus) â driftsmodus for pakkesenderen. Verdi fra 0 til 7, vanligst:
- 3 - klient
- 4 - server
- 5 - kringkastingsmodus
stratum (lagnivÄ) - antall mellomlag mellom serveren og referanseklokken (1 - serveren tar data direkte fra referanseklokken, 2 - serveren tar data fra serveren med nivÄ 1 osv.).
Basseng er et signert heltall som representerer det maksimale intervallet mellom pÄfÞlgende meldinger. NTP-klienten spesifiserer her intervallet den forventer Ä polle serveren med, og NTP-serveren intervallet den forventer Ä bli pollet med. Verdien er lik den binÊre logaritmen av sekunder.
Precision (presisjon) er et fortegnet heltall som representerer nĂžyaktigheten til systemklokken. Verdien er lik den binĂŠre logaritmen av sekunder.
rotforsinkelse (serverlatens) er tiden det tar for klokken Ä nÄ NTP-serveren, som et fast punkt antall sekunder.
rotspredning (serverspredning) - Spredningen av NTP-serverklokken som et fast punkt antall sekunder.
Ref id (kilde-id) â se-id. Hvis serveren har stratum 1, er ref id navnet pĂ„ atomuret (4 ASCII-tegn). Hvis serveren bruker en annen server, inneholder ref-ID-en adressen til denne serveren.
De siste 4 feltene representerer tiden - 32 biter - heltallsdelen, 32 biter - brĂžkdelen.
Referanse - den siste klokken pÄ serveren.
Opprinnelse â tidspunktet da pakken ble sendt (utfylt av serveren â mer om det nedenfor).
Motta â tidspunktet da pakken ble mottatt av serveren.
OverfĂžre â tidspunktet da pakken ble sendt fra serveren til klienten (utfylt av klienten, mer om det nedenfor).
De to siste feltene vil ikke bli vurdert.
La oss skrive pakken vÄr:
Pakkekode
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
For Ä sende (og motta) en pakke til serveren, mÄ vi kunne gjÞre den om til en rekke byte.
For denne (og omvendt) operasjonen vil vi skrive to funksjoner - pack() og unpack():
pakkefunksjon
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))
pakke ut funksjon
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
For late mennesker, som en applikasjon - kode som gjĂžr pakken til en vakker streng
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)
Sender en pakke til serveren
Send en pakke med utfylte felt til serveren Versjon, Mote О OverfÞre. I OverfÞre du mÄ spesifisere gjeldende tid pÄ den lokale maskinen (antall sekunder siden 1. januar 1900), versjon - hvilken som helst av 1-4, modus - 3 (klientmodus).
Serveren, etter Ä ha mottatt forespÞrselen, fyller ut alle feltene i NTP-pakken og kopierer inn i feltet Opprinnelse verdi fra OverfÞre, som kom i forespÞrselen. Det er et mysterium for meg hvorfor klienten ikke umiddelbart kan fylle ut verdien av sin tid i felten Opprinnelse. Som et resultat, nÄr pakken kommer tilbake, har klienten 4 tidsverdier - tidspunktet forespÞrselen ble sendt (Opprinnelse), tidspunktet serveren mottok forespÞrselen (Motta), tidspunktet serveren sendte svaret (OverfÞre) og tidspunktet for mottak av et svar fra klienten - Ankomme (ikke i pakken). Med disse verdiene kan vi stille inn riktig tid.
Kode for sending og mottak av pakke
# 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)
Databehandling fra serveren
Behandlingen av data fra serveren ligner handlingene til den engelske gentlemannen fra det gamle problemet til Raymond M. Smallian (1978): «En person hadde ikke et armbÄndsur, men det var en nÞyaktig veggklokke hjemme, som han noen ganger glemte Ä vind. En dag, da han glemte Ä starte klokken igjen, dro han for Ä besÞke vennen sin, tilbrakte kvelden med ham, og da han kom hjem klarte han Ä stille klokken riktig. Hvordan klarte han Ä gjÞre dette hvis reisetiden ikke var kjent pÄ forhÄnd? Svaret er: «NÄr en person forlater huset, trekker en opp klokken og husker posisjonen til viserne. NÄr han kommer til en venn og forlater gjestene, noterer han tidspunktet for ankomst og avreise. Dette lar ham finne ut hvor lenge han var borte. NÄr han kommer hjem og ser pÄ klokken, bestemmer en person varigheten av fravÊret. Ved Ä trekke fra denne tiden tiden han brukte pÄ besÞk, finner personen ut tiden han brukte pÄ veien frem og tilbake. Ved Ä legge til halvparten av tiden brukt pÄ veien til tidspunktet for Ä forlate gjestene, fÄr han muligheten til Ä finne ut nÄr han kommer hjem og justere viserne pÄ klokken deretter.
Finn tidspunktet serveren jobbet med forespĂžrselen:
- Finne pakkereisetiden fra klienten til serveren: ((Ankomme - Opprinnelse) - (Sende - Motta)) / 2
- Finn forskjellen mellom klient- og servertid:
Motta - Opprinne - ((Ankomme - Opprinne) - (Sende - Motta)) / 2 =
2 * Motta - 2 * Opprinne - Ankomme + Opprinne + Sende - Motta =
Motta - Opprinne - Ankomme + Send
Vi legger den mottatte verdien til den lokale tiden og nyter livet.
Resultatutgang
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)
Nyttig .
Kilde: www.habr.com
