Компьютерные сети: от IPC к C/Winsock
HTTPS: где заканчиваются DNS, TCP, TLS и HTTP
От URL до HTTP response: адрес, transport, identity и application semantics как четыре отдельные границы доказательства.
HTTPS: где заканчиваются DNS, TCP, TLS и HTTP
У процесса A уже есть всё, что мы построили в предыдущих главах: UTF-8 bytes,
route, TCP socket и список адресов от DNS. Теперь он должен отправить слово
Привет по URL https://server.example/hello. Снаружи это один вызов клиента.
Внутри — четыре независимых контракта, и каждый может успешно завершиться после
того, как предыдущий уже доказан.
Задача
Отправим HTTP/1.1 request, где body содержит шесть Unicode-символов и ровно 12 UTF-8 bytes:
POST /hello HTTP/1.1
Host: server.example
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Connection: close
Привет
Content-Length: 12 считает bytes representation, а не символы. Но до этого
header дойдут только после трёх более ранних шагов: resolver должен вернуть
address candidate, TCP — установить transport connection, TLS — аутентифицировать
service identity и согласовать защищённый channel.
Диаграмма важна не как длинная стрелка, а как карта остановок. Если DNS вернул
192.0.2.20, это ещё не означает, что порт доступен. Если завершился TCP
handshake, это не означает, что certificate подходит имени. Если TLS session
защищена, это не означает, что route /hello существует. И наоборот: полученный
HTTP status уже требует, чтобы клиент прошёл предыдущие границы до некоторого
HTTP-speaking component.
Четыре контракта одного URL
Первый контракт — name resolution. server.example превращается в один или
несколько IPv4/IPv6 candidates. Приложение сохраняет исходное имя: оно ещё
понадобится для TLS identity и HTTP Host. Подстановка numeric IP «чтобы
починить DNS» меняет эксперимент и часто ломает certificate verification.
Второй контракт — TCP. connect() работает с конкретным address:port и
создаёт ordered byte stream. SYN/SYN-ACK/ACK доказывают достижимость TCP endpoint,
но не знают ни certificate, ни /hello, ни HTTP status. Открытый port 443 может
принадлежать proxy, load balancer или сервису с неправильной конфигурацией.
Третий контракт — TLS. ClientHello сообщает поддерживаемые параметры; SNI
позволяет назвать intended server, а ALPN — согласовать application protocol.
Затем клиент проверяет certificate chain, срок действия и соответствие service
identity исходному имени. Проверка имени строится от URL, а не от того, какое имя
случайно показал certificate. Ошибка identity должна остановить automated
client; флаг «не проверять certificate» удаляет гарантию, ради которой выбран
https.
Четвёртый контракт — HTTP. Только внутри защищённого channel передаются
method, target, fields и content. Status описывает response, но не всегда
называет физический component, который его сформировал: 503 мог вернуть edge
proxy до origin. Поэтому вместе со status нужны response fields, небольшой
безопасный body excerpt и correlation/request ID.
На схеме body Привет остаётся теми же 12 application bytes, но после TLS
handshake идёт как encrypted TLS application data. Packet capture без session
keys не покажет plaintext request. Он всё равно полезен: фиксирует addresses,
ports, TCP flags, retransmissions и часть handshake metadata. Это другой
observation boundary, не «полный лог разговора».
C/Winsock checkpoint
Raw Winsock нужен, чтобы увидеть transport boundary. Код перебирает результаты
getaddrinfo, создаёт новый socket для каждого candidate и сохраняет
WSAGetLastError() сразу после неудачного connect:
SOCKET connected = INVALID_SOCKET;
int last_connect_error = 0;
for (const struct addrinfo *it = results; it != NULL; it = it->ai_next) {
SOCKET candidate = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
if (candidate == INVALID_SOCKET) {
last_connect_error = WSAGetLastError();
continue;
}
if (connect(candidate, it->ai_addr, (int)it->ai_addrlen) == 0) {
connected = candidate;
break;
}
last_connect_error = WSAGetLastError();
closesocket(candidate);
}
Успешный connected — checkpoint TCP, не HTTPS. Для TLS нельзя писать
«учебное шифрование». На Windows либо строят Schannel state machine поверх
socket, либо используют поддерживаемый HTTP client, например WinHTTP. Второй
вариант сохраняет важные границы без самодельной криптографии:
HINTERNET session = WinHttpOpen(
L"NetworkCourse/1.0",
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0
);
HINTERNET connection = WinHttpConnect(session, L"server.example", 443, 0);
HINTERNET request = WinHttpOpenRequest(
connection,
L"POST",
L"/hello",
NULL,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE
);
Это отдельный production-oriented client path; он не «продолжает» raw socket
из предыдущего фрагмента. WinHTTP сам управляет transport/TLS для своих handles.
Каждый HINTERNET закрывается через WinHttpCloseHandle, а error code читается
через GetLastError() немедленно после failed call. Не отключайте validation,
чтобы тест прошёл: исправьте имя, trust store, certificate или часы.
Где появляются наблюдаемые ошибки
Перед запуском сформулируйте prediction по первой недоказанной границе:
| Наблюдение | Что уже доказано | Что ещё не доказано |
|---|---|---|
getaddrinfo вернул A/AAAA | resolver дал candidates | route, port, identity, HTTP |
connect получил WSAECONNREFUSED | выбранный endpoint активно отказал TCP | TLS и HTTP не начинались |
| TCP established, затем name mismatch | transport к peer работает | TLS service identity не подтверждена |
| TLS validation успешна, затем timeout | защищённый channel был создан | HTTP handler завершил request |
HTTP/1.1 503 + request ID | ответил HTTP-speaking component | что именно случилось внутри origin |
TLS alert и certificate validation result не заменяйте словом «SSL error». Запишите target hostname, numeric peer, negotiated TLS version/cipher, ALPN и конкретный validation result, не сохраняя secrets. Для HTTP запишите method, target, status, latency и request ID. Так handoff попадает владельцу первой сломавшейся boundary, а не случайной команде «сетевиков».
Предсказание → эксперимент → evidence
В Lab 07 сначала запишите ожидаемую последовательность для четырёх controlled
cases: valid certificate; trusted certificate для другого hostname; TCP listener
без TLS; корректный TLS endpoint, который возвращает 503.
Для каждого case сохраните:
- timestamp, source host и точный URL;
- DNS candidates и реально выбранный numeric peer;
- result TCP attempt с duration и error code;
- TLS result: version, ALPN и identity validation;
- HTTP status/fields/request ID, только если response действительно получен.
Prediction для name mismatch: TCP handshake завершится, server представит certificate, validation отклонит identity, а HTTP request не должен быть принят как защищённый request. Evidence должно содержать TCP success и TLS failure; один скриншот browser warning хуже, потому что скрывает выбранный address и точную границу.
Prediction для 503: DNS, TCP и TLS могут быть полностью исправны. Evidence —
HTTP status, response fields, request ID и соответствующий edge/origin log. Если
request ID отсутствует в origin, это аргумент в пользу ответа промежуточного
component, но не автоматическое доказательство без его logs.
Частая ошибка: reachable port называют healthy HTTPS
Test-NetConnection server.example -Port 443 отвечает на узкий вопрос: смогла
ли TCP attempt к выбранному address завершиться. Он не передаёт SNI, не проверяет
certificate identity и не вызывает /hello. Используйте его как один checkpoint,
а не как итоговый health check.
Обратная ошибка — назвать certificate mismatch проблемой DNS только потому, что имя участвовало в обоих шагах. DNS мог корректно привести к server, который предъявил certificate для другого identity. Первая подтверждённая поломка тогда на TLS boundary.
Итого и мост к диагностике
- URL связывает имя, secure origin и resource, но DNS, TCP, TLS и HTTP остаются отдельными contracts.
- TCP reachability не проверяет identity; TLS success не проверяет application semantics; HTTP status не всегда называет ответивший component.
Привет— 12 bytes в HTTP body, а TLS record и TCP segment boundaries не обязаны совпасть с границей body или однимsend.- Хорошее evidence фиксирует последнюю успешную и первую неуспешную boundary.
В следующей главе вместо controlled case будет incident «сеть не работает». Мы превратим эти checkpoints в воспроизводимый evidence ladder и научимся останавливать диагностику на фактах до restart, cache flush и firewall changes.