vyvchy
    Теми розділу

    03 · Веб і мережі для AQA

    DNS, IP, порти та мережа

    Зміст

    Коли автотест відкриває https://app.example.com/login, за одним цим рядком ховається злагоджена робота кількох протоколів: імʼя треба перетворити на адресу, до адреси достукатися, встановити зʼєднання, домовитися про правила обміну. Якщо будь-яка ланка спрацьовує повільно або нестабільно, тест «мигає» (flaky) без жодної помилки у власне продуктовому коді. Тому AQA-інженеру треба розуміти не лише HTTP-запити, а й шар під ними: адреси, імена, порти, транспорт. Цей розділ будує саме таку картину — від адресації до повного відкриття сторінки.

    IP-адреса: як влаштована адресація в мережі

    IP-адреса (IP address, від Internet Protocol) — це числовий ідентифікатор вузла в мережі. Її роль проста: за IP пакети даних знаходять дорогу до потрібного компʼютера, так само як лист знаходить будинок за поштовою адресою. Домени, порти й протоколи вищого рівня — це надбудова; на мережевому рівні маршрутизація йде саме за IP.

    Сьогодні співіснують дві версії протоколу.

    IPv4 використовує 32 біти, які записують як чотири десяткові числа (октети) від 0 до 255, розділені крапками:

    192.168.0.14
    93.184.216.34

    32 біти дають приблизно 4,3 мільярда унікальних адрес. У 1980-х це здавалося величезним числом, але для сучасного інтернету адрес не вистачає — звідси й механізми продовження життя IPv4 на кшталт NAT (про нього нижче), і поява наступної версії.

    IPv6 використовує 128 біт і записується як вісім груп по чотири шістнадцяткові цифри, розділені двокрапками:

    2001:0db8:85a3:0000:0000:8a2e:0370:7334

    Щоб не тягнути нулі, IPv6 дозволяє скорочення: провідні нулі в групі опускають, а один ланцюжок груп із самих нулів згортають до ::. Той самий приклад скорочено:

    2001:db8:85a3::8a2e:370:7334

    Адресний простір IPv6 настільки великий, що дефіциту адрес там немає в осяжній перспективі. Багато вузлів сьогодні двостекові (dual-stack) — мають і IPv4-, і IPv6-адресу; клієнт пробує їх майже одночасно, з невеликою перевагою для IPv6 (алгоритм Happy Eyeballs), і лишає те зʼєднання, що встановилося першим. За специфікацією спроби навіть не стартують строго одночасно: клієнт пробує спочатку IPv6, а IPv4-спробу запускає з невеликою затримкою, якщо перша ще не зʼєдналася. Для тестів це ще одне джерело плаваючих таймінгів: на кривій IPv6-мережі перше зʼєднання може «думати» довше, доки клієнт відкотиться на IPv4.

    Кілька діапазонів варто знати напамʼять, бо AQA стикається з ними щодня.

    Адреса / діапазонЩо цеДе зустрічається
    127.0.0.1 (IPv4), ::1 (IPv6)Loopback — «сам до себе»Локальний застосунок під тестом
    localhostІмʼя, що резолвиться у loopbackbaseURL у конфігах тестів
    10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16Приватні мережі (private ranges)Домашні/офісні/CI-мережі, docker
    0.0.0.0«Усі інтерфейси» при прослуховуванніСервер, доступний ззовні контейнера

    Запис 192.168.0.0/16 — це CIDR-нотація: число після скісної риски (тут 16) означає, скільки старших біт адреси фіксовані як префікс мережі. Решта біт відводиться під адреси вузлів усередині цієї мережі — тобто /16 лишає 16 біт, приблизно 65 тисяч адрес.

    Ім’я localhost за угодою резолвиться в loopback (127.0.0.1 / ::1) і зазвичай прописане в системному файлі hosts, тож працює навіть без DNS. Це не абсолютна гарантія — теоретично hosts можна переписати, — але на практиці на цю відповідність спираються сміливо.

    Приватні адреси не маршрутизуються в публічному інтернеті. Щоб машина з приватної мережі вийшла назовні, роутер підміняє її адресу на свою публічну — це NAT (Network Address Translation). Практичний наслідок для тестування: сервіс, піднятий на 127.0.0.1:3000, видно лише всередині тієї ж машини чи контейнера, а сервіс на 0.0.0.0:3000 слухає всі інтерфейси й доступний ззовні. Плутанина між цими двома адресами — типова причина «у мене працює, а в CI ні».

    DNS: телефонний довідник інтернету

    Люди мислять іменами (google.com), а мережа маршрутизує за числами. DNS (Domain Name System) — це розподілена ієрархічна система, яка перекладає доменні імена в IP-адреси. Класична аналогія — телефонний довідник: ти знаєш імʼя, а система повертає номер.

    DNS вирішує не лише зручність запамʼятовування. Він розвʼязує звʼязок «імʼя ↔︎ адреса», і цей рівень непрямості дає гнучкість:

    • один сайт може стояти за багатьма IP — DNS повертає різні адреси й так балансує навантаження;
    • IP-адресу сервера можна змінити, не чіпаючи імені — користувачі й тести й далі йдуть на app.example.com;
    • за одним IP можуть жити багато доменів (віртуальний хостинг), і сервер розрізняє їх за іменем.

    DNS зберігає не тільки адреси. Дані живуть у вигляді записів (records) різних типів.

    Тип записуЩо зберігає
    AIPv4-адресу для імені
    AAAAIPv6-адресу для імені
    CNAMEПсевдонім (alias): «це імʼя = те імʼя»
    NSЯкі сервери авторитетні для зони
    MXПоштові сервери домену
    TXTДовільний текст (верифікація домену, SPF тощо)

    Як DNS резолвить домен: покроково

    Припустимо, тест уперше відкриває app.example.com, і жодного кешу ще немає. Спрощено відбувається таке.

    1. Застосунок питає операційну систему. Бібліотека всередині браузера чи Node.js викликає системний резолвер (stub resolver) — «дай IP для app.example.com».
    2. Запит іде до рекурсивного резолвера (recursive resolver). Це сервер, який бере на себе всю чорнову роботу пошуку. Зазвичай це резолвер провайдера або публічний на кшталт 8.8.8.8 (Google) чи 1.1.1.1 (Cloudflare).
    3. Рекурсивний резолвер питає кореневі сервери (root servers). Ті не знають адреси app.example.com, але кажуть: «за зону .com відповідають ось ці TLD-сервери».
    4. Резолвер питає TLD-сервери зони .com. Ті теж не знають кінцевої адреси, але повертають: «за домен example.com відповідають ось ці авторитетні сервери (authoritative nameservers)».
    5. Резолвер питає авторитетний сервер example.com. Оцей уже знає точну відповідь і повертає A-запис — реальний IP для app.example.com.
    6. Відповідь повертається до застосунку, і браузер нарешті відкриває зʼєднання за отриманою адресою.
    Авторитетний example.comTLD-сервери .comКореневі сервериРекурсивний резолверЗастосунокАвторитетний example.comTLD-сервери .comКореневі сервериРекурсивний резолверЗастосунокIP для app.example.com?Хто веде зону .com?Питай TLD-сервери .comХто веде example.com?Питай авторитетні сервериIP для app.example.com?A-запис 93.184.216.3493.184.216.34Авторитетний example.comTLD-сервери .comКореневі сервериРекурсивний резолверЗастосунокАвторитетний example.comTLD-сервери .comКореневі сервериРекурсивний резолверЗастосунокIP для app.example.com?Хто веде зону .com?Питай TLD-сервери .comХто веде example.com?Питай авторитетні сервериIP для app.example.com?A-запис 93.184.216.3493.184.216.34

    Ключова різниця в термінах: рекурсивний резолвер зобовʼязаний повернути кінцеву відповідь (він сам обходить усіх), а кожен кореневий/TLD/авторитетний сервер відповідає лише за свій шматок ієрархії і часто дає не адресу, а «referral» — вказівку, кого питати далі.

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

    TTL (Time To Live). Кожен DNS-запис несе TTL — число секунд, скільки відповідь дозволено тримати в кеші. Побачити його можна прямо у відповіді:

    $ dig +noall +answer app.example.com
    app.example.com.   300   IN   A   93.184.216.34

    Тут 300 — це TTL: запис живе 300 секунд (IN — клас Internet, A — тип запису). Поки TTL не вичерпано, використовується кешоване значення; після — запис перезапитують. TTL — це компроміс: великий TTL зменшує навантаження й прискорює відповіді, малий дає змогу швидко перемкнути трафік на нову адресу.

    Чому це важить для автотестів:

    • Якщо тестове середовище нещодавно змінило IP, а десь у ланцюжку живе старий запис із довгим TTL, тести можуть стукати за застарілою адресою — і «падати» без видимої причини в застосунку.
    • Файл hosts на CI-раннері або локально дозволяє жорстко привʼязати домен до потрібного IP в обхід DNS. Це зручно, щоб направити тести на конкретний стенд, але легко забути й отримати загадкову поведінку.
    • Час на резолвінг першого запиту додається до часу відкриття сторінки. У flaky-тестах, чутливих до таймінгів, «холодний» DNS іноді і є причиною випадкового перевищення таймауту.

    Порти: багато сервісів на одній адресі

    IP-адреса веде до машини, але на машині одночасно працюють десятки програм. Порт (port) — це 16-бітне число (від 0 до 65535), яке визначає, до якого саме сервісу на цій машині йде зʼєднання. Аналогія: IP — це адреса будинку, порт — номер квартири.

    Порти прийнято ділити на діапазони (класифікація IANA):

    ДіапазонНазваПризначення
    0–1023Well-known (системні)Стандартні сервіси (HTTP, HTTPS, SSH…)
    1024–49151RegisteredПорти застосунків, зареєстровані за продуктами
    49152–65535Dynamic / ephemeralТимчасові порти клієнтських зʼєднань

    Ефемерні порти варто розуміти окремо: коли клієнт відкриває зʼєднання, ОС виділяє йому тимчасовий (ephemeral) порт як зворотну адресу. Кожне TCP-зʼєднання унікальне четвіркою «джерело-IP : джерело-порт → призначення-IP : призначення-порт», тому браузер може тримати багато паралельних зʼєднань до одного сервера — у кожного свій локальний порт. Важлива тонкість: фактичний ефемерний діапазон залежить від ОС і часто відрізняється від рекомендованого IANA 49152–65535. У Linux за замовчуванням це 32768–60999 (керується параметром net.ipv4.ip_local_port_range), тож на цільовій системі його варто перевіряти, а не покладатися на теоретичні межі.

    Типові порти, які AQA бачить постійно:

    ПортПротокол / сервіс
    80HTTP
    443HTTPS
    22SSH
    53DNS
    25SMTP (пошта)
    3306MySQL
    5432PostgreSQL
    6379Redis
    27017MongoDB
    8080Часто альтернативний HTTP (dev-сервери, проксі)

    Перевірити, хто зайняв порт локально чи чи відкритий він на віддаленому хості, можна прямо з термінала:

    # Який процес слухає порт 3000 локально
    $ lsof -i :3000
    # Чи відкритий порт на віддаленому хості
    $ nc -zv app.example.com 443

    Синтаксис host:8080

    Адресу сервісу записують як host:порт, де двокрапка відділяє порт:

    localhost:8080
    127.0.0.1:3000
    db.internal:5432

    У складі URL це виглядає так:

    http://localhost:8080/api/health
    https://app.example.com/login

    Важлива деталь: для стандартних схем порт можна не писати, бо він мається на увазі за замовчуванням — 80 для http і 443 для https. Тобто https://example.com і https://example.com:443 — те саме. А ось dev-сервер на 8080 треба вказувати явно, інакше браузер піде на 80 і зʼєднання не відбудеться.

    Порти — часте джерело нестабільності в CI. Якщо тест піднімає локальний сервер на фіксованому порту, а той уже зайнятий іншим процесом (наприклад, попереднім прогоном, що не завершився), запуск падає з address already in use. Тому багато фреймворків або підбирають вільний порт динамічно, або вимагають чистого стану між прогонами — це прямий вияв принципу ізоляції стану між тестами.

    Домен vs IP: імʼя і адреса — різні речі

    Домен (app.example.com) — це людиночитане імʼя; IP — числова адреса, куди реально йде трафік. Звʼязок між ними не «один до одного»:

    • Один домен → багато IP. DNS може повертати кілька адрес, і трафік розподіляється між серверами (балансування).
    • Багато доменів → один IP. На одному сервері живуть десятки сайтів; сервер розрізняє їх за іменем, яке клієнт повідомляє в запиті (для HTTP — заголовок Host, для HTTPS — розширення SNI під час встановлення шифрованого зʼєднання).
    • Той самий домен → різні IP у різних місцях. Геораспределені мережі (CDN) віддають різним користувачам різні адреси залежно від їхнього розташування.

    Практичний висновок для тестів: якщо звертатися напряму за IP замість домену, можна не потрапити на потрібний віртуальний хост або зламати перевірку сертифіката (сертифікат виданий на імʼя, а не на IP). І навпаки — підмінивши в hosts домен на IP конкретного стенда, можна детерміновано спрямувати всі тести саме туди, обійшовши балансування й CDN.

    TCP: надійний транспорт під HTTP

    Знайти адресу — замало; треба ще надійно доставити дані. Цим займається транспортний рівень, і головний його представник для вебу — TCP (Transmission Control Protocol).

    TCP дає застосунку надійний, упорядкований потік байтів. За цими словами стоять конкретні гарантії:

    • Надійність (reliability): якщо пакет загубився, TCP помітить це (через відсутність підтвердження) і надішле його знову.
    • Порядок (ordering): пакети можуть прийти врозбій, але TCP збере їх у правильній послідовності, перш ніж віддати застосунку.
    • Контроль потоку й перевантаження (flow / congestion control): TCP підлаштовує швидкість, щоб не завалити ні приймача, ні мережу.

    Саме тому HTTP/1.1 і HTTP/2 працюють поверх TCP: вебу потрібна впевненість, що HTML, JSON і зображення прийдуть цілими й у правильному порядку. HTTP/3 — виняток: він побудований на QUIC поверх UDP, але надійність і впорядкування там реалізовані власними засобами протоколу QUIC.

    TCP vs UDP

    UDP (User Datagram Protocol) — другий важливий транспорт. Він робить мінімум: шле пакети (датаграми) без встановлення зʼєднання й без гарантій.

    ВластивістьTCPUDP
    ЗʼєднанняВстановлюється (handshake)Немає (connectionless)
    Надійність доставкиТак, з перевідправкоюНі, пакет може зникнути
    Порядок пакетівГарантованийНе гарантований
    Накладні витратиВищіНижчі
    Затримка на стартіБільша (рукостискання)Мінімальна
    Типове застосуванняHTTP/1.1 і /2, пошта, передача файлівDNS-запити, відео/аудіо в реальному часі, ігри, QUIC/HTTP3

    Логіка вибору проста: коли важлива цілісність — беруть TCP; коли важлива швидкість і невеликі втрати терпимі (жива відеотрансляція, де перепитувати старий кадр немає сенсу) — беруть UDP. DNS, до речі, зазвичай ходить по UDP на порт 53, а на TCP переходить для великих відповідей (коли UDP-відповідь не вміщується й приходить із прапорцем «truncated») і для передачі зон між серверами.

    TCP-рукостискання (three-way handshake)

    Перш ніж передати хоч байт корисних даних, TCP встановлює зʼєднання за три кроки — це «трикрокове рукостискання» (three-way handshake):

    Клієнт  ── SYN ─────────────▶  Сервер     (хочу зʼєднатися)
    Клієнт  ◀───── SYN-ACK ──────  Сервер     (згоден, і я теж хочу)
    Клієнт  ── ACK ─────────────▶  Сервер     (підтверджую, працюємо)
    • SYN (synchronize) — клієнт просить зʼєднання й повідомляє свій початковий номер послідовності.
    • SYN-ACK — сервер підтверджує запит клієнта і надсилає свій номер послідовності.
    • ACK (acknowledge) — клієнт підтверджує, після чого канал готовий до передачі даних.

    Для HTTPS поверх цього ще накладається рукостискання TLS — обмін ключами для шифрування. Тобто до першого корисного байта встигає пройти кілька обмінів «туди-назад» (round trips).

    Чому це критично для AQA: кожне рукостискання — це затримка. На повільному або далекому каналі відкриття першого зʼєднання відчутно довше за наступні запити тим самим зʼєднанням, яке TCP і HTTP намагаються перевикористати (keep-alive, а в HTTP/2 — ще й мультиплексування кількох запитів в одному зʼєднанні). Звідси часта картина: перший крок тесту повільніший за решту, а перший прогін «холодний». Якщо таймаути виставлені без запасу на встановлення зʼєднання, це прямий шлях до мигання тестів.

    Затримка vs пропускна здатність (latency vs bandwidth)

    Дві характеристики каналу, які легко сплутати, а вони про різне.

    • Затримка (latency) — час, за який дані доходять від точки до точки. Часто міряють як RTT (round-trip time) — час на подорож «туди й назад». Одиниця — мілісекунди.
    • Пропускна здатність (bandwidth) — скільки даних канал здатен пропустити за одиницю часу. Одиниця — мегабіти за секунду (Mbps) і подібні.

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

    Для тестів це розрізнення прикладне:

    • Дії з багатьма дрібними запитами (типовий вебзастосунок з купою API-викликів) страждають насамперед від затримки: кожен запит платить свій RTT, і вони складаються.
    • Завантаження великих файлів чи медіа впирається у пропускну здатність.
    • Емуляція мережі в браузері (throttling) дозволяє штучно підняти затримку чи обрізати смугу й перевірити, чи не розсипаються тести й UI на повільному звʼязку. Це корисний спосіб відтворити flaky-поведінку, яка на швидкій машині розробника ніколи не виникає.

    Proxy: посередник у мережі

    Проксі (proxy) — це сервер-посередник, через який іде трафік замість прямого шляху «клієнт → сервер». Клієнт звертається до проксі, а той уже говорить із цільовим сервером від свого імені і повертає відповідь. Розрізняють два напрямки:

    • Прямий проксі (forward proxy) стоїть з боку клієнта. Через нього виходять у мережу співробітники компанії; він може кешувати, фільтрувати, логувати, обмежувати доступ.
    • Зворотний проксі (reverse proxy) стоїть перед серверами з боку сервісу. Він приймає зовнішні запити й розподіляє їх між внутрішніми серверами, термінує TLS, кешує статику. Приклади — nginx, HAProxy.

    Оскільки проксі бачить увесь трафік, що крізь нього йде, він може його не лише передавати, а й інспектувати та змінювати. Саме на цьому будується найважливіший для AQA сценарій: перехоплювальний проксі (intercepting proxy) на кшталт mitmproxy, Charles або Fiddler дозволяє побачити реальні запити й відповіді, підмінити їх, зімітувати помилку сервера, уповільнити відповідь.

    Той самий принцип вбудований у фреймворки автотестів — тільки перехоплення відбувається всередині браузера, без окремого інструмента. У Playwright це page.route:

    // Перехопити запит до API і повернути фейкову відповідь
    await page.route('**/api/user', route =>
      route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ id: 1, name: 'Test User' }),
      })
    );
    
    // Зімітувати падіння бекенда
    await page.route('**/api/orders', route => route.fulfill({ status: 500 }));

    Практичне застосування в тестуванні: замокати відповідь бекенда, щоб тест UI не залежав від реальних даних; відтворити рідкісний стан (500-та помилка, порожня видача, таймаут через route.abort()); ізолювати тест від нестабільного зовнішнього сервісу. Усе це — способи прибрати зовнішні джерела флаку й зробити перевірку детермінованою.

    VPN: що він робить із трафіком

    VPN (Virtual Private Network) створює зашифрований «тунель» між твоєю машиною і VPN-сервером. Трафік іде так: застосунок → зашифрований тунель → VPN-сервер → цільовий сайт, і назад тим самим шляхом. Що це дає на практиці:

    • Шифрування на ділянці до VPN-сервера. Локальна мережа й провайдер бачать лише факт зʼєднання з VPN, а не куди й що ти шлеш. Це головна причина користуватися VPN у ненадійних мережах (публічний Wi-Fi).
    • Підміна видимої адреси. Для цільового сайту джерелом запиту виглядає VPN-сервер, а не ти. Звідси зміна видимого географічного розташування — корисно, щоб протестувати геозалежну поведінку (мова, ціни, доступність контенту, редиректи).
    • Доступ до закритих мереж. Корпоративний VPN пускає в приватну мережу компанії, де живуть внутрішні тестові стенди, недоступні з публічного інтернету.

    Чого VPN не робить: він не робить тебе анонімним і не шифрує все на всьому шляху — після VPN-сервера трафік іде до сайту звичайним чином (захищений уже своїм TLS, якщо це HTTPS). Сам VPN-сервер бачить твій трафік у міру того, що не зашифроване протоколом вищого рівня.

    Для AQA VPN зʼявляється у двох ролях. По-перше, доступ до внутрішніх стендів — без нього тести просто не достукаються до цільового середовища, і це варто закладати в налаштування CI. По-друге, VPN сам може стати джерелом флаку: він додає затримку (трафік робить гак через VPN-сервер) і власну точку відмови. Якщо тести подеколи падають по таймауту саме коли ввімкнено VPN — це серйозний кандидат у підозрювані, а не «магія мережі».

    Як усе поєднується в одному відкритті сторінки

    Щоб зібрати картину, простежимо https://app.example.com/login від початку до кінця:

    1. Браузер бере імʼя app.example.com і через DNS отримує IP (з кешу або повним рекурсивним обходом; TTL визначає свіжість).
    2. За IP і портом 443 (бо схема https) відкривається TCP-зʼєднання трикроковим рукостисканням SYN / SYN-ACK / ACK.
    3. Поверх TCP проходить TLS-рукостискання; клієнт через SNI повідомляє потрібне імʼя хоста (важливо для віртуального хостингу).
    4. Лише тепер летить власне HTTP-запит на /login, і повертається відповідь.
    5. Якщо між клієнтом і сервером стоїть проксі чи VPN — трафік проходить крізь них, додаючи затримку й, можливо, змінюючи чи інспектуючи вміст.
    СерверDNSБраузерСерверDNSБраузерРезолв app.example.comIP-адресаTCP-рукостискання на порт 443TLS-рукостискання (SNI: app.example.com)GET /loginHTTP-відповідьСерверDNSБраузерСерверDNSБраузерРезолв app.example.comIP-адресаTCP-рукостискання на порт 443TLS-рукостискання (SNI: app.example.com)GET /loginHTTP-відповідь

    Кожна ланка — потенційне джерело затримки. Розкласти час по цих етапах допомагає curl:

    $ curl -w "dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer}\n" \
           -o /dev/null -s https://app.example.com/login
    dns=0.031 connect=0.052 tls=0.108 ttfb=0.216

    Тут видно кожен шар окремо: dns — резолвінг імені, connect — TCP-рукостискання (наростаючим підсумком від старту), tls — завершення TLS, ttfb — прихід першого байта відповіді. Всі ці значення відлічуються від старту запиту, тому кожне наступне включає попереднє. Розуміючи, де саме зупиняється або гальмує запит (DNS не відрезолвився? порт закритий? рукостискання довге? VPN додав RTT?), AQA-інженер діагностує flaky-тест не навмання, а прицільно — і будує очікування, ізоляцію стану й перехоплення мережі, спираючись на те, як цей шар справді працює.