PĂ«rshĂ«ndetje, pĂ«rdorues tĂ« HabrĂ«s. Sot dua tĂ« flas pĂ«r mĂ«nyrĂ«n se si tĂ« shkruajmĂ« klientin tonĂ« tĂ« thjeshtĂ« NTP. Kryesisht, biseda do tĂ« fokusohet nĂ« strukturĂ«n e paketave dhe mĂ«nyrĂ«n e trajtimit tĂ« pĂ«rgjigjeve nga serveri NTP. Kodi do tĂ« jetĂ« shkruar nĂ« Python, sepse, siç mĂ« duket, nuk ka gjuhĂ« mĂ« tĂ« mirĂ« pĂ«r gjĂ«ra tĂ« tilla. EkspertĂ«t do tĂ« vĂ«rejnĂ« ngjashmĂ«rinĂ« e kodit me kodin e ntplib â unĂ« isha "inspiruar" nga ai.
Pra, çfarĂ« Ă«shtĂ« saktĂ«sisht NTP? NTP â Ă«shtĂ« protokolli i bashkĂ«veprimit me serverĂ«t e kohĂ«s sĂ« saktĂ«. Ky protokoll pĂ«rdoret nĂ« shumĂ« makina moderne. PĂ«r shembull, shĂ«rbimi w32tm nĂ« Windows.
Ka gjithsej 5 versione të protokollit NTP. Versioni i parë, versione 0 (1985, RFC958), aktualisht konsiderohet i përjetshëm. Tani përdoren versione më të reja, versioni 1 (1988, RFC1059), versioni 2 (1989, RFC1119), versioni 3 (1992, RFC1305) dhe versioni 4 (1996, RFC2030). Versionet 1-4 janë të pajtueshme me njëra-tjetrën, ato ndryshojnë vetëm në algoritet e funksionimit. servera.
Formati i paketës

Indikatori i leap (indikatori i korrektimit) â njĂ« numĂ«r qĂ« tregon njĂ« paralajmĂ«rim pĂ«r sekondĂ«n e koordinatave. Vlera:
- 0 â pa korrektim
- 1 â minuta e fundit e ditĂ«s pĂ«rmban 61 sekonda
- 2 â minuta e fundit e ditĂ«s pĂ«rmban 59 sekonda
- 3 â çështje tĂ« serverit (kohĂ« e pa sinkronizuar)
Numri i versionit (numri i versionit) â numri i versionit tĂ« protokollit NTP (1-4).
Rezhimi (rezhimi) â mĂ«nyra e funksionimit tĂ« dĂ«rguesit tĂ« paketave. Vlera Ă«shtĂ« nga 0 deri nĂ« 7, mĂ« tĂ« shpeshta:
- 3 â klient
- 4 â server
- 5 â mĂ«nyra e transmetimit tĂ« gj broadcaste
Stratum (niveli i shtresĂ«s) â numri i shtresave ndĂ«rmjet serverit dhe orĂ«ve referuese (1 â serveri merr tĂ« dhĂ«nat direkt nga orĂ«t referuese, 2 â serveri merr tĂ« dhĂ«nat nga njĂ« server me nivel 1 etj.).
Pool â njĂ« numĂ«r i plotĂ« me shenjĂ«, qĂ« paraqet intervalin maksimal ndĂ«rmjet mesazheve tĂ« njĂ«pasnjĂ«shme. NTP-klienti tregon kĂ«tu intervalin me tĂ« cilin planifikon tĂ« pyesĂ« serverin, ndĂ«rsa NTP-serveri â intervalin me tĂ« cilin ai planifikon tĂ« pyesĂ«. Vlera Ă«shtĂ« logaritmi binar i sekondave.
SaktĂ«sia (saktĂ«sia) â njĂ« numĂ«r i plotĂ« me shenjĂ«, qĂ« paraqet saktĂ«sinĂ« e orĂ«ve sistemike. Vlera Ă«shtĂ« logaritmi binar i sekondave.
Vonesa e rrĂ«njĂ«s (vonesa e serverit) â koha qĂ« merr pĂ«r tĂ« arritur leximi i orĂ«ve te NTP-serveri, si numĂ«r sekondash me pikĂ« fikse.
ShpĂ«rndarja e rrĂ«njĂ«s (shkalla e leximeve tĂ« serverit) â shkalla e leximeve tĂ« orĂ«s NTP-server si numĂ«r sekondash nga njĂ« pikĂ« e caktuar.
Ref id (identifikuesi i burimit) â id e orĂ«s. NĂ«se serveri ka stratumin 1, atĂ«herĂ« ref id â emri i orĂ«ve atomike (4 karaktere ASCII). NĂ«se serveri pĂ«rdor njĂ« server tjetĂ«r, atĂ«herĂ« nĂ« ref id ruhet adresa e kĂ«tij serveri.
KatĂ«r fushat e fundit pĂ«rbĂ«jnĂ« kohĂ«n â 32 bit â pjesa e plotĂ«, 32 bit â pjesa e thjeshtĂ«.
Referenca â leximet e fundit tĂ« orĂ«s nĂ« server.
Originate â koha kur paketa u dĂ«rgua (mbushĂ«t nga serveri â pĂ«r kĂ«tĂ« mĂ« poshtĂ«).
Receive â koha e marrjes sĂ« paketĂ«s nga serveri.
Transmit â koha e dĂ«rgesĂ«s sĂ« paketĂ«s nga serveri tek klienti (mbushĂ«t nga klienti, pĂ«r kĂ«tĂ« gjithashtu mĂ« poshtĂ«).
Fushat e fundit nuk do t'i shqyrtojmë.
Le të shkruajmë paketën tonë:
Kodi i paketës
class NTPPacket:
_FORMAT = "!B B b b 11I"
def __init__(self, version_number=2, mode=3, transmit=0):
# Nevojshëm për të hyrë në sekondën e kërcimit (2 bits)
self.leap_indicator = 0
# Versioni i protokollit (3 bits)
self.version_number = version_number
# Modaliteti i dërguesit (3 bits)
self.mode = mode
# Niveli i "shtresimit" të leximit të kohës (1 byte)
self.stratum = 0
# Intervali mes kërkesave (1 byte)
self.pool = 0
# Saktësia (log2) (1 byte)
self.precision = 0
# Intervali për arritjen e orës te serveri NTP (4 bytes)
self.root_delay = 0
# Shkalla e orës së serverit NTP (4 bytes)
self.root_dispersion = 0
# Treguesi i orëve (4 bytes)
self.ref_id = 0
# Koha e fundit e përditësimit në server (8 bytes)
self.reference = 0
# Koha e dërgimit të paketës nga makina lokale (8 bytes)
self.originate = 0
# Koha e marrjes në server (8 bytes)
self.receive = 0
# Koha e dërgimit të përgjigjes nga serveri (8 bytes)
self.transmit = transmit
Për të dërguar (dhe pranuar) një paketë në server, ne duhet të jemi në gjendje ta kthejmë atë në një varg bajtësh.
PĂ«r kĂ«tĂ« (dhe operacionin e kundĂ«rt) do tĂ« shkruajmĂ« dy funksione â pack() dhe unpack():
Funksioni pack
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))
Funksioni unpack
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
PĂ«r ata qĂ« janĂ« tĂ« lenient, si njĂ« aplikacion â kodi qĂ« kthen njĂ« paketĂ« nĂ« njĂ« varg tĂ« bukur
def to_display(self):
return "Tregues i leap: {0.leap_indicator}n"
"Numri i versionit: {0.version_number}n"
"Rezhimi: {0.mode}n"
"Stratum: {0.stratum}n"
"Pool: {0.pool}n"
"Saktësia: {0.precision}n"
"Vonesa e rrënjës: {0.root_delay}n"
"Shperndarja e rrënjës: {0.root_dispersion}n"
"Id referimi: {0.ref_id}n"
"Referenca: {0.reference}n"
"Origjino: {0.originate}n"
"Merr: {0.receive}n"
"Dërgo: {0.transmit}"
.format(self)
Dërgimi i paketës në server
Duhet të dërgoni një paketë në server me fushat e plota Version, Rezhimi dhe Transmit. Në Transmit duhet të specifikoni kohën aktuale në makinën lokale (numri i sekondave nga 1 janari 1900), versioni - cili do qoftë nga 1-4, rezhimi - 3 (rezhimi i klientit).
Serveri, pasi merr kĂ«rkesĂ«n, plotĂ«son nĂ« paketĂ«n NTP tĂ« gjitha fushat, duke kopjuar nĂ« fushĂ«n Originate vlerĂ«n nga Transmit, e cila erdhi nĂ« kĂ«rkesĂ«. PĂ«r mua Ă«shtĂ« njĂ« mister, pse klienti nuk mund tĂ« plotĂ«sojĂ« menjĂ«herĂ« vlerĂ«n e kohĂ«s sĂ« tij nĂ« fushĂ«n Originate. Si rezultat, kur paketa kthehet prapa, klienti ka 4 vlera tĂ« kohĂ«s â koha e dĂ«rgimit tĂ« kĂ«rkesĂ«s (Originate), koha e marrjes sĂ« kĂ«rkesĂ«s nga serveri (Receive), koha e dĂ«rgimit tĂ« pĂ«rgjigjes nga serveri (Transmit) dhe koha e marrjes sĂ« pĂ«rgjigjes nga klienti â Arrij (nuk Ă«shtĂ« nĂ« paketĂ«). Me kĂ«to vlera ne mund tĂ« vendosim kohĂ«n e saktĂ«.
Kodi i dërgimit dhe marrjes së paketës
# 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)
Përpunimi i të dhënave nga serveri
Përpunimi i të dhënave nga serveri është i ngjashëm me veprimet e një gentlemeni anglez nga detyra e vjetër e Raymond M. Smullyan (1978): «Një njeri nuk kishte orë dore, por në shtëpi kishte ora të sakta murale që ndonjëherë i haronte t'i vinte në punë. Një ditë, pasi harroi përsëri t'i vendosë orët, ai shkoi në vizitë te një mik, kaloi mbrëmjen atje, dhe kur u kthye në shtëpi, arriti të vendosë saktë orët. Si arriti ta bëjë këtë, nëse koha e udhëtimit nuk dihej paraprakisht?» Përgjigja është: «Te dalë nga shtëpia, njeriu vendos orët dhe e mbështet veten në cilat pozita janë akrepat. Në vizitë te miku, ai shënon orën e ardhjes dhe ikjes. Kjo i lejon të dijë sa kohë kaloi te miku. Duke u kthyer në shtëpi dhe duke parë orët, njeriu përcakton kohëzgjatjen e mungesës së tij. Duke shkarkuar nga kjo kohë atë që kaloi te miku, ai e kupton kohën e kaluar në rrugë. Duke shtuar në kohën e daljes nga mikushin gjysmën e kohës së kaluar, ai arrin të dijë orën e kthimit në shtëpi dhe të rregullojë akrepat e orëve të tij përkatësisht.»
Po kërkimin e kohës së punës së serverit mbi kërkesën:
- Po kĂ«rkimin e kohĂ«s sĂ« rrugĂ«s sĂ« paketĂ«s nga klienti nĂ« server: ((Arrive â Originate) â (Transmit â Receive)) / 2
- Po kërkimin e diferencës në mes të kohës së klientit dhe serverit:
Receive â Originate â ((Arrive â Originate) â (Transmit â Receive)) / 2 =
2 * Receive â 2 * Originate â Arrive + Originate + Transmit â Receive =
Receive â Originate â Arrive + Transmit
Shtojmë vlerën e nxjerrë në kohën lokale dhe gëzohemi për jetën.
Shfaq rezultatin
time_different = answer.get_time_different(arrive_time)
result = "Diferenca e kohës: {}nKoha e serverit: {}n{}".format(
time_different,
datetime.datetime.fromtimestamp(time.time() + time_different).strftime("%c"),
answer.to_display())
print(result)
E dobishme .
Burimi: habr.com
