Deep Engineering
Средний·Опубликовано·30 МИН

Разрешение имени: приложение зовёт не DNS, а getaddrinfo

Между строкой «медленный DNS» в постмортеме и настоящим сервером имён стоит библиотека и три файла. Почти всё, за что ругают сеть, решается в них: ответ может прийти вообще без сети, кеша в процессе нет, а имя, которого не существует, стоит секунд, посчитанных из настроек, ещё до того, как начнёт тикать таймаут приложения.

Полное техническое изложение

TL;DR

Между именем и соединением стоит отдельный шаг, и делает его не приложение. Программа просит у системы адрес по имени, а система решает, где взять ответ: сначала локальный файл, и только потом сервер имён по сети. Поэтому «идём в DNS» — не полный ответ: ответ может прийти вообще без сети, а цена этого шага задана настройками машины, а не скоростью чужого сервера.

Отсюда главное следствие: этот шаг не ограничен таймаутом запроса. Таймаут ставится на соединение, а соединять ещё не с чем. Измерено: при timeout:2 attempts:3 и двух серверах имён один вызов для несуществующего имени длился 12,0 секунды — и всё это время клиент с трёхсотмиллисекундным таймаутом просто ждал. Ограничить такой вызов может только дедлайн, охватывающий его целиком.

Дальше — числа, имена настроек и границы. Функция, которую зовёт приложение, — getaddrinfo; порядок источников задаёт nsswitch.conf, расписание повторов — resolv.conf, и у самой getaddrinfo параметра таймаута нет вовсе. Измерено: localhost — 0,01 мс из /etc/hosts, example.com — 29,32 мс через сеть. На исследованном пути кеша нет: шестой вызов для одного имени стоил ровно столько же, сколько первый, — 2,17 мс против 2,17, — а адрес между вызовами сменился; но это граница замера, а не свойство мира — кеш может стоять в нескольких других местах, и урок их перечисляет. В этой конфигурации резолвера цена ненайденного имени сложилась как timeout × attempts × число серверов: 3003,7 мс при timeout:1 attempts:3 и 4003,8 мс при двух серверах и timeout:1 attempts:2 — то есть второй сервер не запасной, а удвоение худшего случая: он получает все попытки после первого, а не вместо него. Умолчание спрашивает дважды: AF_UNSPEC отправил 2 запроса, AF_INET — 1. А поисковая область превращает один поиск в несколько: имя с одной точкой при ndots:5 спрошено дважды и стоило 2002,5 мс, то же имя с точкой на конце — один раз и 1001,4 мс.

Порог входа
Перед уроком достаточно понимать
  • в коде и в конфигурации сервис записан именем, а не числовым адресом;
  • соединение в конце концов устанавливается всё-таки с адресом, значит где-то между ними есть перевод;
  • у запроса к сервису обычно есть таймаут, поставленный в клиенте.
Заранее знать не нужно
  • как устроен DNS изнутри: зоны, записи, рекурсия, кеширование на серверах;
  • getaddrinfo, nsswitch.conf, resolv.conf, ndots, AF_UNSPEC — всё это встретится дальше и будет объяснено по дороге.

Что здесь на самом деле спрашивают

Лестница выглядит так:

  1. «Как приложение узнаёт адрес по имени?» — разминка, на которой напрашивается ответ «идёт в DNS».
  2. «Что произойдёт, если имя есть в /etc/hosts и в DNS?» — вопрос про порядок источников.
  3. «Где кешируется результат?» — вопрос-ловушка: на прямом пути — нигде.
  4. «У нас таймаут 300 мс, а запрос висел 12 секунд. Как?» — главный вопрос темы.
  5. «Почему в кластере имена разрешаются медленнее, чем снаружи?» — вопрос про поисковые области и ndots.
  6. «Что делать, если сервер имён перестал отвечать?» — вопрос про то, что запасного сервера не бывает.

Числа получены прогоном bench/nameres/resolve.py и bench/nameres/practice.py. Времена настоящих запросов в этой среде гуляют от 2,2 до 29,3 мс, поэтому на них здесь не опирается ни одно утверждение: содержательны счёт запросов и выдержки, которые библиотека отсчитывает по часам.

База: имя, адрес, соединение

Модель, из которой всё выводится, состоит из трёх слов: имя → адрес → соединение. В коде и в конфигурации написано имя; соединение в конце концов устанавливается с числовым адресом; значит, между ними обязан быть отдельный шаг перевода. Этот шаг происходит до того, как появится хоть какое-то соединение, — и это не мелочь, а причина половины сюрпризов в теме.

На вопрос «кто переводит имя в адрес» обычно отвечают «DNS», и ответ неполон в одном важном месте: приложение не разговаривает с сервером имён само. Оно обращается к операционной системе — одной просьбой «дай адрес по этому имени», — а уже система решает, где взять ответ. За её решением стоит порядок источников: сначала локальный файл на этой же машине, и только если там ничего не нашлось — сервер имён по сети.

Отсюда сразу три следствия, ради которых стоит весь урок. Ответ может прийти вообще без сети, и тогда никакого DNS в происходящем нет. Длительность этого шага задана не скоростью чужого сервера, а настройками самой машины — тем, у кого спрашивать, сколько ждать и сколько раз повторять. И, наконец, таймаут, поставленный вами на запрос, к этому шагу не относится: он ставится на соединение, а соединять ещё не с чем.

Ту самую системную функцию в Linux зовут getaddrinfo, и дальше урок отвечает на вопрос «как это делает ОС»: откуда берётся порядок источников, из чего складывается цена ответа и почему один такой вызов может стоить секунд.

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

Механизм 1: ответ может прийти без сети

контракт языкаПорядок источников задаёт nsswitch.conf(5). Это поведение glibc, а не свойство DNS: от языка и фреймворка оно не зависит, потому что все они в конце концов зовут одну и ту же функцию.

Вопрос «сколько стоит DNS» поставлен неверно с самого начала. Приложение зовёт getaddrinfo, а куда та пойдёт за ответом — решает отдельный файл:

The order of the services on the line determines the order in which those services will be queried, in turn, until a result is found.

перевод

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

nsswitch.conf(5)

Строка hosts: files dns читается буквально: сначала /etc/hosts, и только если там не нашлось — сервер имён. Проверяем:

1. THE ANSWER CAN COME WITHOUT ANY NETWORK AT ALL
-------------------------------------------------
  nsswitch.conf says hosts:                    files dns
  getaddrinfo('localhost'), ms                 0.01  -> 127.0.0.1
  getaddrinfo('example.com'), ms               29.32  -> 104.20.23.154
наблюдение замераbench/nameres/resolve.py. Первый вызов в процессе грузит модули NSS, и эта разовая цена вынесена за замер отдельным прогревом. Миллисекунды второй строки у вас будут другими; первая останется на два-три порядка меньше.

Один и тот же вызов, одна и та же библиотека — и совершенно разная цена, потому что первое имя не покинуло машину.

Отсюда два практических следствия, за которые этот механизм и спрашивают. Первое: запись в /etc/hosts побеждает DNS молча, и её никто не увидит в трассировке — сетевого запроса не было. Второе: если приложение в контейнере ходит не туда, /etc/hosts образа — первое, куда стоит посмотреть, а не последнее.

Механизм 2: на исследованном пути кеша нет

Заблуждение, ради которого написан раздел: «разрешили один раз, дальше из кеша». Проверим шестью вызовами подряд.

2. THERE IS NO CACHE INSIDE THE PROCESS
---------------------------------------
  call 1 of getaddrinfo('example.com'), ms     2.17  -> 104.20.23.154
  call 2 of getaddrinfo('example.com'), ms     24.06  -> 104.20.23.154
  call 3 of getaddrinfo('example.com'), ms     2.15  -> 172.66.147.243
  call 4 of getaddrinfo('example.com'), ms     2.41  -> 104.20.23.154
  call 5 of getaddrinfo('example.com'), ms     14.82  -> 104.20.23.154
  call 6 of getaddrinfo('example.com'), ms     2.17  -> 104.20.23.154
  distinct addresses returned                  2
  last call against the first, ms              2.17 against 2.17
наблюдение замераbench/nameres/resolve.py. Смотреть здесь надо не на отдельные миллисекунды — они шумят, — а на две вещи: разброс не зависит от номера вызова, и адрес меняется на третьем.

Смотреть надо на две строки. Последний вызов стоит ровно столько же, сколько первый: 2,17 мс против 2,17, хотя между ними было четыре одинаковых. Разброс внутри ряда — от 2,15 до 24,06 мс — с номером вызова никак не связан. Адрес не постоянен: он сменился на третьем вызове, и за шесть вызовов их вернулось два разных.

Оба наблюдения об одном: на этом пути результат нигде не запоминается.

И границу у вывода надо назвать сразу, потому что звучит он шире, чем есть на деле. Измерено отсутствие кеша в конкретном пути: прямые вызовы getaddrinfo в этой среде, с её строкой hosts: и её резолвером. Замер не говорит, что кеша нет вообще, — он не касался ни одного из мест, где кеш вполне может оказаться:

  • в самом приложении или его рантайме — клиентские библиотеки и виртуальные машины нередко держат собственный кеш имён;
  • в другом источнике NSS — в строке hosts: может стоять не только files и dns;
  • в системном кеширующем резолвере на той же машине — nscd, systemd-resolved;
  • в узловом кеше кластера, на который направлен резолвер контейнера;
  • в сопроводительном процессе рядом с приложением, через который идёт его трафик.

Поэтому правило пишется от границы, а не от замера: «кешируется ли у нас имя» — вопрос к среде, а не к библиотеке, и отвечают на него проверкой своего пути, а не умолчанием. Рассчитывать по умолчанию не на что.

Практический вывод: библиотека клиента, которая зовёт имя на каждый запрос, в среде без кеша платит за это на каждом запросе. Отсюда привычка держать соединение, а не открывать новое: переиспользованное соединение не только экономит рукопожатие, но и вообще не спрашивает имени.

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

Механизм 3: цена имени, которого нет, — арифметика

контракт языкаСмысл timeout, attempts и строк nameserver задан resolv.conf(5). Из документа следует и порядок: второй сервер опрашивается после первого.

Цену задают три настройки: timeout, attempts и число строк nameserver. Статья про timeout сама предупреждает, чем это кончается:

Sets the amount of time the resolver will wait for a response from a remote name server before retrying the query via a different name server. This may not be the total time taken by any resolver API call and there is no guarantee that a single resolver API call maps to a single timeout.

перевод

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

resolv.conf(5), timeout

«Нет гарантии, что один вызов соответствует одному таймауту» — это и есть предупреждение. Сколько именно таймаутов приходится на один вызов, видно из замера:

3. A NAME THAT DOES NOT RESOLVE COSTS timeout x attempts x servers
------------------------------------------------------------------
   nameservers  timeout  attempts  expected, s  measured, ms  queries
             1        1         1            1        1001.4        1
             1        1         2            2        2002.7        2
             1        1         3            3        3003.7        3
             1        2         2            4        4004.2        2
             2        1         2            4        4003.8        4
наблюдение замераbench/nameres/resolve.py. Сервер имён здесь свой: он стоит на 127.0.0.1:53, принимает запросы и не отвечает ни на один. Поэтому в числах нет ни чужой сети, ни чужой нагрузки — только расписание повторов самой библиотеки.

Колонка «expected» посчитана умножением до замера, колонка «measured» — результат. Они совпадают с точностью до нескольких миллисекунд: в этой конфигурации резолвера цена ненайденного имени сложилась как timeout × attempts × число серверов, а не «сколько-нибудь».

Границу у этого равенства надо назвать сразу, иначе оно превращается в универсальную формулу, которой не является. Умножение здесь — не закон, а результат конкретной настройки: один резолвер glibc, единственный источник в строке hosts: и сервер имён, который принимает запросы и не отвечает ни на один. Сам resolv.conf(5) предупреждает об этом в уже процитированной строке: «нет гарантии, что один вызов резолвера соответствует одному таймауту». Достаточно изменить любое из условий — другой резолвер, сервер, который отвечает отказом вместо молчания, кеширующий посредник по дороге, поисковый список, — и множители перестанут складываться в это произведение.

Поэтому правило пишется от границы, а не от формулы: длительность этого шага задана настройками резолвера, а не сетью, и узнают её, посчитав по своему resolv.conf и проверив замером, а не приняв на веру произведение трёх чисел. Полезно оно тем, что задаёт порядок величины: секунды, а не миллисекунды.

Последняя строка отвечает на вопрос «что даёт второй сервер». Замер показывает множитель: два сервера — четыре запроса вместо двух. А то, что второй опрашивается после первого, а не вместо него, говорит resolv.conf(5) прямо в процитированной строке: резолвер ждёт полный таймаут, «прежде чем повторить запрос через другой сервер».

Теперь возьмите настройки, обычные для контейнера: timeout:2 attempts:3 и два сервера. Замер с ними даёт 12,0 секунды на одно имя, которого нет.

Куда попадают эти двенадцать секунд — вопрос отдельный, и ответ на него не в замере, а в сигнатуре: у getaddrinfo нет параметра таймаута вовсе. Таймаут, поставленный на сокет, эту часть не ограничивает — соединять ещё не с чем. Ограничить её может только дедлайн, охватывающий весь вызов целиком.

Это и есть ответ на вопрос «у нас таймаут 300 мс, а висело 12 секунд»: таймаут стоял на соединении.

Механизм 4: умолчание спрашивает дважды

Раздел короткий, но объясняет частый вид «иногда медленно, иногда нет»: вызов ждёт двух ответов, а приходит один. getaddrinfo(3) говорит, что бывает, когда подсказки не заданы:

Specifying hints as NULL is equivalent to setting ai_socktype and ai_protocol to 0; ai_family to AF_UNSPEC; and ai_flags to (AI_V4MAPPED | AI_ADDRCONFIG).

перевод

Передача hints как NULL равносильна установке ai_socktype и ai_protocol в 0, ai_family — в AF_UNSPEC, а ai_flags — в (AI_V4MAPPED | AI_ADDRCONFIG).

getaddrinfo(3)

А AF_UNSPEC — это «любое семейство»: и IPv4, и IPv6. То есть два вопроса вместо одного:

4. AF_UNSPEC ASKS TWICE: ONE QUESTION PER ADDRESS FAMILY
--------------------------------------------------------
  AF_UNSPEC (the default of most clients)      2 queries, 1001.3 ms
  AF_INET (IPv4 only)                          1 query, 1001.3 ms
наблюдение замераbench/nameres/resolve.py. Считаются запросы, а не миллисекунды: на настоящем сервере имён разброс времени больше самой разницы, а счёт запросов точен.

Времена в двух строках почти совпали, и это не случайность: оба вопроса уходят одновременно, и вызов ждёт их параллельно. Второй вопрос удваивает не время, а число участников, от которых зависит ответ.

Отсюда следует — уже не из замера, а из того, что вызов ждёт обоих, — неприятный класс аварий: сервер, который отвечает на A мгновенно и молчит на AAAA, делает медленным весь вызов, хотя нужный ответ уже пришёл. Со стороны приложения это выглядит как «DNS иногда тормозит», и по одному только времени причину не отличить.

Диагностика отсюда простая: если одно и то же имя разрешается быстро при явном AF_INET и медленно при умолчании — дело не в сети, а во втором вопросе.

Оговорка из той же цитаты: AI_ADDRCONFIG может второй вопрос и снять — если у машины нет ни одного глобального адреса IPv6. В среде замера он есть, поэтому в прогоне два запроса, а не один.

Глубже: поисковые области размножают запрос

контракт языкаСмысл ndots и поискового списка задан resolv.conf(5): порог считается по числу точек в имени, а имя с точкой на конце считается полным.

Последний раздел — про то, почему в кластере имена разрешаются иначе, чем на ноутбуке. resolv.conf(5) описывает механизм и сам же предупреждает о цене:

Note that this process may be slow and will generate a lot of network traffic if the servers for the listed domains are not local, and that queries will time out if no server is available for one of the domains.

перевод

Заметьте, что этот процесс может быть медленным и создаст много сетевого трафика, если серверы для перечисленных доменов не локальны, и что запросы завершатся по таймауту, если для одного из доменов нет доступного сервера.

resolv.conf(5), search

Порогом заведует ndots: имя, в котором точек меньше порога, считается неполным, и к нему сначала приписываются поисковые области. Что при этом спрашивается на самом деле:

5. SEARCH DOMAINS TURN ONE LOOKUP INTO SEVERAL
----------------------------------------------
  ndots:5, name has one dot                    2002.5 ms, 2 lookups
    asked for                                  zz-probe-9931.internal.svc.cluster.local
    asked for                                  zz-probe-9931.internal
  ndots:1, same name                           2002.9 ms, 2 lookups
    asked for                                  zz-probe-9931.internal
    asked for                                  zz-probe-9931.internal.svc.cluster.local
  ndots:5, same name with a trailing dot       1001.4 ms, 1 lookup
    asked for                                  zz-probe-9931.internal
наблюдение замераbench/nameres/resolve.py. Имена в строках asked for — это то, что библиотека действительно отправила: они разобраны из пакетов, пришедших на свой сервер имён.

Три строки — три разных ответа на один и тот же вопрос «какой адрес у имени, кончающегося на .internal».

При ndots:5 в имени одна точка, порог не пройден, и первым спрашивается имя с приписанной областью — то, которого заведомо нет. Настоящее имя пробуется вторым.

При ndots:1 порог пройден, и порядок обратный: сначала имя как есть, область — вторым запросом.

С точкой на конце имя объявлено полным, и поисковый список не трогается вовсе: один запрос вместо двух, ровно вдвое дешевле.

Соединяя с механизмом 3: каждое лишнее имя проходит всю цепочку timeout × attempts × серверов заново. Замера такой связки нет — она складывается арифметически: при умолчаниях среды и трёх пробуемых именах это трижды по двенадцать секунд.

Как отвечать на собеседовании

Короткий ответ: приложение зовёт не DNS, а функцию операционной системы, за которой стоит порядок источников — сначала файл, потом сервер имён. Поэтому ответ может прийти вообще без сети, а длительность этого шага задана настройками машины и не ограничена таймаутом запроса: таймаут ставится на соединение, а соединять ещё не с чем.

Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.

Если интервьюер копает глубже

Функция называется getaddrinfo, порядок источников задаёт nsswitch.conf, а расписание повторов — resolv.conf. Измерено: имя из файла — 0,01 мс без единого пакета; имя, которого нет, в этой конфигурации резолвера стоило timeout × attempts × число серверов секунда в секунду.

Хороший ответ отличают три вещи. Первая — вы говорите, что у getaddrinfo нет параметра таймаута: таймаут, поставленный на сокет, эту часть не ограничивает, потому что соединять ещё не с чем. Вторая — вы называете второй сервер имён не запасным, а удвоением худшего случая, и объясняете почему. Третья — вы упоминаете, что на прямом пути через getaddrinfo кеша не оказалось, и делаете из этого правильный вывод: не «добавим кеш», а «не будем разрешать имя на каждый запрос».

Две оговорки, которые отличают точный ответ от заученного. Первая: «кеша нет» — утверждение про исследованный путь, а не про мир; кеш может стоять в самом приложении или его рантайме, в другом источнике NSS, в системном кеширующем резолвере, в узловом кеше кластера или в соседнем процессе рядом с приложением, и есть ли он у вас — вопрос к среде. Вторая: timeout × attempts × серверов — не универсальная формула, а то, как сложилась цена при этой конфигурации резолвера и сервере, который молчит; менять надо не веру в формулу, а собственный resolv.conf, и проверять замером.

Чего говорить не стоит: «DNS отвечает за столько-то миллисекунд». Это число ни о чём: оно зависит от сервера, кеша и того, спрашиваете ли вы одно семейство адресов или два.

Дальше спросят

Спросят дальше

Как поставить таймаут на разрешение имени?

Короткий ответ

Обычным способом — никак. В сигнатуре getaddrinfo таймаута нет: длительность целиком задана файлом, а не вызовом. Таймаут, поставленный на сокет, эту часть не ограничивает; дедлайн, охватывающий весь вызов, — ограничивает, и именно так устроены средства вроде общего бюджета запроса из шестого урока.

Отсюда два практических пути. Первый — уменьшать timeout и attempts, то есть чинить причину: при timeout:1 attempts:2 худший случай на одном сервере равен двум секундам вместо шести, а на двух — четырём вместо двенадцати. Второй — уносить разрешение имени из критического пути: держать соединения открытыми, разрешать имя заранее и обновлять фоном.

Спросят дальше

Помогает ли локальный кеширующий резолвер?

Короткий ответ

Помогает от повторов, но не от промахов. Попадание в кеш убирает сетевой запрос целиком; промах по несуществующему имени по-прежнему стоит полной арифметики, только теперь между приложением и сервером стоит ещё один участник.

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

Спросят дальше

Что делать с `ndots` в кластере?

Короткий ответ

Понимать, что он про порядок, а не про запрет. При большом ndots короткие внутренние имена находятся первым же запросом — ради этого он и большой, — а вот внешние имена вроде api.example.com сначала пробуются с приписанной областью и стоят лишнего.

Точка на конце снимает вопрос для конкретного имени: замер показывает один запрос вместо двух — 1001,4 мс против 2002,5. Это самый дешёвый приём в теме, потому что не меняет настроек среды и действует ровно там, где написан.

Спросят дальше

Почему одно и то же имя иногда возвращает разные адреса?

Короткий ответ

Потому что за именем может стоять несколько адресов, и порядок в ответе не обязан быть постоянным. В замере шесть вызовов подряд вернули два разных адреса.

Практическое следствие важнее самого факта: клиент не должен считать, что адрес у имени один. Проверка «а тот ли это адрес» и привязка состояния к адресу — источник аварий при любом изменении на стороне сервиса; правильная единица — имя, а не адрес.

Частые заблуждения

Утверждение

Разрешение имени — это всегда запрос в сеть

На самом деле

Не всегда: порядок источников задаёт nsswitch.conf, и при hosts: files dns файл побеждает сеть. Измерено: localhost разрешился за 0,01 мс и не отправил ни одного пакета. Поэтому запись в /etc/hosts образа переопределяет DNS молча и в трассировке сетевого трафика не видна.

Утверждение

Результат разрешения кешируется, поэтому платим только за первый раз

На самом деле

На исследованном пути — прямых вызовах getaddrinfo в этой среде — нет. Измерено: шестой вызов стоил ровно столько же, сколько первый (2,17 мс против 2,17), а адрес между ними менялся. Замер не отменяет кеша в других местах: он может быть в самом приложении или его рантайме, в другом источнике NSS, в системном кеширующем резолвере (nscd, systemd-resolved), в узловом кеше кластера или в соседнем процессе рядом с приложением. Есть ли он у вас — вопрос к среде, и отвечает на него проверка, а не умолчание.

Утверждение

Второй сервер имён в resolv.conf — это резервирование

На самом деле

Это удвоение худшего случая. Измерено: при timeout:1 attempts:2 один сервер стоил 2002,7 мс, два — 4003,8 мс и четыре запроса. Что второй опрашивается после первого, а не вместо него, сказано в resolv.conf(5): резолвер ждёт полный таймаут, прежде чем повторить запрос через другой сервер.

Утверждение

Таймаут запроса ограничивает и разрешение имени

На самом деле

Таймаут, поставленный на сокет, — не ограничивает: соединять ещё не с чем, а в сигнатуре getaddrinfo таймаута нет вовсе. Измерено: при timeout:2 attempts:3 и двух серверах сам вызов длился 12,0 с. Ограничить его может только дедлайн, охватывающий весь вызов, а не выдержка на сокете.

Утверждение

Запрос за адресом — один

На самом деле

При умолчании их два. getaddrinfo(3): пустые подсказки означают ai_family = AF_UNSPEC, то есть «любое семейство». Измерено: 2 запроса при AF_UNSPEC против 1 при AF_INET. Сервер, молчащий на одном семействе, делает медленным весь вызов.

Практика

Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ печатает сам скрипт замера.

Практика · что напечатает

В resolv.conf есть строка search svc.cluster.local cluster.local. Печатаются три числа: сколько имён библиотека спросила при ndots:5 для имени с одной точкой; сколько имён она спросила для того же имени с точкой на конце; и сколько запросов ушло на одно имя, когда семейство адресов не задано. Что напечатает этот код?
relative = asked_for(server, f"{PROBE}.internal", ndots=5)
absolute = asked_for(server, f"{PROBE}.internal.", ndots=5)
families = family_queries(server)
print(len(relative))
print(len(absolute))
print(families)

Практика · оцените

В resolv.conf стоит timeout:2, attempts:3 и два сервера имён, и ни один из них не отвечает. Сколько секунд займёт одна попытка разрешить имя, которого нет?
секунд

Проверка знаний

Вопрос 1 из 5

Имя есть и в /etc/hosts, и в DNS, а в nsswitch.conf написано hosts: files dns. Какой адрес получит приложение?

Источники и что читать дальше

3 ИСТОЧНИКА

  1. getaddrinfo(3), Linux man-pages 6.7Официальная документация. Что на самом деле зовёт приложение и с какими умолчаниями. Про семейство адресов: «The value AF_UNSPEC indicates that getaddrinfo() should return socket addresses for any address family (either IPv4 or IPv6, for example) that can be used with node and service» (Значение AF_UNSPEC означает, что getaddrinfo() должен вернуть адреса сокетов для любого семейства адресов (например, IPv4 или IPv6), пригодного для node и service). И про то, что это и есть умолчание, когда подсказки не заданы: «Specifying hints as NULL is equivalent to setting ai_socktype and ai_protocol to 0; ai_family to AF_UNSPEC; and ai_flags to (AI_V4MAPPED | AI_ADDRCONFIG)» (Передача hints как NULL равносильна установке ai_socktype и ai_protocol в 0, ai_family — в AF_UNSPEC, а ai_flags — в (AI_V4MAPPED | AI_ADDRCONFIG)). Последний флаг важен: при отсутствии у машины глобального адреса IPv6 он снимает второй вопрос.https://man7.org/linux/man-pages/man3/getaddrinfo.3.html
  2. nsswitch.conf(5), Linux man-pages 6.7Официальная документация. Откуда берётся порядок источников — почему файл побеждает сеть: «The order of the services on the line determines the order in which those services will be queried, in turn, until a result is found» (Порядок служб в строке определяет порядок, в котором эти службы будут опрашиваться по очереди, пока не найдётся результат). Строка `hosts: files dns` — это и есть «сначала /etc/hosts, потом сервер имён».https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html
  3. resolv.conf(5), Linux man-pages 6.7Официальная документация. Три настройки, из которых складывается цена ненайденного имени. `timeout`: «Sets the amount of time the resolver will wait for a response from a remote name server before retrying the query via a different name server. This may not be the total time taken by any resolver API call and there is no guarantee that a single resolver API call maps to a single timeout» (Задаёт время, которое резолвер будет ждать ответа от удалённого сервера имён, прежде чем повторить запрос через другой сервер. Это может быть не полное время вызова резолвера, и нет гарантии, что один вызов соответствует одному таймауту). `attempts`: «Sets the number of times the resolver will send a query to its name servers before giving up and returning an error to the calling application» (Задаёт, сколько раз резолвер пошлёт запрос своим серверам имён, прежде чем сдаться и вернуть ошибку вызывающему приложению). `ndots`: «The default for n is 1, meaning that if there are any dots in a name, the name will be tried first as an absolute name before any search list elements are appended to it» (Умолчание для n — 1: это значит, что если в имени есть хоть одна точка, имя сначала будет опробовано как абсолютное, прежде чем к нему добавят элементы списка поиска). И прямое предупреждение про поисковые области: «Note that this process may be slow and will generate a lot of network traffic if the servers for the listed domains are not local, and that queries will time out if no server is available for one of the domains» (Заметьте, что этот процесс может быть медленным и создаст много сетевого трафика, если серверы для перечисленных доменов не локальны, и что запросы завершатся по таймауту, если для одного из доменов нет доступного сервера).https://man7.org/linux/man-pages/man5/resolv.conf.5.html