κ°„λ‹¨ν•œ NTP ν΄λΌμ΄μ–ΈνŠΈ μž‘μ„±

μ•ˆλ…•ν•˜μ„Έμš”, Habrausersμž…λ‹ˆλ‹€. μ˜€λŠ˜μ€ κ°„λ‹¨ν•œ NTP ν΄λΌμ΄μ–ΈνŠΈλ₯Ό μž‘μ„±ν•˜λŠ” 방법에 λŒ€ν•΄ μ΄μ•ΌκΈ°ν•˜κ³  μ‹ΆμŠ΅λ‹ˆλ‹€. 기본적으둜 λŒ€ν™”λŠ” νŒ¨ν‚·μ˜ ꡬ쑰와 NTP μ„œλ²„μ˜ 응닡이 μ²˜λ¦¬λ˜λŠ” λ°©μ‹μœΌλ‘œ μ „ν™˜λ©λ‹ˆλ‹€. μ½”λ“œλŠ” Python으둜 μž‘μ„±λ  κ²ƒμž…λ‹ˆλ‹€. μ™œλƒν•˜λ©΄ 제 μƒκ°μ—λŠ” κ·ΈλŸ¬ν•œ μž‘μ—…μ— 더 쒋은 μ–Έμ–΄κ°€ μ—†κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. 전문가듀은 이 μ½”λ“œμ™€ ntplib μ½”λ“œμ˜ μœ μ‚¬μ„±μ— μ£Όλͺ©ν•  κ²ƒμž…λ‹ˆλ‹€. μ €λŠ” 이 μ½”λ“œμ—μ„œ "μ˜κ°μ„ μ–»μ—ˆμŠ΅λ‹ˆλ‹€".

κ·Έλ ‡λ‹€λ©΄ NTPλž€ λ¬΄μ—‡μΌκΉŒμš”? NTPλŠ” νƒ€μž„ μ„œλ²„μ™€ ν†΅μ‹ ν•˜κΈ° μœ„ν•œ ν”„λ‘œν† μ½œμž…λ‹ˆλ‹€. 이 ν”„λ‘œν† μ½œμ€ λ§Žμ€ μ΅œμ‹  κΈ°κ³„μ—μ„œ μ‚¬μš©λ©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ Windows의 w32tm μ„œλΉ„μŠ€μž…λ‹ˆλ‹€.

NTP ν”„λ‘œν† μ½œμ—λŠ” 총 5κ°€μ§€ 버전이 μžˆμŠ΅λ‹ˆλ‹€. 첫 번째 버전인 버전 0(1985λ…„, RFC958)은 ν˜„μž¬ μ‚¬μš©λ˜μ§€ μ•ŠλŠ” κ²ƒμœΌλ‘œ κ°„μ£Όλ©λ‹ˆλ‹€. ν˜„μž¬ μ‚¬μš© 쀑인 μ΅œμ‹  버전은 버전 1(1988λ…„, RFC1059), 버전 2(1989λ…„, RFC1119), 버전 3(1992λ…„, RFC1305), 그리고 버전 4(1996λ…„, RFC2030)μž…λ‹ˆλ‹€. 버전 1λΆ€ν„° 4κΉŒμ§€λŠ” μ„œλ‘œ ν˜Έν™˜λ˜λ©°, μž‘λ™ μ•Œκ³ λ¦¬μ¦˜λ§Œ λ‹€λ¦…λ‹ˆλ‹€. μ„œλ²„.

νŒ¨ν‚· ν˜•μ‹

κ°„λ‹¨ν•œ NTP ν΄λΌμ΄μ–ΈνŠΈ μž‘μ„±

도약 ν‘œμ‹œκΈ° (μˆ˜μ • ν‘œμ‹œ)λŠ” 윀초 κ²½κ³ λ₯Ό λ‚˜νƒ€λ‚΄λŠ” μˆ«μžμž…λ‹ˆλ‹€. 의미:

  • 0 - μˆ˜μ • μ—†μŒ
  • 1 - ν•˜λ£¨μ˜ λ§ˆμ§€λ§‰ 61뢄은 XNUMXμ΄ˆμž…λ‹ˆλ‹€.
  • 2 - ν•˜λ£¨μ˜ λ§ˆμ§€λ§‰ 59뢄은 XNUMXμ΄ˆμž…λ‹ˆλ‹€.
  • 3 - μ„œλ²„ 였λ₯˜(동기화 μ‹œκ°„ 초과)

버전 번호 (버전 번호) – NTP ν”„λ‘œν† μ½œ 버전 번호(1-4)μž…λ‹ˆλ‹€.

λͺ¨λ“œ (λͺ¨λ“œ) β€” νŒ¨ν‚· μ†‘μ‹ μžμ˜ μž‘λ™ λͺ¨λ“œμž…λ‹ˆλ‹€. 0λΆ€ν„° 7κΉŒμ§€μ˜ κ°’(κ°€μž₯ μΌλ°˜μ μž„):

  • 3 - ν΄λΌμ΄μ–ΈνŠΈ
  • 4 - μ„œλ²„
  • 5 - 방솑 λͺ¨λ“œ

μ§€μΈ΅ (계측화 μˆ˜μ€€) - μ„œλ²„μ™€ μ°Έμ‘° 클둝 μ‚¬μ΄μ˜ 쀑간 계측 수(1 - μ„œλ²„κ°€ μ°Έμ‘° ν΄λ‘μ—μ„œ 직접 데이터λ₯Ό κ°€μ Έμ˜΄, 2 - μ„œλ²„κ°€ 레벨 1의 μ„œλ²„μ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜΄ λ“±).
ν’€ 연속 λ©”μ‹œμ§€ μ‚¬μ΄μ˜ μ΅œλŒ€ 간격을 λ‚˜νƒ€λ‚΄λŠ” λΆ€ν˜Έ μžˆλŠ” μ •μˆ˜μž…λ‹ˆλ‹€. NTP ν΄λΌμ΄μ–ΈνŠΈλŠ” μ—¬κΈ°μ—μ„œ μ„œλ²„λ₯Ό 폴링할 κ²ƒμœΌλ‘œ μ˜ˆμƒλ˜λŠ” 간격을 μ§€μ •ν•˜κ³ , NTP μ„œλ²„λŠ” 폴링이 μ˜ˆμƒλ˜λŠ” 간격을 μ§€μ •ν•©λ‹ˆλ‹€. 값은 초의 이진 λ‘œκ·Έμ™€ κ°™μŠ΅λ‹ˆλ‹€.
μ •λ°€μ„± (정밀도)λŠ” μ‹œμŠ€ν…œ μ‹œκ³„μ˜ 정확도λ₯Ό λ‚˜νƒ€λ‚΄λŠ” λΆ€ν˜Έ μžˆλŠ” μ •μˆ˜μž…λ‹ˆλ‹€. 값은 초의 이진 λ‘œκ·Έμ™€ κ°™μŠ΅λ‹ˆλ‹€.
루트 μ§€μ—° (μ„œλ²„ λŒ€κΈ° μ‹œκ°„)은 μ‹œκ³„κ°€ NTP μ„œλ²„μ— λ„λ‹¬ν•˜λŠ” 데 κ±Έλ¦¬λŠ” μ‹œκ°„(초 λ‹¨μœ„)μž…λ‹ˆλ‹€.
뿌리 λΆ„μ‚° (μ„œλ²„ λΆ„μ‚°) - NTP μ„œλ²„ μ‹œκ³„μ˜ κ³ μ • μ†Œμˆ˜μ  수(초) λΆ„μ‚°μž…λ‹ˆλ‹€.
μ°Έμ‘° ID (μ†ŒμŠ€ ID) - μ‹œκ³„ IDμž…λ‹ˆλ‹€. μ„œλ²„μ— Stratum 1이 μžˆλŠ” 경우 ref idλŠ” μ›μž μ‹œκ³„μ˜ 이름(ASCII 문자 4개)μž…λ‹ˆλ‹€. μ„œλ²„κ°€ λ‹€λ₯Έ μ„œλ²„λ₯Ό μ‚¬μš©ν•˜λŠ” 경우 μ°Έμ‘° IDμ—λŠ” 이 μ„œλ²„μ˜ μ£Όμ†Œκ°€ ν¬ν•¨λ©λ‹ˆλ‹€.
λ§ˆμ§€λ§‰ 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 쀑 ν•˜λ‚˜, λͺ¨λ“œ - XNUMX(ν΄λΌμ΄μ–ΈνŠΈ λͺ¨λ“œ)을 μ§€μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μš”μ²­μ„ 받은 μ„œλ²„λŠ” NTP νŒ¨ν‚·μ˜ λͺ¨λ“  ν•„λ“œλ₯Ό μ±„μš°κ³  ν•΄λ‹Ή ν•„λ“œμ— λ³΅μ‚¬ν•©λ‹ˆλ‹€. 유래 κ°€μΉ˜ 전솑, μš”μ²­μ— ν¬ν•¨λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 고객이 ν˜„μž₯μ—μ„œ μžμ‹ μ˜ μ‹œκ°„μ˜ κ°€μΉ˜λ₯Ό μ¦‰μ‹œ μ±„μšΈ 수 μ—†λŠ” μ΄μœ λŠ” λ‚˜μ—κ²Œ λ―ΈμŠ€ν„°λ¦¬μž…λ‹ˆλ‹€. 유래. 결과적으둜 νŒ¨ν‚·μ΄ λŒμ•„μ˜€λ©΄ ν΄λΌμ΄μ–ΈνŠΈλŠ” 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)

μ„œλ²„μ—μ„œ 데이터 처리

μ„œλ²„μ˜ 데이터 μ²˜λ¦¬λŠ” Raymond M. Smallian(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)

유λŠ₯ν•œ 링크.

좜처 : habr.com

μ½”λ©˜νŠΈλ₯Ό μΆ”κ°€