-
Notifications
You must be signed in to change notification settings - Fork 0
/
time_server.py
158 lines (131 loc) · 4.56 KB
/
time_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3
#20200301
#Jan Mojzis
#Public domain.
import logging
import socket
import time
import datetime
import os
import random
import sys
import struct
import getopt
NTPFORMAT = ">3B b 3I 4Q"
NTPDELTA = 2208988800.0
def s2n(t = 0.0):
"""
System to NTP
"""
t += NTPDELTA
return (int(t) << 32) + int(abs(t - int(t)) * (1<<32))
def n2s(x = 0):
"""
NTP to System
"""
t = float(x >> 32) + float(x & 0xffffffff) / (1<<32)
return t - NTPDELTA
def tfmt(t = 0.0):
"""
Format System Timestamp
"""
return datetime.datetime.fromtimestamp(t).strftime("%Y-%m-%d_%H:%M:%S.%f")
def usage():
"""
Print the usage
"""
print("ntpserver.py [-vh] [ip] [port] [chroot directory]", file = sys.stderr)
sys.exit(100)
loglevel = logging.INFO
# parse program parameters
try:
options, arguments = getopt.getopt(sys.argv[1:], 'hv')
except Exception as e:
#bad option
usage()
# process options
for opt, val in options:
if opt == "-h":
usage()
if opt == "-v":
loglevel = logging.DEBUG
try:
localip = arguments[0]
except IndexError:
localip = "0.0.0.0"
try:
localport = int(arguments[1])
except IndexError:
localport = 123
try:
root = arguments[2]
except IndexError:
root = '.'
logging.basicConfig(
format='%(asctime)s %(levelname)-8s %(message)s',
level = loglevel,
datefmt='%Y-%m-%d_%H:%M:%S')
logging.info("starting ntpserver.py %s %d" % (localip, localport))
# create socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((localip, localport))
logging.debug('local socket: %s:%d' % s.getsockname())
# drop privileges
try:
uid = 100000000 + 100000 * random.randint(0, 999) + os.getpid()
os.setgid(uid)
os.setuid(uid)
except OSError:
uid = 10000 + random.randint(0, 9999)
os.setgid(uid)
os.setuid(uid)
logging.debug('UID/GID set to %d' % (uid))
# get the precision
try:
hz = int(1 / time.clock_getres(time.CLOCK_REALTIME))
except AttributeError:
hz = 1000000000
precision = 0
while hz > 1:
precision -= 1;
hz >>= 1
while True:
try:
# receive the query
data, addr = s.recvfrom(struct.calcsize(NTPFORMAT))
serverrecv = s2n(time.time())
if len(data) != struct.calcsize(NTPFORMAT):
raise Exception("Invalid NTP packet: packet too short: %d bytes" % (len(data)))
try:
data = struct.unpack(NTPFORMAT, data)
except struct.error:
raise Exception("Invalid NTP packet: unable to parse packet")
data = list(data)
# parse the NTP query (only Version, Mode, Transmit Timestamp)
version = data[0] >> 3 & 0x7
if (version > 4):
raise Exception("Invalid NTP packet: bad version %d" % (version))
mode = data[0] & 0x7
if (mode != 3):
raise Exception("Invalid NTP packet: bad client mode %d" % (mode))
clienttx = data[10]
# create the NTP response
data[0] = version << 3 | 4 # Leap, Version, Mode
data[1] = 1 # Stratum
data[2] = 0 # Poll
data[3] = precision # Precision
data[4] = 0 # Synchronizing Distance
data[5] = 0 # Synchronizing Dispersion
data[6] = 0 # Reference Clock Identifier
data[7] = serverrecv # Reference Timestamp
data[8] = clienttx # Originate Timestamp
data[9] = serverrecv # Receive Timestamp
data[10] = s2n(time.time()) # Transmit Timestamp
print("time query spotted!")
# send the response
data = struct.pack(NTPFORMAT, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10])
s.sendto(data, addr)
except Exception as e:
logging.warning("%s: failed: %s" % (addr[0], e))
else:
logging.info('%s: ok: client="%s", server="%s"' % (addr[0], tfmt(n2s(clienttx)), tfmt(n2s(serverrecv))))