ایک سادہ NTP کلائنٹ لکھنا

ہیلو، Habrausers. آج میں آپ کے اپنے سادہ NTP کلائنٹ کو لکھنے کے طریقے کے بارے میں بات کرنا چاہتا ہوں۔ بنیادی طور پر، بات چیت پیکٹ کی ساخت اور NTP سرور سے جواب پر کارروائی کرنے کے طریقہ کار کی طرف مڑ جائے گی۔ کوڈ Python میں لکھا جائے گا، کیونکہ مجھے لگتا ہے کہ ایسی چیزوں کے لیے کوئی بہتر زبان نہیں ہے۔ ماہرین ntplib کوڈ کے ساتھ کوڈ کی مماثلت کو نوٹ کریں گے - میں اس سے "متاثر" ہوا تھا۔

تو بالکل NTP کیا ہے؟ NTP عین وقت کے سرورز کے ساتھ تعامل کے لیے ایک پروٹوکول ہے۔ یہ پروٹوکول بہت سی جدید مشینوں میں استعمال ہوتا ہے۔ مثال کے طور پر، ونڈوز میں w32tm سروس۔

NTP پروٹوکول کے کل 5 ورژن ہیں۔ پہلا، ورژن 0 (1985، RFC958))، فی الحال متروک سمجھا جاتا ہے۔ اب نئے استعمال کیے جاتے ہیں، پہلا (1، RFC1988)، دوسرا (1059، RFC2)، تیسرا (1989، RFC1119) اور چوتھا (3، RFC1992)۔ ورژن 1305-4 ایک دوسرے کے ساتھ مطابقت رکھتے ہیں؛ وہ صرف سرور آپریشن الگورتھم میں مختلف ہیں۔

پیکیج کی شکل

ایک سادہ NTP کلائنٹ لکھنا

لیپ انڈیکیٹر (تصحیح اشارے) - ایک نمبر جو کوآرڈینیشن سیکنڈ کے بارے میں انتباہ کا اشارہ کرتا ہے۔ معنی:

  • 0 - کوئی اصلاح نہیں۔
  • 1 - دن کے آخری منٹ میں 61 سیکنڈ ہوتے ہیں۔
  • 2 - دن کے آخری منٹ میں 59 سیکنڈ ہوتے ہیں۔
  • 3 - سرور کی خرابی (وقت مطابقت پذیر نہیں ہے)

ورژن نمبر (ورژن نمبر) – NTP پروٹوکول ورژن نمبر (1-4)۔

موڈ (موڈ) - پیکٹ بھیجنے والے کا آپریٹنگ موڈ۔ 0 سے 7 تک کی قدر، سب سے عام:

  • 3 - کلائنٹ
  • 4 - سرور
  • 5 - براڈکاسٹ موڈ

اسٹراٹم (لیئرنگ لیول) - سرور اور حوالہ گھڑی کے درمیان درمیانی تہوں کی تعداد (1 - سرور براہ راست حوالہ گھڑی سے ڈیٹا لیتا ہے، 2 - سرور پرت 1 وغیرہ والے سرور سے ڈیٹا لیتا ہے)۔
پول ایک دستخط شدہ عدد ہے جو لگاتار پیغامات کے درمیان زیادہ سے زیادہ وقفہ کی نمائندگی کرتا ہے۔ این ٹی پی کلائنٹ یہاں اس وقفہ کی وضاحت کرتا ہے جس پر وہ سرور کو پول کرنے کی توقع رکھتا ہے، اور این ٹی پی سرور اس وقفہ کی وضاحت کرتا ہے جس پر اسے پول ہونے کی توقع ہے۔ قدر سیکنڈ کے بائنری لوگارتھم کے برابر ہے۔
صحت سے متعلق (درستگی) ایک دستخط شدہ عدد ہے جو سسٹم کلاک کی درستگی کو ظاہر کرتا ہے۔ قدر سیکنڈ کے بائنری لوگارتھم کے برابر ہے۔
جڑ میں تاخیر (سرور میں تاخیر) – گھڑی کی ریڈنگز کو NTP سرور تک پہنچنے میں جو وقت لگتا ہے، سیکنڈوں کی ایک مقررہ پوائنٹ نمبر کے طور پر۔
جڑ کی بازی (سرور اسپریڈ) - ایک مقررہ نقطہ کے ساتھ کئی سیکنڈ کے طور پر NTP سرور کلاک ریڈنگ کا پھیلاؤ۔
حوالہ آئی ڈی (ذریعہ شناخت کنندہ) - گھڑی کی شناخت۔ اگر سرور کا درجہ 1 ہے، تو ریف آئی ڈی ایٹم کلاک کا نام ہے (4 ASCII حروف)۔ اگر سرور دوسرا سرور استعمال کرتا ہے، تو ریف آئی ڈی میں اس سرور کا پتہ ہوتا ہے۔
آخری 4 فیلڈز وقت کی نمائندگی کرتے ہیں - 32 بٹس - انٹیجر حصہ، 32 بٹس - فریکشنل حصہ۔
حوالہ - سرور پر تازہ ترین گھڑی کی ریڈنگ۔
شروع کریں - وہ وقت جب پیکٹ بھیجا گیا تھا (سرور کے ذریعہ بھرا ہوا تھا - ذیل میں اس پر مزید)۔
وصول - سرور کے ذریعہ پیکٹ موصول ہونے کا وقت۔
ترسیل - سرور سے کلائنٹ کو پیکٹ بھیجنے کا وقت (کلائنٹ کے ذریعہ بھرا ہوا، ذیل میں اس پر مزید)۔

ہم آخری دو شعبوں پر غور نہیں کریں گے۔

آئیے اپنا پیکیج لکھیں:

پیکیج کوڈ

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

سرور کو ایک پیکٹ بھیجنے (اور وصول کرنے) کے لیے، ہمیں اسے بائٹ صف میں تبدیل کرنے کے قابل ہونا چاہیے۔
اس (اور ریورس) آپریشن کے لیے، ہم دو فنکشن لکھیں گے - pack() اور unpack():

پیک فنکشن

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

پیک کھولنے کی تقریب

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

سست لوگوں کے لیے، ایک ایپلیکیشن کے طور پر - کوڈ جو ایک پیکج کو ایک خوبصورت تار میں بدل دیتا ہے۔

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)

سرور کو ایک پیکج بھیجنا

بھرے ہوئے کھیتوں کے ساتھ ایک پیکٹ سرور کو بھیجنا ضروری ہے۔ ورژن, موڈ и ترسیل. میں ترسیل آپ کو مقامی مشین پر موجودہ وقت (1 جنوری 1900 سے سیکنڈوں کی تعداد)، ورژن - 1-4 میں سے کوئی بھی، موڈ - 3 (کلائنٹ موڈ) کی وضاحت کرنی چاہیے۔

سرور، درخواست کو قبول کرنے کے بعد، این ٹی پی پیکٹ میں تمام فیلڈز کو بھرتا ہے، اس فیلڈ میں کاپی کرتا ہے شروع کریں سے قدر ترسیل، جو درخواست میں آیا تھا۔ یہ میرے لیے ایک معمہ ہے کہ موکل فوری طور پر فیلڈ میں اپنے وقت کی قیمت کیوں نہیں بھر سکتا شروع کریں. نتیجے کے طور پر، جب پیکٹ واپس آتا ہے، کلائنٹ کے پاس 4 وقت کی قدر ہوتی ہے - جب درخواست بھیجی گئی تھی (شروع کریں)، جب سرور کو درخواست موصول ہوئی (وصول)، جب سرور نے جواب بھیجا (ترسیل) اور کلائنٹ کو جواب موصول ہونے کا وقت - آو (پیکیج میں نہیں)۔ ان اقدار کو استعمال کرتے ہوئے ہم صحیح وقت کا تعین کر سکتے ہیں۔

پیکیج بھیجنے اور وصول کرنے کا کوڈ

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

سرور سے ڈیٹا پر کارروائی ہو رہی ہے۔

سرور سے ڈیٹا پر کارروائی کرنا ریمنڈ ایم سملیان (1978) کے پرانے مسئلے سے انگریز شریف آدمی کے اقدامات سے ملتا جلتا ہے: "ایک آدمی کے پاس کلائی کی گھڑی نہیں تھی، لیکن گھر میں ایک درست دیوار گھڑی تھی، جسے وہ کبھی کبھی بھول جاتا تھا۔ ختم کرنے کے لئے. ایک دن، وہ اپنی گھڑی کو دوبارہ سمیٹنا بھول گیا، وہ اپنے دوست سے ملنے گیا، شام اس کے ساتھ گزاری، اور جب وہ گھر واپس آیا، تو اس نے گھڑی کو درست طریقے سے ترتیب دیا۔ اگر سفر کا وقت پہلے سے معلوم نہیں تھا تو اس نے ایسا کیسے کیا؟ جواب یہ ہے: "گھر سے نکلتے وقت، ایک شخص اپنی گھڑی کو سمیٹتا ہے اور یاد کرتا ہے کہ ہاتھ کس پوزیشن میں ہیں۔ کسی دوست کے پاس آکر مہمانوں کو رخصت کرتے ہوئے وہ اپنے آنے اور جانے کا وقت نوٹ کرتا ہے۔ اس سے وہ یہ معلوم کرنے کی اجازت دیتا ہے کہ وہ کتنے عرصے سے دورہ کر رہا تھا۔ گھر واپس آکر گھڑی دیکھ کر آدمی اپنی غیر حاضری کی مدت کا تعین کرتا ہے۔ اس وقت سے اس وقت کو گھٹا کر جو اس نے دورہ کرنے میں گزارا، ایک شخص کو معلوم ہوتا ہے کہ وہاں اور واپس سفر میں کتنا وقت گزرا ہے۔ مہمانوں کو رخصت کرنے کے وقت میں سڑک پر گزارے گئے آدھے وقت کو جوڑ کر اسے گھر پہنچنے کا وقت معلوم کرنے اور اس کے مطابق اپنی گھڑی کے ہاتھ ایڈجسٹ کرنے کا موقع ملتا ہے۔

درخواست پر سرور کام کرنے کا وقت تلاش کریں:

  1. کلائنٹ سے سرور تک پیکٹ کے سفر کا وقت تلاش کریں: ((پہنچنا – شروع کرنا) – (منتقل کرنا – وصول کرنا)) / 2
  2. کلائنٹ اور سرور کے وقت کے درمیان فرق تلاش کریں:
    وصول کریں - شروع کریں - ((پہنچیں - شروع کریں) - (منتقل کریں - وصول کریں)) / 2 =
    2 * وصول کریں - 2 * شروع کریں - پہنچیں + شروع کریں + منتقل کریں - وصول کریں =
    وصول کریں - شروع کریں - پہنچیں + منتقل کریں۔

ہم نتیجہ خیز قیمت کو مقامی وقت میں شامل کرتے ہیں اور زندگی سے لطف اندوز ہوتے ہیں۔

نتیجہ کی پیداوار

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)

مفید لنک.

ماخذ: www.habr.com

نیا تبصرہ شامل کریں