diff --git a/README.md b/README.md index 41bbd205..a8768598 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,101 @@ -# Лабораторные работы за осенний семестр 2020 +## Лабораторная работа 2 – NTP-клиент -В `master` ветке данного репозитория присутствуют актуальные задания, включая -распределение протоколов по студентам. +### Инструкция по запуску +Запускается из командной строки с необязательными параметрами: +`python3 ntp_client.py -s -t ` +Значения по умолчанию: +* сервер: [pool.ntp.org](https://www.ntppool.org/ru/) +* порт: 123 +* время ожидания ответа от сервера: 10 сек -# Прогресс -Легенда: +### Тестирование работы +Пример работы клиента: +``` +$ python3 ntp_client.py +Server: pool.ntp.org +-------------------------------------- +Server time: 2021-01-10 22:57:41.563910 +Local time: 2021-01-10 22:57:41.369149 +Round trip time: 92 ms +Offset: 149 ms +``` -| Символ | Значение | -| -- | -- | -| | Работа не сдана или находится на рассмотрении | -| + | Работа принята | -| ! | Работа рассмотрена, требуется демо | -| ? | Работа рассмотрена, но требует доработки | -| - | Работу сдавать не требуется | -| -------- | -------- | -| SSH ключ | [Отправить SSH ключ преподавателю](https://insysnw.github.io/labs/900-ssh-keygen/) | -| Л.1a | [TCP чат](https://insysnw.github.io/labs/01-tcp-chat/) | -| Л.1б | [TCP чат](https://insysnw.github.io/labs/01-tcp-chat/) (неблокирующие сокеты) | -| Л.2c | [UDP сервер существующего протокола](https://insysnw.github.io/labs/02-udp-real-protocol/) | -| Л.2к | [UDP клиент существующего протокола](https://insysnw.github.io/labs/02-udp-real-protocol/) | -| Л.3 | Задание из методички | +Превышено время запроса: +``` +$ python3 ntp_client.py -s ntp.nict.jp +Server: ntp.nict.jp +-------------------------------------- +Timeout! Try to connect to the server again +``` -## Группа 201 +### Описание протокола +**NTP (Network Time Protocol)** — протокол, который работает поверх UDP и используется для синхронизации локальных часов с часами на сервере точного времени. Актуальная версия – 4, она же используется в лабораторной работе. -| ФИО | SSH ключ | Л.1a | Л.1б | Л.2c | Л.2к | Л.3 | -| -- | -- | -- | -- | -- | -- | -- | -| Антропова А.А. | + | | | | | | -| Белов Е.А. | + | | | tftp | dhcp | | -| Буй К.Д. | + | | | tftp | dns | | -| Гладкова Е.Д. | + | | | | | | -| Голзицкий Н.С. | + | | | | | | -| Гуляев Д.В. | | | | | | | -| Данилов А.И. | + | | | dhcp | ntp | | -| Казанджи М.А. | + | | | ntp | snmp | | -| Киселев Н.Д. | + | | | dns | tftp | | -| Лялин А.С. | + | | | | | | -| Натура А.А. | + | | | snmp | dns | | -| Никитин И.Н. | + | | | | | | -| Романов А.Л. | + | | | dns | dhcp | | -| Свечников Р.А. | + | | | dhcp | dns | | -| Сибагатулин А.Ф. | | | | | | | -| Товпеко К.А. | + | [?](https://github.com/insysnw/2020h2/pull/2) | [?](https://github.com/insysnw/2020h2/pull/2) | dns | ntp | | -| Черноног С.А. | + | | | | | | -| Шаляпин Г.А. | + | | | | | | +#### Формат пакетов -## Группа 203 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |LI | VN |Mode | Stratum | Poll | Precision | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Root Delay | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Root Dispersion | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reference ID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Reference Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Origin Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Receive Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + Transmit Timestamp (64) + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + . . + . Extension Field 1 (variable) . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + . . + . Extension Field 2 (variable) . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Key Identifier | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | dgst (128) | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| ФИО | Ssh ключ | Л.1a | Л.1б | Л.2с | Л.2к | Л.3 | -| -- | -- | -- | -- | -- | -- | -- | -| Ворошилов А.А. | + | | | | | | -| Гусев Н.С. | | | | | | | -| Зарецкая Е.С. | | | | | | | -| Иванов И.Д. | + | | | | | | -| Калашников Р.А. | | | | dns | snmp | | -| Костарев В.И. | + | | | dhcp | snmp | | -| Любченкова А.А. | + | | | tftp | dns | | -| Меньшов П.А. | + | | | dns | tftp | | -| Морозов Е.С. | + | | | dns | dhcp | | -| Никитина Д.С. | + | | | | | | -| Овсянников Е.А. | + | | | | | | -| Орлова П.А. | + | | | tftp | dns | | -| Пентегов А.О. | + | | | | | | -| Семёнов Д.С. | + | | | dhcp | dns | | -| Середин К.В. | + | | | dhcp | ntp | | -| Трушин И.А. | + | | | tftp | dhcp | | -| Черникова А.С. | + | | | tftp | ntp | | -| Шелепов В.А. | + | | | dhcp | tftp | | +* **Leap indicator (LI), 2 бита** — число, предупреждающее о секунде координации. Может быть от 0 до 3, где 0 — нет коррекции, 1 — последняя минута дня содержит 61 с, 2 — последняя минута дня содержит 59 с, 3 — неисправность сервера. При значении 3 полученным данным доверять не следует. Вместо этого нужно обратиться к другому серверу. Наш псевдосервер будет всегда возвращать 0. +* **Version number (VN), 3 бита** — номер версии протокола NTP (1–4). Мы поставим туда 3. +* **Mode, 3 бита** — режим работы отправителя пакета. Значение от 0 до 7, где 3 — клиент, а 4 — сервер. +* **Stratum, 1 байт** — сколько посредников между клиентом и эталонными часами (включая сам NTP-сервер). 1 — сервер берет данные непосредственно с атомных (или других точных) часов, то есть между клиентом и часами только один посредник (сам сервер); 2 — сервер берет данные с сервера со значением Stratum 1 и так далее. +* **Poll, 1 байт** — целое число, задающее интервал в секундах между последовательными обращениями. Клиент может указать здесь интервал, с которым он хочет отправлять запросы на сервер, а сервер — интервал, с которым он разрешает, чтобы его опрашивали. +* **Precision (точность), 1 байт** — число, которое сообщает точность локальных системных часов. Значение равно двоичному логарифму секунд. +* **Root delay (задержка сервера), 4 байта** — время, за которое показания эталонных часов доходят до сервера NTP. Задается как число секунд с фиксированной запятой. +* **Root dispersion, 4 байта** — разброс показаний сервера. +* **RefID, 4 байта** (идентификатор источника) — ID часов. Если поле Stratum равно единице, то RefID — имя атомных часов (четыре символа ASCII). Если текущий сервер NTP использует показания другого сервера, то в RefID записан IP-адрес этого сервера. +* **Reference, 8 байт** — последние показания часов сервера. +* **Originate, 8 байт** — время, когда пакет был отправлен, по версии сервера. +* **Receive, 8 байт** — время получения запроса сервером. +* **Transmit, 8 байт** — время отправки ответа сервера клиенту, которое заполняет клиент. -# Требования к отчету: +Со стороны клиента заполняются только поля LI (unknown), VN (4), Mode (3) и Transmit. -* Инструкция по использованию; -* Инструкция по сборке/установке; -* Описание используемого протокола; - * Своего для первой и третьей лабораторной; - * Используемого подмножества для второй; -* ??? -* PROFIT -Отчет можно писать как в сообщении к PR-у, так и присылать в иных -форматах (`.pdf`, `.docx`, `.txt` и т.п.). - -# Порядок сдачи - -* Fork от данного репозитория -* Push каждой лабораторной в отдельную ветку -* Создание отдельного PR на каждую лабораторную -* Ставите label по сдаваемой лабе (`First lab`, `Second lab`, `Third lab`) - -При создании PR, в качестве напоминалки, сделан шаблон. +#### Процесс передачи данных +Клиент посылает запрос на сервер, запоминая, когда этот запрос был отправлен. Сервер принимает пакет, запоминает и записывает в пакет время приема, заполняет время отправки и отвечает клиенту. Клиент запоминает, когда он получил ответ, и получает нечто вроде RTT (Round-Trip Time) до сервера. Дальше он определяет, сколько времени понадобилось пакету, чтобы дойти от сервера обратно ему (время между запросом и ответом клиента минус время обработки пакета на сервере, деленное на два). \ No newline at end of file diff --git a/ntp_client.py b/ntp_client.py new file mode 100644 index 00000000..4efd28fd --- /dev/null +++ b/ntp_client.py @@ -0,0 +1,126 @@ +import argparse +import os +import sys +from datetime import datetime +from math import modf +import socket +from struct import pack +from struct import unpack + +# Unix time starts on Jan 1 1970. In seconds, that's 2208988800 +SEVENTY_YEARS = 2208988800 + + +def NTPtime_to_datetime(int_val, fract_val): + ntp_time = int_val + fract_val / 2 ** 32 + return datetime.fromtimestamp(ntp_time - SEVENTY_YEARS) + + +def datetime_to_NTPtime(dt): + ntp_ts_frac, ntp_ts_int = modf(dt.timestamp() + SEVENTY_YEARS) + ntp_ts_frac = int(ntp_ts_frac * 2**32) + ntp_ts_int = int(ntp_ts_int) + + return ntp_ts_frac, ntp_ts_int + + +class NTPClient(object): + # Leap indicator – a number that warns about a second of coordination + LEAP_ATR = { + 0: "No warning", + 1: "Last minute of the day has 61 seconds", + 2: "Last minute of the day has 59 seconds", + 3: "Unknown (clock is not synchronized)" + } + + # Mode of packet's sender + MODE_ATR = { + 0: "Reserved", + 1: "Symmetric active", + 2: "Symmetric passive", + 3: "Client", + 4: "Server", + 5: "Broadcast", + 6: "Reserved for NTP control messages", + 7: "Reserved for private use", + } + + def __init__(self, server, timeout, port=123): + own_ts_frac, own_ts_int = datetime_to_NTPtime(datetime.now()) + + ref_ts = pack( + '!4B11I', + int('11100011', 2), # LI=11(unknown), VN=100(v4), MODE=011(client) + 0, # Stratum - часовой слой + 0, # Poll Interval - интервал опроса + 0, # Precision - точность + 0, # Root Delay - задержка + 0, # Root Dispersion - дисперсия + 0, # Reference Identifier - идентификатор источника + 0, # Reference Timestamp (integer part) - время обновления + 0, # Reference Timestamp (fraction part) + 0, # Originate Timestamp (integer part) - время клиента, когда запрос отправляется серверу + 0, # Originate Timestamp (fraction part) + 0, # Receive Timestamp (integer part) - время сервера, когда пришло сообщение от клиента + 0, # Receive Timestamp (fraction part) + own_ts_int, # Transmit Timestamp (integer part) + own_ts_frac, # Transmit Timestamp (fraction part) + ) + + # send request to NTP server + self.local_transmit_ts = datetime.now() + + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.sendto(ref_ts, (server, port)) + s.settimeout(timeout) + try: + response = s.recvfrom(1024)[0] + except: + print('Timeout! Try to connect to the server again') + os._exit(0) + self.local_receive_ts = datetime.now() + + # parse response + if response: + unpacked = unpack('!4B11I', response) + first_oct = format(unpacked[0], 'b').zfill(8) + + leap = int(first_oct[0:2], 2) + + self.leap_indicator = leap + self.leap_indicator_atr = self.LEAP_ATR[leap] if leap in self.LEAP_ATR else 'Unknown' + + self.version_number = int(first_oct[2:5], 2) + + mode = int(first_oct[5:8], 2) + self.mode = mode + self.mode_atr = self.MODE_ATR[mode] if mode in self.MODE_ATR else 'Unknown' + + self.stratum = unpacked[1] + self.poll_interval = unpacked[2] + self.precision = unpacked[3] + self.root_delay = unpacked[4] + self.root_dispersion = unpacked[5] + self.reference_identifier = unpacked[6] + self.reference_ts = NTPtime_to_datetime(unpacked[7], unpacked[8]) + self.originate_ts = NTPtime_to_datetime(unpacked[9], unpacked[10]) + self.receive_ts = NTPtime_to_datetime(unpacked[11], unpacked[12]) + self.transmit_ts = NTPtime_to_datetime(unpacked[13], unpacked[14]) + self.rtt = (self.local_receive_ts - self.local_transmit_ts).total_seconds() - (self.transmit_ts - self.receive_ts).total_seconds() + self.offset = ((self.transmit_ts - self.local_receive_ts).total_seconds() + (self.receive_ts - self.local_transmit_ts).total_seconds()) * 0.5 + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-s', '--server', type=str, default='pool.ntp.org') + parser.add_argument('-t', '--timeout', type=int, default=10) + args = parser.parse_args(sys.argv[1:]) + print(f'Server: {args.server}') + print('--------------------------------------') + + ntp = NTPClient(args.server, args.timeout) + + print(f"Server time: {ntp.transmit_ts}") + print(f"Local time: {ntp.local_transmit_ts}") + print("Round trip time: {:.0f} ms".format(ntp.rtt * 1000)) + print("Offset: {:.0f} ms".format(ntp.offset * 1000))