Синхронизирующий сетевой протокол (Network Time Protocol, NTP) — это сложный протокол синхронизации часов в глобальной или локальной сети. Его точность часто может достигать миллисекунд. В RFC 1305 [76] этот протокол подробно описан, а в RFC 2030 [77] рассматривается протокол SNTP — упрощенная версия NTP, предназначенная для узлов, которым не требуется функциональность полной реализации NTP. Типичной является ситуация, когда несколько узлов в локальной сети синхронизируют свои часы через Интернет с другими узлами NTP, а затем распространяют полученное значение времени в локальной сети с использованием либо широковещательной, либо многоадресной передачи.
В этом разделе мы создадим клиент SNTP, который прослушивает широковещательные или групповые сообщения NTP на всех присоединенных сетях, а затем выводит разницу во времени между пакетом NTP и текущим истинным временем узла. Мы не пытаемся изменить это время, поскольку для этого необходимы права привилегированного пользователя.
Файл ntp.h
, показанный в листинге 21.11, содержит некоторые из основных определений формата пакета NTP.
Листинг 21.11. Заголовок ntp.h: формат пакета NTP и определения
//ssntp/ntp.h
1 #define JAN_1970 2208988800UL /* 1970 - 1900 в секундах */
2 struct l_fixedpt { /* 64-разрядное число с фиксированной точкой */
3 uint32_t int_part;
4 uint32_t fraction;
5 };
6 struct s_fixedpt { /* 32-разрядное число с фиксированной точкой */
7 u_short int_part;
8 u_short fraction;
9 };
10 struct ntpdata { /* заголовок NTP */
11 u_char status;
12 u_char stratum;
13 u_char ppoll;
14 int precision:8;
15 struct s_fixedpt distance;
16 struct s_fixedpt dispersion;
17 uint32_t refid;
18 struct l_fixedpt reftime;
19 struct l_fixedpt org;
20 struct 1_fixedpt rec;
21 struct l_fixedpt xmt;
22 };
23 #define VERSION_MASK 0x38
24 #define MODE_MASK 0x07
25 #define MODE CLIENT 3
26 #define MODE_SERVER 4
27 #define MODE_BROADCAST 5
2-22 l_fixedpt
задает 64-разрядные числа с фиксированной точкой, используемые NTP для отметок времени, a s_fixedpt
— 32-разрядные значения с фиксированной точкой, также используемые NTP. Структура ntpdata
представляет 48-байтовый формат пакета NTP.
В листинге 21.12 пpeдcтaвлeнa функция main
.
Листинг 21.12. Функция main
//ssntp/main.c
1 #include "sntp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int sockfd;
6 char buf[MAXLINE];
7 ssize_t n;
8 socklen_t salen, len;
9 struct ifi_info *ifi;
10 struct sockaddr *mcastsa, *wild, *from;
11 struct timeval now;
12 if (argc != 2)
13 err_quit("usage: ssntp ");
14 sockfd = Udp_client(argv[1], "ntp", (void**)&mcastsa, &salen);
15 wild = Malloc(salen);
16 memcpy(wild, mcastsa. salen); /* копируем семейство и порт */
17 sock_set_wild(wild, salen);
18 Bind(sockfd, wild, salen); /* связываем сокет с универсальным
[3] Имеется в виду адрес, записанный с помощью символов подстановки. — Примеч. перев.
адресом */
19 #ifdef MCAST
20 /* получаем список интерфейсов и обрабатываем каждый интерфейс */
21 for (ifi = Get_ifi_info(mcastsa->sa_family, 1); ifi != NULL;
22 ifi = ifi->ifi_next) {
23 if (ifi->ifi_flags & IFF_MULTICAST) {
24 Mcast_join(sockfd, mcastsa, salen, ifi->ififname, 0);
25 printf("joined %s on %s\n",
26 Sock_ntop(mcastsa, salen), ifi->ifi_name);
27 }
28 }
29 #endif
30 from = Malloc(salen);
31 for (;;) {
32 len = salen;
33 n = Recvfrom(sockfd, buf, sizeof(buf), 0, from, &len);
34 Gettimeofday(&now, NULL);
35 sntp_proc(buf, n, &now);
36 }
37 }
Получение IP-адреса многоадресной передачи
12-14
При выполнении программы пользователь должен задать в качестве аргумента командной строки адрес многоадресной передачи, к которому он будет присоединяться. В случае IPv4 это будет 224.0.1.1 или имя ntp.mcast.net
. В случае IPv6 это будет ff05::101
для области действия NTP, локальной в пределах сайта. Наша функция udp_client
выделяет в памяти пространство для структуры адреса сокета корректного типа (либо IPv4, либо IPv6) и записывает адрес многоадресной передачи и порт в эту структуру. Если эта программа выполняется на узле, не поддерживающем многоадресную передачу, может быть задан любой IP-адрес, так как в этой структуре задействуются только семейство адресов и порт. Обратите внимание, что наша функция udp_client
не связывает адрес с сокетом (то есть не вызывает функцию bind
) — она лишь создает сокет и заполняет структуру адреса сокета.
Связывание универсального адреса с сокетом
15-18
Мы выделяем в памяти пространство для другой структуры адреса сокета и заполняем ее, копируя структуру, заполненную функцией udp_client
. При этом задаются семейство адреса и порт. Мы вызываем нашу функцию sock_set_wild, чтобы присвоить IP-адресу универсальный адрес, а затем вызываем функцию bind.
Читать дальше
Конец ознакомительного отрывка
Купить книгу