⌨ Labor omnia vincit ☮

UDP socket: NTPv3 client in C

Posted in C, Network by anaumov on 06.01.2017

Прошлый год запомнился резким скачком интереса к NTP, а точнее к проблемам безопасности, связанным с этим протоколом. Пытаясь вникнуть в причину уязвимости, ловлю себя на мысли, что пока я сам не запрограммирую NTP, по-настоящиему разобраться не получится. К тому же в книге TCP/IP Guide, которая стала моей настольной книгой, нет описания NTP, что огорчает, но в тоже время вызывает любопытство 🙂
Сам протокол достаточно сложен, тем не менее клиенскую часть можно описать в одном посте, что я сейчас и собираюсь сделать. Язык прогроммирования естественно С, пишем без каких-либо библиотек/оберток, иначе это не имеет смысла. Я попытался писать как можно компактнее (не в ущерб пониманию кода конечно же).

Итак, что нам надо знать об NTP? Это UDP. Мы помним о плюсах и минусах этого протокола. Для программиста это большая головная боль, когда дело касается отладки, т.к. дебажит соединение, которое не должно устанавливаться в принципе, совсем не прикольно. С другой стороны, NTP подразумевает открытый сервис для всех в интернет, чего нельзя сказать, к примеру, об SNMP. Нам не нужно устанавливать сервер у себя, мы можем воспользоваться публичными серверами для синхронизации времени. Задача сводится лишь к созданию NTP пакета, который поймет сервер и на который ответит, т.е. соответствующий стандарту.

Кстати о стадарте. NTP является одним из самых старых протоколов, последней версией которого является 4 (между 3 и 4 разница небольшая, они совместимы). Последняя, четвертая, версия описана в RFC5905, а сам NTP header на его 19 странице. Русский перевод можно найти тут. Запрашивая в гугле structure ntp package, вы скорее всего найдете описание 3 версии. Они совместимы, так что большой ошибки не будет. Итак, NTPv3 пакет, который мы должны построить и отослать сервергу должен выглядеть вот так:

NTP package
Внимательный читатель заметил, что на картинке и по ссылкам разные NTP пакеты 🙂 На картинке NTPv3 (пакет размером в 48 байт). Это необходимые поля для минимального NTP сообщения. “Поля расширения” и остальное далее было добавленно в 4 версии.

IP и UDP заголовки отдадим сделать ядру, сами напишем только NTP header.
Я не буду повторятся, по ссылкам выше стандарт на языке оригинала и на русском. Я уверен, вы разберетесь. Сверяйте код ниже со стандартом. Мы заполняем 48 байтовое (384 бит) NTP сообщение:

	typedef struct 
	{
		unsigned leap	: 2;  // 2 bits
		unsigned ver	: 3;  // 3 bits
		unsigned mode	: 3;  // 3 bits
		
		uint8_t stratum;      // 8 bits
		uint8_t poll;         // 8 bits
		uint8_t precision;    // 8 bits
		
		uint32_t root_delay;  // 32 bits
		uint32_t root_dis;    // 32 bits
		uint32_t ref_id;      // 32 bits
		
		uint32_t ref_Tm_s;    // 32 bits
		uint32_t ref_Tm_f;    // 32 bits
		
		uint32_t orig_Tm_s;   // 32 bits
		uint32_t orig_Tm_f;   // 32 bits
		
		uint32_t rcv_Tm_s;    // 32 bits
		uint32_t rcv_Tm_f;    // 32 bits
		
		uint32_t tsm_Tm_s;    // 32 bits
		uint32_t tsm_xTm_f;   // 32 bits
} ntp_packet;

Эта стуктура и будет нашим NTP сообщением. Нам нужно заполнить только 8 первых бит: индикатор перехода (2 бита), номер версии (3 бита) и режим (3 бита). Остальное заполним нулями:

ntp_packet packet = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

// Set the first bits to 00,011,011: leap = 0, ver = 3, mode = 3
*((char *) &packet + 0) = 0x1b; // Represents 00011011 in base 2

Все. Наш NTP пакет готов. Можно отправлять. Создаем UDP сокет и запихиваем в него нашу структуру.
Мы все учились по разным книгам. Я учился по книгам Стивенса, поэтому для UDP использую sendto() и recvfrom() вместо write() и read(). А вот зашиваить номер порта в код не совсем хорошая идея; можете изменить это. И конечно же проверка возвращаемых значений из всех функций.

	struct sockaddr_in serv_addr;
	struct hostent *server;
	int sockfd;

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		oops("can't create socket", 2);
	
	if ((server = gethostbyname(*++argv)) == NULL)
		oops("can't find this host", 2);
	
	bzero((void *)&serv_addr, sizeof(serv_addr));
	bcopy((void *)server->h_addr, (void *)&serv_addr.sin_addr, server->h_length);
	
	serv_addr.sin_port = htons(123); // sorry for that %)
	serv_addr.sin_family = AF_INET;
	
	if (sendto(sockfd, &packet, sizeof(packet), 0,
                  (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1)
          oops("sendto failed", 3);

	socklen_t saddrlen = sizeof(serv_addr);	
	if (recvfrom(sockfd, (char*) &packet, sizeof(packet), 0,
                    (struct sockaddr *) &serv_addr, &saddrlen) == -1)
          oops("recvfrom failed", 3);

Вызов recvfrom() перезаписывает значения полей структуры, т.е. там находится ответ от сервера. Размер 1:1, никаких переполнений. Все строго по стандарту.

Необходимое значение для времени достаем из Transmit Timestamp, т.е. packet.tsm_Tm_s. Там не само время, а значение, которое мы будем использовать для определения/подсчета времени.

	packet.tsm_Tm_s = ntohl(packet.tsm_Tm_s);
	time_t tTm = (time_t) (packet.tsm_Tm_s - NTP_TIMESTAMP_DELTA);
	printf("Time: %s", ctime((const time_t*) &tTm));

Полностью исходник можно скачать отсюда.

Чтобы лучше представить содержание пакета, запустите снифер и посмотрите на наш пакет изнутри. Интересно пронаблюдать за поведениием сервера, изменяя значения полей в NTP запросах. Хотя реакция предсказуема, ведь она тоже стандартизирована.

NTP answer

Итак, мы тут немного поигрались с NTPv3. Я делаю актцент, что мы работали только с 3 версией. Статья не закончена. Я напишу продолжение, где, во-первых, будет рассмотрен уже NTPv4, а во-вторых, сами соберем не только NTP-, но и IP- и UPD-заголовок.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: