В октябре 28-я Генеральная конференция по мерам и весам решила, что с 20 мая 2027 года в UTC перестанут добавлять високосные секунды. База часовых поясов tzdb в выпуске 2026f от 27 октября откликнулась строкой «leap second tables no longer expire» и научила zic не смотреть на срок таблицы. При этом файл leap-seconds.list, который tzdb раздаёт вместе с зонами и который читают NTP-демоны, по-прежнему со сроком годности: строка #@ в нём говорит, что файл истекает 28 июня 2027 года. Будет ли IERS продлевать его после отмены, пока не сказал никто.

Я запустил классический ntpd, NTPsec и chrony с истёкшим файлом, со свежим под подменённой датой после 28 июня 2027 и с файлом, из которого убрана строка срока. Истёкший файл принимают все трое: ntpd и NTPsec загружают таблицу со смещением 37 секунд и пишут ошибку, chrony пишет предупреждение и файл не отвергает. Плохо кончается другой вариант. Файл без строки срока chrony 4.6.1 отвергает целиком, а оба ntpd либо не грузят его совсем, либо считают просроченным с 1900 или 1970 года.

Зачем у файла срок годности

О новой високосной секунде IERS объявляет примерно за полгода в бюллетене C, и leap-seconds.list перевыпускают раз в полгода. Строка #@ — срок годности файла: до этой даты таблица считается актуальной. Meinberg в своей базе знаний объясняет смысл так: после срока программа уже не может знать, не назначили ли новую секунду. Последний раз секунду вставили 1 января 2017 года, с тех пор UTC отстаёт от TAI на 37 секунд. Бюллетень C 72 от 6 июля 2026 года подтвердил, что в конце декабря секунды не будет. Действующий файл обновлён по этому бюллетеню и истекает 28 июня 2027.

Выпуск tzdb 2026f снял срок с таблиц внутри TZif-файлов (зоны right/*), как и просит RFC 9636: таблицы, опубликованные после отмены, «SHOULD NOT expire». Про сам leap-seconds.list в NEWS ни слова. Его выпускает IERS, tzdb только кладёт рядом. Ещё в 2023a скрипт leapseconds.awk в tzdb научили обрабатывать файл, «that never expires; this might be useful if leap seconds are discontinued». Значит, впереди один из двух вариантов: IERS продолжит продлевать #@ или выпустит файл без срока. Третий вариант уже идёт: файл в системе просто не обновили.

Есть и противоречие между источниками. Meinberg пишет, что ntpd 4.2.8 и новее «really check the expiration date of the leap second file, and stop using it after the file has expired». Если понимать это буквально, после 28 июня 2027 у всех, кто не обновит tzdata, демон потеряет таблицу. Поэтому я проверил, что делают сами демоны.

Как проверял

Пакеты брал из архивов: NTPsec 1.2.3 и chrony 4.6.1 из Debian 13, ntpd 4.2.8p15 из Ubuntu 22.04. Распаковывал dpkg-deb -x в одноразовом контейнере и запускал без root. Проверку getuid() у ntpd обходил маленьким шимом через LD_PRELOAD. Часы демоны не ставили: в контейнере нет CAP_SYS_TIME. Дату после срока подставлял libfaketime внутри контейнера, часы хоста никто не трогал. chrony проверяет срок файла только после синхронизации, поэтому второй chronyd на loopback служил ему сервером.

Файлы были такие:

  • свежий из tzdb, срок 2027-06-28;

  • из tzdata2023c, срок 2023-12-28;

  • свежий под датами 2027-07-01 и 2030-01-01;

  • свежий без строки #@, с прежней подписью #h и без неё;

  • свежий со сроком 2100-01-01 и пересчитанной подписью.

Подпись #h — это SHA-1 от цифр строк #$, #@ и строк данных. Чтобы не сломать её по ошибке, я сначала пересчитал хеш исходного файла своим скриптом, он совпал с #h.

Истёкший файл: ошибка в журнале, таблица загружена

Классический ntpd с файлом 2023 года:

leapsecond file ('/work/e2/old.list'): expired 1042 days ago
leapsecond file ('/work/e2/old.list'): good hash signature
leapsecond file ('/work/e2/old.list'): loaded, expire=2023-12-28T00:00:00Z last=2017-01-01T00:00:00Z ofs=37
ntpq: leap=11, leapsec=201701010000, expire=202312280000

Таблица загружена, ntpq отдаёт дату последней секунды и срок. NTPsec пишет почти то же самое: «expired less than 1042 days ago» и loaded … ofs=37. Со свежим файлом под датой 1 июля 2027 года оба пишут «expired 3 days ago», под 1 января 2030 года — «918 days ago», и оба загружают таблицу.

Значит, слова Meinberg «stop using it» на этих версиях про потерю таблицы не говорят. В коде NTPsec загрузка вообще не смотрит на срок. Срок нужен в другом месте: функция leapsec_add_dyn разрешает добавить секунду, объявленную вышестоящими серверами, только когда таблица истекла. Истёкший файл перестаёт быть последним словом о будущих секундах, но прошлые 37 секунд остаются. Сам приём объявления от сервера я не проверял: для этого нужен вышестоящий сервер, который выставляет флаг секунды.

Цена истёкшего файла — шум. По коду NTPsec после срока пишет ошибку уровня LOG_ERR раз в сутки, и это условие не кончается. Живой журнал из бага Ubuntu на Launchpad #2144453 так и выглядит: «expired less than 68 days ago», через сутки 69, ещё через сутки 70. chrony после синхронизации пишет предупреждение Leap second list … needs update, по исходнику leapdb.c не чаще двух раз в сутки. До синхронизации, в режиме chronyd -Q, он молчит, и моя первая проба это пропустила.

файл

ntpd 4.2.8p15

NTPsec 1.2.3

chrony 4.6.1

свежий, срок 2027-06-28

загружен, ofs=37

загружен, ofs=37

молча

истёк 2023-12-28

ошибка, загружен, ofs=37

ошибка, загружен, ofs=37

WARN «needs update»

свежий, дата 2027-07-01

ошибка, загружен, ofs=37

ошибка, загружен, ofs=37

WARN «needs update»

без #@, прежняя подпись

signature mismatch, не загружен

signature mismatch, не загружен

отвергнут

без #@ и без подписи

загружен, срок 1900-01-01

загружен, срок 1970-01-01

отвергнут

срок 2100-01-01, подпись пересчитана

загружен

загружен

молча

Файл без срока: chrony его отвергает

У chrony 4.6.1 разбор leapseclist заканчивается проверкой:

  /* Make sure the file looks sensible */
  if (!feof(f) || !lsl_updated || !lsl_expiry)
    goto error;

Нет строки #@, значит lsl_expiry остаётся нулём, и файл объявляется негодным. В журнале это выглядит так, в обоих вариантах файла без срока:

Failed to parse leap seconds list /work/e3/noexp.list
Leap second list /work/e3/noexp.list failed check, ignoring
Selected source 127.0.0.1

Время chrony синхронизирует дальше, но работает так, будто директивы leapseclist в конфиге нет. Файл без срока с подписью, пересчитанной уже без строки #@, я не собирал. chrony отверг бы и его: условие выше проверяет только наличие строки срока, подпись тут ни при чём.

У ntpd картина зависит от подписи. Если просто вырезать #@, прежняя подпись перестаёт сходиться, оба демона пишут signature mismatch и таблицу не грузят: в выводе ntpq пропадают leapsec и expire. Без подписи файл загружается, но срок становится нулевым, то есть началом эпохи. У классического ntpd это 1900 год и «expired 46328 days ago», у NTPsec 1970 год и «expired less than 20761 days ago». Для ntpd такой «вечный» файл ничем не лучше просроченного: та же ежедневная ошибка, только с первого дня.

Без жалоб все три приняли один вариант: далёкий срок в #@ и подпись, пересчитанная вместе с ним. Одна деталь: классический ntpq показал срок 2100 года как expire=196311251731. Это переполнение 32-битной NTP-эры, которая кончается 7 февраля 2036 года. Сам демон дату загрузил верно, в журнале expire=2100-01-01.

Какой файл стоит у вас сейчас

Срок файла в системе показывает одна команда. В #@ записаны секунды от 1900 года, отсюда поправка:

date -u -d @$(( $(awk '/^#@/{print $2}' /usr/share/zoneinfo/leap-seconds.list) - 2208988800 )) +%F

Ту же проверку я прогнал по пакетам tzdata из индексов Debian, Ubuntu и Alpine на 3 ноября 2026 года. Для Debian брал самую новую версию из релизного кармана и -updates, для Ubuntu ещё и из -security, для Alpine — из ветки main:

выпуск

tzdata

срок #@

Debian 11

2024a

2024-12-28, истёк

Debian 12

2026b

2026-12-28

Debian 13

2026f

2027-06-28

Ubuntu 20.04

2025b

2025-12-28, истёк

Ubuntu 22.04, 24.04, 26.04

2026e

2027-06-28

Ubuntu 25.10

2026b

2026-12-28

Alpine 3.19

2025b

2025-12-28, истёк

Alpine 3.20

2026b

2026-12-28

Alpine 3.21–3.23, edge

2026e, 2026f

2027-06-28

Три выпуска уже несут истёкший файл, ещё у трёх он истечёт через восемь недель, 28 декабря. Если образ собран только из релизного кармана, без -updates, файл истёк у всех Ubuntu, кроме 26.04. Баг #2144453 в марте жаловался, что истекли все LTS. Для 22.04 и 24.04 это уже исправлено пакетом 2026e, для 20.04 нет.

Что из этого следует

Если IERS продолжит продлевать срок раз в полгода, ничего не меняется: обновляйте tzdata, как раньше. Если перестанет и файл просто истечёт 28 июня 2027 года, в моих прогонах ntpd и NTPsec с таким файлом держали таблицу со смещением 37 секунд, а chrony его не отвергал. Останется ежедневная ошибка в журнале ntpd и NTPsec и предупреждение у chrony, не чаще двух раз в сутки. Пока кто-нибудь не выпустит файл с продлённым сроком, правило мониторинга на эти строки будет срабатывать впустую.

Риск в другом варианте. Если выйдет «вечный» файл без строки #@, после обновления tzdata chrony 4.6.1, который читает этот файл директивой leapseclist, файл отвергнет и останется без таблицы, а у ntpd даст ошибку с первого дня. С директивой leapsectz chrony берёт секунды из зоны tzdb, и этот файл его не касается. После такого обновления стоит поискать в журнале chrony Failed to parse leap seconds list. Для тех, кто будет готовить такой файл: без жалоб все три демона загрузили только далёкий срок с пересчитанной подписью. Если важен вывод ntpq у классического ntpd, срок лучше держать до 2036 года.

Чего я не проверял: синхронизацию ntpd с настоящим вышестоящим сервером и приём объявленной им секунды при истёкшей таблице, значение tai= через ntpq (ntpd публикует его только после синхронизации) и другие версии демонов. Все прогоны сделаны 1 и 3 ноября 2026 года в контейнере на образе python:3.15 (Debian 13).

Комментарии (0)