Роль разрешения DNS заключается в преобразовании доменных имен в соответствующие IP-адреса, поскольку маршрутизаторы в глобальной сети должны знать IP-адрес, чтобы знать, кому отправлять сообщение. DNS — это аббревиатура от Domain Name System, это протокол, вRFC 1035Этот протокол подробно описан. Конкретный процесс показан на следующем рисунке:
Этот процесс кажется простым, но есть несколько проблем:
(1) Как браузер узнает сервер разрешения DNS, такой как 8.8.8.8 на рисунке выше?
(2) Можно ли преобразовать доменное имя в несколько IP-адресов?Если есть только один IP-адрес, в случае большого количества параллелизма этот сервер может взорваться?
(3) После привязки доменного имени к хосту необходимо ли разрешать IP-адрес, указанный локальным хостом, напрямую без имени домена?
(4) Как долго длится эффективное время разрешения доменного имени, то есть сколько времени требуется для повторного разрешения того же доменного имени?
(5) Что такое запись DNS A, запись AAAA, запись CNAME?
На самом деле разрешение доменного имени не имеет прямого отношения к Chrome.Даже самая простая команда curl требует разрешения доменного имени, но мы можем посмотреть исходный код Chrome, чтобы увидеть, на что похож этот процесс, и ответить на поставленные выше вопросы.
Прежде всего, первый вопрос, как браузер узнает сервер разрешения DNS?Вы можете увидеть текущий IP-адрес DNS-сервера в сетевых настройках этой машины, например, моего компьютера:
Эти два DNS-сервера предоставляются положительной широкополосной связью, подключенной к моему дому:
Обычные поставщики услуг широкополосного доступа предоставляют DNS-серверы. Google также предоставляет две бесплатные общедоступные службы DNS, а именно 8.8.8.8 и 8.8.4.4. Эти два IP-адреса легко запоминаются. Когда служба DNS недоступна Когда она прост в использовании, вы можете попробовать изменить эти два.
Как подключенные устройства получают эти IP-адреса? Это через протокол динамической конфигурации хоста (DHCP).Когда устройство подключено к маршрутизатору, маршрутизатор назначает ему IP-адрес через DHCP и сообщает его DNS-серверу.Настройки DHCP маршрутизатора следующие:
Этот процесс можно наблюдать, перехватывая пакеты с помощью wireshark:
Когда мой компьютер подключен к Wi-Fi, он отправит широковещательную рассылку DHCP Request.После того, как маршрутизатор получит эту широковещательную рассылку, он назначит IP-адрес моему компьютеру и сообщит DNS-серверу.
На данный момент в системе есть DNS-сервер, а Chrome — это настройкаres_ninitЭта системная функция (Linux) получает DNS-сервер системы Эта функция получает DNS, читая файл /etc/resolver.conf:
#
# Mac OS X Notice
#
# This file is not used by the host name and address resolution
# or the DNS query routing mechanisms used by most processes on
# this Mac OS X system.
#
# This file is automatically generated.
#
search DHCP HOST
nameserver 59.108.61.61
nameserver 219.232.48.61
Функция поиска заключается в том, что если доменное имя не может быть разрешено, он попытается добавить соответствующий суффикс, например, ping hello. результат не будет решен в конце.
Chrome получает конфигурацию DNS-сервера в соответствии с различными операционными системами при запуске, а затем помещает ее в серверы имен DNSConfig:
// List of name server addresses.
std::vector<IPEndPoint> nameservers;
Chrome также прослушивает изменения в сети, чтобы синхронизировать изменения конфигурации.
Затем используйте этот список серверов имен для инициализации пула сокетов, который является пулом сокетов, а сокет используется для отправки запросов. Когда необходимо выполнить разрешение доменного имени, сокет будет взят из пула сокетов, и будет передан server_index, который вы хотите использовать.При инициализации он равен 0, то есть берется IP-адрес первой службы DNS. запрос разрешения дважды терпит неудачу, затем server_index + 1 использует следующую службу DNS.
unsigned server_index =
(first_server_index_ + attempt_number) % config.nameservers.size();
// Skip over known failed servers.
// 最大attempts数为2,在构造DnsConfig设定的
server_index = session_->NextGoodServerIndex(server_index);
Если все серверы имен выходят из строя, то берется самый ранний отказавший сервер имен.
Когда Chrome запускается, помимо чтения DNS-сервера, он также читает и анализирует файл hosts и помещает его в свойство hosts DNSConfig, которое представляет собой хэш-карту:
// Parsed results of a Hosts file.
//
// Although Hosts files map IP address to a list of domain names, for name
// resolution the desired mapping direction is: domain name to IP address.
// When parsing Hosts, we apply the "first hit" rule as Windows and glibc do.
// With a Hosts file of:
// 300.300.300.300 localhost # bad ip
// 127.0.0.1 localhost
// 10.0.0.1 localhost
// The expected resolution of localhost is 127.0.0.1.
using DnsHosts = std::unordered_map<DnsHostsKey, IPAddress, DnsHostsKeyHash>;
Файл hosts находится в /etc/hosts в системах Linux:
const base::FilePath::CharType kFilePathHosts[] =
FILE_PATH_LITERAL("/etc/hosts");
В чтении этого файла нет никакой хитрости, его нужно обрабатывать построчно, и оценивать некоторые недопустимые ситуации, такие как комментарии к приведенному выше коду.
Таким образом, в DNSConfig есть две конфигурации: одна — это хосты, а другая — серверы имен. DNSConfig объединяется в DNSSession. Отношения их комбинации показаны на следующем рисунке:
Преобразователь — это класс драйвера, отвечающий за синтаксический анализ. Он объединяет клиент. Клиент создает сеанс. Сеансовый уровень играет большую роль в управлении server_index и пулом сокетов, например, в распределении сокетов. Сеанс инициализирует конфигурацию, и конфигурация используется читать локальные привязанные хосты и серверы имен в двух конфигурациях. У каждого из этих слоев есть свои обязанности.
У преобразователя есть важная функция, он объединяет задания для создания очереди заданий. Резолвер также сочетает в себе Hostcache, который является кешем для результатов синтаксического анализа. Если кеш-кэш попадает, нет необходимости его анализировать. Интерфейс сначала определит, может ли он быть обработан локально:
int net_error = ERR_UNEXPECTED;
if (ServeFromCache(*key, info, &net_error, addresses, allow_stale,
stale_info)) {
source_net_log.AddEvent(NetLogEventType::HOST_RESOLVER_IMPL_CACHE_HIT,
addresses->CreateNetLogCallback());
// |ServeFromCache()| will set |*stale_info| as needed.
return net_error;
}
// TODO(szym): Do not do this if nsswitch.conf instructs not to.
// http://crbug.com/117655
if (ServeFromHosts(*key, info, addresses)) {
source_net_log.AddEvent(NetLogEventType::HOST_RESOLVER_IMPL_HOSTS_HIT,
addresses->CreateNetLogCallback());
MakeNotStale(stale_info);
return OK;
}
return ERR_DNS_CACHE_MISS;
Приведенный выше код сначала вызывает serveFromCache, чтобы узнать, есть ли что-то в кеше. Если кеш попадает, он возвращается. В противном случае это зависит от того, попали ли хосты. Если он не попал, он возвращает флаг CACHE_MISS. Если возвращаемое значение не равно CACHE_MISS, вернитесь напрямую:
if (rv != ERR_DNS_CACHE_MISS) {
LogFinishRequest(source_net_log, info, rv);
RecordTotalTime(info.is_speculative(), true, base::TimeDelta());
return rv;
}
В противном случае создайте задание и посмотрите, можно ли его выполнить немедленно.Если очередей заданий слишком много, добавьте его в конец очереди заданий и передайте обработчик успешного обратного вызова.
Итак, здесь в основном то же самое, что и наше познание, сначала посмотрите, есть ли кеш, затем посмотрите, есть ли хост, а если нет, то запросите. При запросе кеша, если кеш устарел, то есть устарел, он также вернет null, а критерии для оценки того, устарел ли он, следующие:
bool is_stale() const {
return network_changes > 0 || expired_by >= base::TimeDelta();
}
То есть сеть изменилась, или expired_by больше 0, считается устаревшим кэшем. Эта разница во времени представляет собой текущее время минус текущее время истечения кэша:
stale.expired_by = now - expires_;
Время истечения — это значение now + ttl, используемое во время инициализации, а этот ttl — это ttl, возвращенный при анализе последнего запроса:
uint32_t ttl_sec = std::numeric_limits<uint32_t>::max();
ttl_sec = std::min(ttl_sec, record.ttl);
*ttl = base::TimeDelta::FromSeconds(ttl_sec);
Приведенный выше код выполняет процесс защиты от переполнения. Этот ttl можно интуитивно увидеть в DNS-ответе wireshark:
Значение TTL текущего доменного имени составляет 600 секунд или 10 минут. Это можно установить у провайдера, у которого вы купили доменное имя:
Кроме того, вы можете видеть, что тип записи A, что такое A, как показано на следующем рисунке:
При добавлении разрешения вы можете видеть, что A разрешает доменное имя в адрес IPv4, AAAA разрешает в адрес IPv6, а CNAME разрешает в другое доменное имя. Преимущество использования CNAME заключается в том, что когда многие другие доменные имена указывают на CNAME, когда необходимо изменить IP-адрес, пока изменяется адрес этого CNAME, другие также вступят в силу, но необходимо вторичное разрешение. Готово.
Если доменное имя не может быть разрешено локально, Chrome отправит запрос. Операционная система предоставляет системную функцию под названием getaddrinfo для разрешения доменного имени, но Chrome не использует ее, а сам реализует DNS-клиент, включая инкапсуляцию сообщений запроса DNS и анализ сообщений ответа DNS. Это может быть связано с большей гибкостью, например, Chrome может решать, как использовать серверы имен, порядок и количество неудачных попыток.
Начните синтаксический анализ в startJob распознавателя. Получите следующий queryId, затем создайте запрос, затем создайте DnsUDPAttempt, а затем выполните его запуск, поскольку запрос DNS-клиента использует UDP-пакеты (вторичный сервер доменных имен запрашивает первичный сервер доменных имен с помощью TCP):
uint16_t id = session_->NextQueryId();
std::unique_ptr<DnsQuery> query;
query.reset(new DnsQuery(id, qnames_.front(), qtype_, opt_rdata_));
DnsUDPAttempt* attempt =
new DnsUDPAttempt(server_index, std::move(lease), std::move(query));
int rv = attempt->Start(
base::Bind(&DnsTransactionImpl::OnUdpAttemptComplete,
base::Unretained(this), attempt_number,
base::TimeTicks::Now()));
Конкретный процесс разбора разбит на несколько шагов, организация кода такая, а порядок выполнения определяется состоянием:
int rv = result;
do {
// 最开始的state为STATE_SEND_QUERY
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_SEND_QUERY:
rv = DoSendQuery();
break;
case STATE_SEND_QUERY_COMPLETE:
rv = DoSendQueryComplete(rv);
break;
case STATE_READ_RESPONSE:
rv = DoReadResponse();
break;
case STATE_READ_RESPONSE_COMPLETE:
rv = DoReadResponseComplete(rv);
break;
default:
NOTREACHED();
break;
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
После выполнения первого кейса состояние становится состоянием второго кейса, а в функции выполнения второго кейса оно меняется на третье и так далее, пока в цикле while не станет STATE_DONE, или Состояние ERR для завершения текущей транзакции транзакции. Так что эта организация кода довольно интересна.
После успешного окончательного парсинга результат будет помещен в кеш:
if (did_complete) {
resolver_->CacheResult(key_, entry, ttl);
RecordJobHistograms(entry.error());
}
Затем создайте список адресов и передайте его соответствующему обратному вызову, поскольку разрешение DNS может возвращать несколько результатов, например следующие:
Здесь мы не используем Chrome для печати результатов, мы просто смотрим на вывод wireshark напрямую, потому что добавить функцию печати сложнее, и более интуитивно понятно смотреть на вывод wireshark напрямую и экономить время.
Эта статья кратко знакомит с процессом разрешения DNS и некоторыми связанными с ним концепциями DNS.Я полагаю, что на этом этапе вы должны быть в состоянии ответить на поставленные выше вопросы. Как правило, клиент инициирует запрос к DNS-серверу, и сервер возвращает ответ. Серверы имен DNS-серверов отправляются на устройство маршрутизатором через DHCP, когда устройство подключено к сети. Chrome будет инициировать запрос в порядке серверов имен и кэшировать результаты. Время действия основано на ttl и кеше. используется непосредственно для двух запросов в течение периода действия. Существует несколько типов результатов разрешения DNS, наиболее распространенными являются записи A и записи CNAME.Запись указывает, что результатом является IP-адрес, а CNAME указывает, что результатом является другое доменное имя.
Эта статья не содержит подробностей и подробностей, но основные понятия и логические процессы должны быть освещены.