9.15. Імена та DNS

Усі попередні сторінки використовували числові IP-адреси – 192.168.1.20 та подібні. Реальні застосунки майже ніколи так не роблять. Сервери мають назви example.com або api.example.com, і застосунок шукає ім’я під час виконання, щоб знайти IP-адресу для надсилання пакетів. Цей пошук і є Системою доменних імен, або DNS.

9.15.1. Що повертає розв’язання імені

Ім’я — це просто мітка. example.com сам по собі не містить жодної IP-інформації – його потрібно шукати, так само як номер телефону шукають у телефонній книзі. Інфраструктура DNS є розподіленою телефонною книгою інтернету, і результатом пошуку є одна або декілька IP-адрес, до яких може підключитися камера.

example.com  ->  93.184.216.34

Одне ім’я часто відповідає декільком адресам (для балансування навантаження, географічної надлишковості, версій IPv4 та IPv6 одного й того ж сервісу). Будь-яка з них підходить; застосунок вибирає одну і пробує її, переходячи до наступної, якщо вона не спрацьовує.

9.15.2. Як відбувається пошук

Коли камера запитує example.com:

  1. Камера надсилає невелику UDP-датаграму (так, UDP – дивіться UDP – відправ пакет і сподівайся на краще) на свій налаштований DNS-сервер. Адреса DNS-сервера надійшла з того ж DHCP-обміну, який надав камері її власний IP.

  2. DNS-сервер може вже мати відповідь у кеші (нещодавно його вже запитували). Якщо так – він відповідає негайно.

  3. Якщо ні, DNS-сервер обходить глобальну ієрархію DNS: запитує кореневі сервери про .com, запитує ці сервери про example.com, запитує ті сервери про ім’я. Весь обхід дерева прихований від камери; камера бачить один запит і одну відповідь.

  4. DNS-сервер кешує результат для наступного разу та надсилає відповідь назад до камери у вигляді ще однієї UDP-датаграми.

Весь обмін зазвичай займає кілька мілісекунд при теплому кеші і до ста або більше при холодному.

9.15.3. Інтерфейс Python

Функція getaddrinfo() виконує пошук і повертає адресу, готову до передачі в конструктор сокету:

import socket

addr = socket.getaddrinfo("example.com", 80)[0][-1]
print(addr)
# ('93.184.216.34', 80)

Сигнатура: getaddrinfo(host, port). Значення, що повертається, – це список кортежів із 5 елементів (по одному на кожну розв’язану адресу); [0] вибирає перший, а [-1] – останнє поле, яким є кортеж (ip, port), що можна безпосередньо передати у сокет:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(socket.getaddrinfo("example.com", 80)[0][-1])
    s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
    ...

Більшість коду камери, що зв’язується з віддаленим сервером, починається з цього одного рядка. Допоміжні засоби asyncio (asyncio.open_connection()) виконують пошук внутрішньо, якщо передається ім’я замість числового IP, тому асинхронний код зазвичай не викликає getaddrinfo() безпосередньо.

9.15.4. Що може піти не так

  • DNS-сервер недоступний. Якщо Wi-Fi щойно піднявся і лінія нестабільна, перший виклик getaddrinfo() може вийти за часом. Виникає OSError; повторіть спробу після стабілізації лінії.

  • Ім’я не існує. Друкарська помилка або застаріле ім’я викликає OSError після того, як DNS-сервер повертає «немає такого імені». Код помилки відрізняє це від «DNS-сервер недосяжний», але для більшості застосунків для камери політика повторних спроб/відмов однакова.

  • Повернута адреса не працює. DNS може повернути адресу, яка більше не обслуговує цей сервіс. Вирішення – перейти до наступного запису у списку результатів getaddrinfo() або виконати пошук імені пізніше (кеш DNS до того часу, швидше за все, оновиться).

  • Захоплені портали. Деякі мережі Wi-Fi перехоплюють DNS і повертають IP-адресу сторінки захопленого порталу для всього. Камера, здається, підключається, але отримані дані не відповідатимуть тому, що надіслав би реальний сервіс. Не поширено в розгорнутих середовищах, але трапляється в конференційних мережах Wi-Fi та подібних.

9.15.5. Власне ім’я камери

Попередні сторінки виконували пошук імен інших пристроїв. У камери також є власне ім’я, яке вона оголошує в локальній мережі, коли запитує адресу. За замовчуванням це загальний ідентифікатор, що змінюється залежно від плати; network.hostname() замінює його на щось, що решта мережі впізнає:

import network
network.hostname("kitchen-cam")
# ... then bring the link up as usual ...

Встановіть ім’я хоста перед піднятям інтерфейсу, щоб ім’я передавалось як частина початкового запиту адреси камери, а не після.

Після приєднання камери до мережі відбуваються дві речі. По-перше, більшість домашніх маршрутизаторів реєструють імена хостів, яким вони надають адреси, у власному локальному довіднику, тому інші пристрої можуть звертатися до камери як kitchen-cam – не знаючи числової адреси, яку призначив маршрутизатор. (Корпоративні мережі можуть підтримувати або не підтримувати це; поведінка залежить від маршрутизатора.) По-друге, сама камера запускає резолвер mDNS з коробки, тому те саме ім’я також доступне як kitchen-cam.local у будь-якій мережі, клієнти якої розуміють mDNS – а це роблять більшість сучасних настільних операційних систем.

Примітка

Передавайте просте ім’я хоста у network.hostname() – просто "kitchen-cam", без суфіксу .local. Форма .local – це те, що mDNS додає під час пошуку; вбудовування його в ім’я хоста змушує камеру оголошувати kitchen-cam.local як просте ім’я хоста, що не є тим, чого очікує жодна зі сторін пошуку.

9.15.6. Коли імен недостатньо

Кілька ситуацій, в яких DNS не допомагає:

  • Виявлення в локальній мережі. Стандартний DNS відповідає на запитання про імена, зареєстровані в глобальному каталозі; він нічого не знає про пристрої в локальному сегменті. Multicast DNS (mDNS) – це система, яка заповнює цю прогалину. Кожен учасник приєднується до спеціальної групи multicast в локальній мережі та прослуховує запити; коли пристрій запитує ім’я, що закінчується на .local, пристрій-власник цього імені відповідає безпосередньо. Без центрального сервера, без налаштування DNS. Bonjour на пристроях Apple, Avahi на Linux та Windows 10+ — всі говорять на одному протоколі – саме тому ім’я kitchen-cam.local, налаштоване в попередньому розділі, розв’язується в домашній мережі без жодних додаткових налаштувань.

    Сторона камери в цьому обміні є респондером, і він вже запущений. Чого у камери немає – це резолвера – іншої половини, яка дозволила б скрипту запитати мережу «де знаходиться printer.local?» і отримати відповідь. Вбудований код mDNS є лише респондером (вбудовані пристрої зазвичай є знайденою річчю, а не тією, що шукає). Коли виявлення має відбуватися в зворотному напрямку, UDP broadcast (дивіться UDP – відправ пакет і сподівайся на краще) є простішою відповіддю для випадку локального сегмента, або чистий Python-модуль, такий як cbrand/micropython-mdns, додає повноцінний резолвер.

  • Імена IPv6. getaddrinfo() повертає результати як IPv4, так і IPv6, якщо обидва доступні. Вибирайте сімейство, яке може використовувати сокет застосунку.

Для більшості коду на стороні камери getaddrinfo – це один рядок на початку будь-якої функції, що відкриває мережеве з’єднання. Приклади в інших місцях цього розділу, що працювали з "192.168.1.20", працюватимуть так само з публічним іменем, як-от "api.example.com" – просто спочатку виконайте розв’язання.