FastNetMon

Tuesday 5 May 2015

Linux TCP timestamp и его генерация

Столкнулся с интересной задачей по сетям и полез разбираться, как же генерируется TCP Timestamp в Linux. Испытания было решено провести на Linux 3.16 и моем любимом Debian Jessie.

Итак, для начала поднимем любой  http сервер на локалхосте и дернем его curl'ом с той же машины, глядя в этот момент на показания tcpdump.

 18:25:40.137056 IP 127.0.0.1.33673 > 127.0.0.1.80: Flags [S], seq 4291943858, win 43690, options [mss 65495,sackOK,TS val 170139796 ecr 0,nop,wscale 7], length 0
18:25:40.137079 IP 127.0.0.1.80 > 127.0.0.1.33673: Flags [S.], seq 3577086755, ack 4291943859, win 43690, options [mss 65495,sackOK,TS val 170139796 ecr 170139796,nop,wscale 7], length 0
Ок, что мы тут видим? Мы видим число 170139796,  которое и является TCP timestamp.

Как же оно сгенерировано?

После часа поисков по коду Линукс Ядра я наткнулся на вот такой код в файле include/net/tcp.h:
/* TCP timestamps are only 32-bits, this causes a slight
 * complication on 64-bit systems since we store a snapshot
 * of jiffies in the buffer control blocks below.  We decided
 * to use only the low 32-bits of jiffies and hide the ugly
 * casts with the following macro.
 */
#define tcp_time_stamp          ((__u32)(jiffies))
Стало быть, некий jiffie (который сам по себе 64 битный) преобразуется в 32 битное значение, путем отсечения старших битов.

Но что же такое jiffie и как его посчитать? Вопрос сложный и мерзкий. Если кратко, то jiffies - это число срабатываний прерываний таймера в Linux с момента загрузки системы (ключевое слово uptime).

Интерес заключается в том, что для каждой системы частота этого таймера индивидуальна (и зовется общепринято - HZ) и я потратил очень много времени прежде чем узнал, как часто срабатывает этот таймер на моей машине (на вашей показания могут быть совершенно иные!).

Итак, чтобы прекратить растрату времени я решил написать простейший модуль для своего ядра и посмотреть, что он выдает. Код можете найти вот здесь.

Ровно в момент когда я дергал curl я также загрузил этот модуль ядра и получил в dmesg следующие показания:
[680865.248893] Current jiffie: 4465108609
[680865.249188] Current jiffie converted to timestamp: 170141313
[680865.249480] Current HZ: 250
В то время, как все возможные статьи в интернете говорили о том, что HZ либо 100 либо 1000, но уж никак не 250.

Опа! Вы уже заметили. что странное число в поле "current jiffie converted to timestamp" очень похоже на TCP timestamp? Поздравляю! Это он и есть. Осталось понять, как он все-таки получается и как связан с аптаймом машины.

Итак, мы узнали, что таймер работает с частотой 250 герц, то есть срабатывает 1/250 раз в секунду. Чтобы перевести jiffies в секунды нужно jiffies поделить на HZ, получится следующее:
perl -e 'print 170141313/250'
680565.252
А в свою очередь это время в секундах показывающее, как давно был загружен сервер, его можно также взять в переменной /proc/uptime (первое число):
cat /proc/uptime
681437.68 5443271.52
Также uptimе можно получить с помощью соответствующей команды:
uptime
18:39:48 up 7 days, 21:21,  3 users,  load average: 0.11, 0.06, 0.06

Итак, мы теперь можем имея время аптайма в секундах сами сгенерировать TCP timestamp просто умножив на показания из /proc/uptime на 250 и отбросив дробные значения :)


2 comments :

  1. Собственно частота таймера указывается при сборке ядра и для Debian она поумолчанию 250Гц.

    ReplyDelete
  2. "работает с частотой 250 герц, то есть срабатывает 1/250 раз в секунду" - тут "1/" немного лишнее :)

    ReplyDelete

Note: only a member of this blog may post a comment.