Что такое DNS?

Ответ

DNS (Domain Name System) — это распределённая иерархическая система, преобразующая удобные для человека доменные имена (например, www.example.com) в машиночитаемые IP-адреса (например, 93.184.216.34). Это фундаментальный протокол прикладного уровня интернета.

Как это работает в контексте C++ сетевого программирования: Перед установкой соединения по TCP или отправкой UDP-пакета на хост по имени, программа должна разрешить это имя в адрес. Для этого используются функции, такие как getaddrinfo, которые инкапсулируют сложность DNS-запроса.

Пример разрешения имени в C++ с использованием сокетов Беркли:

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

std::string resolve_hostname(const std::string& hostname) {
    struct addrinfo hints = {}, *res;
    hints.ai_family = AF_UNSPEC;    // Принимаем IPv4 или IPv6
    hints.ai_socktype = SOCK_STREAM; // Для TCP

    int status = getaddrinfo(hostname.c_str(), nullptr, &hints, &res);
    if (status != 0) {
        throw std::runtime_error(gai_strerror(status));
    }

    // Берём первый адрес из списка и преобразуем в строку
    char ipStr[INET6_ADDRSTRLEN];
    void* addr;
    if (res->ai_family == AF_INET) { // IPv4
        addr = &((struct sockaddr_in*)res->ai_addr)->sin_addr;
    } else { // IPv6
        addr = &((struct sockaddr_in6*)res->ai_addr)->sin6_addr;
    }
    inet_ntop(res->ai_family, addr, ipStr, sizeof(ipStr));

    freeaddrinfo(res);
    return std::string(ipStr);
}

int main() {
    try {
        std::string ip = resolve_hostname("google.com");
        std::cout << "Resolved IP: " << ip << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}

Важные для разработчика детали:

  • Кеширование: DNS-ответы кешируются на разных уровнях (ОС, провайдер, локальный резолвер), что ускоряет повторные запросы.
  • Типы записей: Помимо основной записи A (IPv4) или AAAA (IPv6), существуют MX (для почты), CNAME (алиас), PTR (обратное разрешение).
  • Протокол: Обычно работает поверх UDP (порт 53) для запросов, но может использовать TCP для больших ответов или зонных трансферов.
  • Асинхронное разрешение: В высокопроизводительных приложениях (например, асинхронных серверах) используют неблокирующие или асинхронные DNS-клиенты, чтобы не блокировать поток на время сетевого запроса.

Ответ 18+ 🔞

Давай разберём эту DNS-хуйню, а то некоторые думают, что интернет — это магия, а не протоколы. Представь, что ты хочешь позвать друга в гости, но помнишь только его кличку "Лохматый". DNS — это такой всемирный справочник, который по кличке "Лохматый" находит его точный адрес: город, улицу, дом, квартиру. Только вместо друга — сервер, а вместо адреса — IP, типа 93.184.216.34. Ёпта, без этого ты в интернете нихуя не найдёшь.

Как эта хрень работает, когда ты пишешь код на C++: Допустим, твоей программе надо постучаться на google.com. Она же не понимает буквы, ей нужны циферки. Вот тут ты и вызываешь всякие функции, вроде getaddrinfo, которые делают всю грязную работу: идут спрашивать у DNS-серверов, где этот самый гугл живёт. Главное — не писать это говно в основном потоке, если делаешь что-то серьёзное, а то всё повиснет, пока где-то там на другом конце света мартышлюшка с DNS тебе отвечает.

Смотри, как это выглядит в коде (не бойся, тут ничего сложного):

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

std::string resolve_hostname(const std::string& hostname) {
    struct addrinfo hints = {}, *res;
    hints.ai_family = AF_UNSPEC;    // Без разницы, IPv4 или IPv6 — сойдёт любая дырка в заборе
    hints.ai_socktype = SOCK_STREAM; // Для TCP

    int status = getaddrinfo(hostname.c_str(), nullptr, &hints, &res);
    if (status != 0) {
        throw std::runtime_error(gai_strerror(status));
    }

    // Выковыриваем первый попавшийся адрес из списка и превращаем в строку
    char ipStr[INET6_ADDRSTRLEN];
    void* addr;
    if (res->ai_family == AF_INET) { // Старый добрый IPv4
        addr = &((struct sockaddr_in*)res->ai_addr)->sin_addr;
    } else { // Модный IPv6
        addr = &((struct sockaddr_in6*)res->ai_addr)->sin6_addr;
    }
    inet_ntop(res->ai_family, addr, ipStr, sizeof(ipStr));

    freeaddrinfo(res); // Не забудь за собой убрать, а то память потечёт
    return std::string(ipStr);
}

int main() {
    try {
        std::string ip = resolve_hostname("google.com");
        std::cout << "Resolved IP: " << ip << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}

А теперь, чувак, держи важные фишки, о которых часто забывают:

  • Кеширование, ёб твою мать! Система не тупо каждый раз бегает на край света. Ответы кешируются везде: у тебя в ОС, у провайдера, ещё где. Это чтобы не тратить время на одно и то же. Представь, что ты каждый раз ищешь в справочнике номер "Лохматого", хотя он у тебя на холодильнике магнитиком прилеплен.

  • Типы записей — их овердохуища. Ты думал, есть только адрес (A или AAAA)? А как же MX для почтовых серверов? Или CNAME, который говорит, что "www" — это просто псевдоним? А PTR, который по адресу находит имя? Вот именно.

  • Протокол. Обычно DNS-запросы — это короткие вопли по UDP на 53-й порт. Быстро и просто. Но если ответ не влезает в одну посылку, или нужно передать всю зону (зонный трансфер), то переходят на TCP. Так что это не всегда только UDP.

  • Асинхронное разрешение — вот где собака порылась. Если твоё приложение должно работать быстро и не тормозить, нельзя блокировать поток, пока DNS-сервер где-то там думает. Надо использовать неблокирующие вызовы или специальные асинхронные либы. Иначе пользователи подумают, что всё накрылось медным тазом, пока ты ждёшь ответа про какой-то yahoo.com. Волнение ебать, а терпения ноль.