Иногда какие-то вещи нам не удается предугадать. Например, когда появился интернет, он занимал у нас пятое-шестое место по важности.
Билл Гейтс, выступление в Вашингтонском университете, 1998
В этой главе мы кратко рассмотрим обертки и высокоуровневые примитивы, которые можно использовать при разработке сетевых Windows-приложений. Это библиотеки WinINet и WinHTTP, предоставляющие высокоуровневые функции и набор классов MFC. Они не требуют от разработчиков глубоких знаний сокетов, TCP/IP и деталей конкретных сетевых протоколов.
Мы также кратко ознакомимся с универсальной технологией фильтрации сети Windows — WFP, на которой основан межсетевой экран в ОС Windows.
Технология дает возможность фильтровать и изменять сетевые данные, а также перенаправлять их в другое место назначения.
В завершение главы мы расскажем о подсистеме WSL, позволяющей выполнять сетевые приложения Linux в ОС Windows без перекомпиляции.
Мы рассмотрели функции низкого уровня, но часто прикладному разработчику ими пользоваться не обязательно. За него уже все написали системные программисты Microsoft.
WinINet был разработан как клиентская платформа для классических приложений, выполняющих обмен по HTTP. Он может использовать GUI, например, для вывода сообщений об ошибках.
Это не новый набор функций, они поддерживаются еще с поздних версий Windows 98, поэтому об их использовании накоплено достаточно информации.
WinINet API позволяет приложениям получать доступ к протоколам интернета для выполнения следующих действий:
• загрузка HTML-страниц;
• отправка FTP-запросов на загрузку файлов или получение списков каталогов. Типичным запросом является анонимный вход в систему для загрузки файла;
• использование системы меню Gopher для доступа к ресурсам в интернете.
WinINet абстрагирует эти протоколы в высокоуровневый интерфейс.
Функции WinInet можно вызывать напрямую или использовать через классы MFC, описанные далее.
Функция InternetOpen() служит для получения дескриптора, который будет в дальнейшем использован в вызовах функций WinInet:
#include <wininet.h>
HINTERNET InternetOpen(LPCSTR lpszAgent, DWORD dwAccessType, LPCSTR lpszProxy,
LPCSTR lpszProxyBypass, DWORD dwFlags);
Параметры функции InternetOpen():
• lpszAgent — указатель на строку, содержащую значение HTTP-заголовка User-Agent.
• dwAccessType — тип доступа. Может принимать следующие значения:
• INTERNET_OPEN_TYPE_DIRECT — выполнить разрешение имен узлов локально.
• INTERNET_OPEN_TYPE_PRECONFIG — получить прокси-сервер или конфигурацию без прокси из реестра.
• INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY — то же, что и INTERNET_OPEN_TYPE_PRECONFIG, а также запрет использования загрузочного файла Microsoft JScript или файла настройки интернета.
• INTERNET_OPEN_TYPE_PROXY — отправлять запросы прокси-серверу, если не указан список непроксируемых узлов или узел не в этом списке.
• lpszProxy — необязательный указатель на строку, в которой указывается прокси-сервер.
• lpszProxyBypass — необязательный указатель на строку, в которой указываются серверы, запросы к которым проксировать не требуется.
• dwFlags — параметры работы. Может принимать значения:
• INTERNET_FLAG_ASYNC — выполнять только асинхронные запросы к дескрипторам;
• INTERNET_FLAG_FROM_CACHE, INTERNET_FLAG_OFFLINE — не выполнять сетевые запросы. Результат возвращается из кэша; если запрошенного элемента нет в кэше, возникает ошибка.
Функция вернет дескриптор, который может быть использован в дальнейших вызовах.
Например, следующая функция откроет FTP- или HTTP-ресурс по URL:
HINTERNET InternetOpenUrl(HINTERNET hInternet, LPCSTR lpszUrl,
LPCSTR lpszHeaders, DWORD dwHeadersLength,
DWORD dwFlags, DWORD_PTR dwContext);
Параметры функции InternetOpenUrl():
• hInternet — дескриптор текущего сеанса интернета.
• lpszUrl — URL.
• lpszHeaders — указатель на строку, которая содержит HTTP-заголовки для отправки на сервер.
• dwHeadersLength — размер дополнительных заголовков.
• dwFlags — параметры работы. Влияют на переиспользование соединения, HTTP-редиректы, сертификаты, аутентификацию и т.п.
• dwContext — указатель на переменную, значение которой будет передаваться в функции обратного вызова.
Флагов достаточно много, подробное их описание см. в . Мы же разделим флаги по категориям.
Флаги, управляющие кэшированием:
• INTERNET_FLAG_HYPERLINK — принудительно выполнить загрузку, если при определении необходимости загрузки из сети сервер не вернул заголовки Expires и LastModified.
• INTERNET_FLAG_RELOAD — принудительно загрузить запрошенный объект с сервера, а не из кэша.
• INTERNET_FLAG_NEED_FILE — создать временный файл, если загружаемый не может быть кэширован.
• INTERNET_FLAG_RESYNCHRONIZE — перезагрузить HTTP-ресурс, если он был изменен с момента последней загрузки. Все FTP-ресурсы перезагружаются.
• INTERNET_FLAG_NO_CACHE_WRITE — не добавлять возвращенный объект в кэш.
• INTERNET_FLAG_PRAGMA_NOCACHE — принудительно разрешить запрос через исходный сервер, даже если прокси-сервер предоставляет копию.
Флаги перенаправлений и управления соединением:
• INTERNET_FLAG_NO_AUTO_REDIRECT — не обрабатывать перенаправление HttpSendRequest автоматически.
• INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP — отключить перенаправление URL-адресов с HTTPS на HTTP.
• INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS — отключить перенаправление URL-адресов с HTTP на HTTPS.
• INTERNET_FLAG_KEEP_CONNECTION — использовать keep-alive — поддержание активности, если такая возможность доступна. Флаг необходим для проверки подлинности в сетях Microsoft: MSN, NTLM и других типов.
Флаги безопасности:
• INTERNET_FLAG_NO_AUTH — не пытаться выполнить аутентификацию автоматически.
• INTERNET_FLAG_IGNORE_CERT_CN_INVALID — отключить проверку сертификатов Secure Sockets Layer/Private Communications Technology, SSL/PCT, возвращаемых сервером, по имени узла в запросе.
• INTERNET_FLAG_IGNORE_CERT_DATE_INVALID — отключить проверку сроков действия сертификатов SSL/PCT.
• ИНТЕРНЕТ_FLAG_SECURE — использовать безопасные транзакции, то есть протокол SSL/PCT. Имеет смысл только в HTTP-запросах.
Флаги управления cookies:
• INTERNET_FLAG_NO_COOKIES — не добавлять заголовки к запросам файлов cookie автоматически и не добавлять возвращенные файлы cookie в базу данных.
• INTERNET_FLAG_NO_UI — отключить диалоговое окно cookie.
Флаги FTP:
• INTERNET_FLAG_EXISTING_CONNECT — попытаться использовать существующий объект InternetConnect, если он имеет те же атрибуты, которые необходимы для выполнения запроса. Данный флаг полезен только при операциях FTP, чтобы выполнять несколько операций в течение одного сеанса. WinINet API кэширует дескриптор соединения для каждого дескриптора HINTERNET, созданного InternetOpen().
• INTERNET_FLAG_RAW_DATA — вернуть структуру WIN32_FIND_DATA при получении информации о FTP-каталоге. Если флаг не указан или вызов был выполнен через прокси-сервер, InternetOpenUrl() вернет HTML-представление каталога. Ранние версии Windows также возвращают структуру GOPHER_FIND_DATA при получении информации о каталоге Gopher.
• INTERNET_FLAG_PASSIVE — использовать пассивный режим FTP.
Функция вернет дескриптор, который может использоваться, чтобы прочитать данные. Сделать это возможно, используя следующую функцию:
bool InternetReadFile(HINTERNET hFile, LPVOID lpBuffer,
DWORD dwNumberOfBytesToRead,
LPDWORD lpdwNumberOfBytesRead);
Параметры функции InternetReadFile():
• hFile — дескриптор, возвращенный InternetOpenUrl(), FtpOpenFile() или HttpOpenRequest().
• lpBuffer — указатель на буфер, который получит данные.
• dwNumberOfBytesToRead — количество считываемых байтов.
• lpdwNumberOfBytesRead — сюда будет записано количество прочитанных байтов.
Если данные получены, функция вернет истину.
Когда требуется открыть сессию с сервером HTTP или FTP, можно использовать функцию InternetConnect():
HINTERNET InternetConnect(HINTERNET hInternet, LPCSTR lpszServerName,
INTERNET_PORT nServerPort, LPCSTR lpszUserName,
LPCSTR lpszPassword, DWORD dwService,
DWORD dwFlags, DWORD_PTR dwContext);
Параметры функции InternetConnect():
• lpszServerName — указатель на строку, в которой передается имя узла интернет-сервера.
• nServerPort — порт TCP/IP на сервере.
• lpszUserName — указатель на строку, в которой передается имя пользователя для входа.
• lpszPassword — указатель на строку, в которой передается пароль пользователя для входа.
• dwService — тип службы доступа:
• INTERNET_SERVICE_HTTP — для HTTP;
• INTERNET_SERVICE_FTP — для FTP;
• INTERNET_SERVICE_GOPHER — для Gopher.
• dwFlags — флаги. Для FTP определен единственный флаг — INTERNET_FLAG_PASSIVE, открывающий сессию в пассивном режиме.
• dwContext — указатель на переменную, содержащую значение, определенное приложением, которое передается в обратные вызовы.
После завершения работы все дескрипторы необходимо закрыть. Для этого используется следующая функция:
bool InternetCloseHandle(HINTERNET hInternet);
Как взаимодействуют функции Internet API, показано на рис. 20.1, где не только изображена структура функций, но и отмечено, в каком порядке эти функции необходимо вызывать.
Использовать данный API очень просто:
int main()
{
const auto internet_handle = InternetOpen(TEXT("Mozilla/5.12345"),
INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
if (!internet_handle)
{
std::cerr
<< "InternetOpen failed with code " << GetLastError()
<< std::endl;
return EXIT_FAILURE;
}
Рис. 20.1. WinINet-функции для работы с FTP и HTTP
// Открыть ресурс.
auto url_handle = InternetOpenUrl(internet_handle,
TEXT("http://artiomsoft.ru"), 0, 0,
INTERNET_FLAG_RAW_DATA, 0);
if (!url_handle)
{
std::cerr
<< "InternetOpenUrl failed with code " << GetLastError()
<< std::endl;
InternetCloseHandle(internet_handle);
return EXIT_FAILURE;
}
std::array<TCHAR, 1024> buf;
DWORD read_bytes = 0;
// Прочитать данные.
while (InternetReadFile(url_handle, buf.data(),
static_cast<DWORD>(buf.size()), &read_bytes))
{
if (!read_bytes) break;
std::ostream_iterator<TCHAR> out_it(std::cout, "");
std::copy(buf.begin(), buf.begin() + read_bytes, out_it);
}
// Закрыть дескриптор ресурса.
InternetCloseHandle(url_handle);
// Закрыть дескриптор интернета.
InternetCloseHandle(internet_handle);
return EXIT_SUCCESS;
}
Вызов приведет к загрузке индексной страницы HTTP-ресурса:
D:\build\bin> b01-ch20-windows-internet.exe
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Artiomsoft</title>
...
Вместо InternetOpenUrl() можно использовать более специфичные функции, такие как HttpOpenRequest() и FtpOpenFile():
#include <wininet.h>
HINTERNET FtpOpenFile(HINTERNET hConnect, LPCSTR lpszFileName,
DWORD dwAccess, DWORD dwFlags, DWORD_PTR dwContext);
HINTERNET HttpOpenRequest(HINTERNET hConnect, LPCSTR lpszVerb,
LPCSTR lpszObjectName, LPCSTR lpszVersion, LPCSTR lpszReferrer,
LPCSTR *lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext);
Также можно отправить HTTP-запросы, вызвав функцию HttpSendRequest():
bool HttpSendRequest(HINTERNET hRequest, LPCSTR lpszHeaders,
DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength);
Параметры функции HttpSendRequest():
• hRequest — дескриптор, возвращаемый вызовом функции HttpOpenRequest().
• lpszHeaders — указатель на строку с заголовками для отправки HTTP-серверу.
• dwHeadersLength — размер дополнительных заголовков.
• lpOptional — указатель на необязательные данные, отправляемые сразу после заголовков запроса. Этот параметр обычно используется для HTTP-запросов PUT и POST.
• dwOptionalLength — размер необязательных данных в байтах.
В случае успешной отправки запроса функция вернет истину.
WinHTTP — службы HTTP Microsoft Windows, предоставляющие высокоуровневый интерфейс для серверных и неинтерактивных приложений, работающих по HTTP/1.1 и HTTP/2 и, начиная с Windows 8, по протоколу WebSocket. Он используется в системных службах и клиентских приложениях на основе HTTP, но не поддерживает FTP, cookies, кэширование, автоматический ввод учетных данных посредством диалоговых окон, совместимость с браузером и т.п.
Структура и взаимодействие функций WinHTTP API показаны на рис. 20.2.
Рис. 20.2. WinHTTP-функции
WinHTTP API более, чем WinINet API, и построен так, чтобы быть похожим на WinINet. Функции одного API во многом отображаются на функции другого.
Например, аналог функции HttpOpenRequest() в WinHTTP — функция WinHttpOpenRequest(), функциям HttpSendRequest() и HttpSendRequestEx() соответствует функция WinHttpSendRequest() и т.д.
Хотя для некоторых функций эквиваленты отсутствуют, сложности при использовании API возникнуть не должно. А для того, чтобы облегчить перенос с WinINet на WinHTTP, Microsoft предоставляет .
Microsoft Foundation Classes — это набор C++-классов и функций, предоставляемых Microsoft для разработки Windows-приложений. Для наглядности он представлен на рис. 20.3.
Рис. 20.3. Набор C++-классов и функций MFC
MFC является частью пакета разработки приложений Microsoft Visual Studio и в основном используется для создания графических интерфейсов.
Однако в MFC существует много классов, позволяющих работать с различными WinAPI в рамках объектного подхода, в частности, предназначенных для сетевой подсистемы.
Классы поддерживают две модели взаимодействия:
• Синхронную, или блокирующую. Данная модель реализована в классе CSocket.
• Асинхронную. Данная модель реализована в классе CAsyncSocket.
Класс CAsyncSocket является базовым для класса CSocket. Он обеспечивает абстракцию более высокого уровня для работы через объект MFC CArchive. Это необходимо для синхронной работы с экземплярами класса CArchive, который выполняет сериализацию и десериализацию. Данные концепции мы рассмотрим в книге 3.
CSocket наследует многие функции-члены от CAsyncSocket, которые инкапсулируют API-интерфейсы Windows Sockets, а также обеспечивает блокировку и фоновую обработку сообщений Windows.
Обычно при работе создается экземпляр класса CSocket с параметрами по умолчанию для клиента, а для сервера указывается порт в вызове Create(). Затем клиенту необходимо вызвать CAsyncSocket::Connect() для подключения к серверу. Если этот сокет серверный, нужно вызвать CAsyncSocket::Listen(), чтобы ожидать клиент, и CAsyncSocket::Accept(), чтобы принять запрос на подключение.
Экземпляр класса CArchive взаимодействует с экземпляром класса CSocketFile, который вместо обмена данными с файлом выполняет обмен через CSocket, как это показано на рис. 20.4.
Рис. 20.4. Обмен данными через CSocket
Класс CArchive управляет буфером. Когда буфер отправителя заполнен, связанный с ним объект CFile записывает содержимое буфера.
Опустошение буфера архива, подключенного к сокету, эквивалентно отправке сообщения. Когда буфер принимающего абонента заполняется, объект CFile прекращает чтение до тех пор, пока буфер снова не станет доступен.
Класс CSocketFile является наследником от CFile, но не поддерживает методы CFile для определения длины, позиционирования и блокировки.
Неподдерживаемые методы CFile определены в CSocketFile, но они генерируют исключение CNotSupportedException.
Экземпляр класса CSocketFile вызывает методы объекта класса CSocket для отправки или получения данных. Разработчику достаточно создать сокет, файл и архив, чтобы начать обмен данными, добавляя их в «архив» и извлекая из него.
Хотя в классе CSocket реализован синхронный сокет, экземпляры этого класса могут получать асинхронные уведомления. Синхронными являются операции получения и отправки данных. Пока синхронная операция не будет завершена, сокет не будет получать асинхронные уведомления.
Для обработки асинхронных уведомлений можно использовать следующие методы, переопределяемые в классе-наследнике:
• OnReceive() — в буфере есть данные для чтения;
• OnSend() — можно отправлять данные, вызвав Send();
• OnAccept() — прослушивающий сокет может принимать ожидающие запросы на подключение, вызвав Accept();
• OnConnect() — уведомление об успешном или ошибочном завершении попытки подключения;
• OnClose() — сокет закрыт.
Помимо сокетов MFC поддерживает классы-расширения WinINet, которые упрощают получение данных по HTTP и FTP без использования WinSock и предоставляют следующие возможности:
• буферизованный ввод-вывод;
• типобезопасные дескрипторы для данных;
• параметры по умолчанию для функций;
• обработку исключений для распространенных ошибок;
• автоматическое закрытие хэндлов и соединений.
Классы MFC WinINet предоставляют те же функции, что и CStdioFile, для данных, передаваемых через интернет.
Для создания подключения к FTP-серверу в MFC достаточно сделать один вызов CInternetSession::GetFTPConnection().
Еще одно из «сетевых» расширений — интерфейсу для создания, обработки, передачи и хранения почтовых сообщений.
, — это универсальная платформа для контроля сетевых взаимодействий. Она весьма напоминает BPF 2 в Linux и показана на рис. 20.5.
Платформа фильтрации охватывает все основные уровни, начиная с транспортного и заканчивая канальным, и предоставляет разработчикам множество возможностей для фильтрации и изменения сетевых данных перед их получением точкой назначения.
WFP появилась в Windows Vista и с тех пор постоянно развивается и обновляется.
На данный момент существует возможность фильтрации на уровне Ethernet, а также перенаправления как исходящих, так и входящих соединений, в том числе многократное перенаправление соединения с одного прокси-сервера на другой.
Платформа состоит из подсистемы фильтрации, драйверов Callout, оболочек совместимости, базового модуля фильтрации и API-вызовов.
Рис. 20.5. Архитектура
Filtering Engine — базовая многоуровневая инфраструктура фильтрации.
WFP работает с модулями, которые предоставляют набор функций для фильтрации трафика; в терминологии WFP этот набор функций называют callout, или внешними вызовами.
Callout проводят инспекцию пакетов трафика:
• classifyFn — классифицирующая функция. Определяет, запретить или разрешить пакет или соединение, а также может возвращать состояния: продолжить обработку, запросить больше данных, прервать соединение.
• notifyFn — вызывается при создании или удалении фильтра.
• flowDeleteFn — вызывается при удалении потока обработки. В функции обычно выполняется очистка.
Инфраструктура фильтрации решает следующие задачи:
• Фильтрует сетевой трафик на любом уровне по любым полям данных, которые может предоставить оболочка.
• Реализует фильтры путем вызова функций внешних классификаторов в процессе классификации.
• Возвращает состояние «Разрешить» или «Блокировать» вызвавшей его оболочке.
• Обеспечивает арбитраж между различными источниками политик. Например, определяет приоритет, если приложение настроено получать защищенный трафик, но брандмауэр настроен для предотвращения этого.
Фильтры — правила, указывающие, в каких случаях будет вызываться нужный callout. Фильтры могут объединяться по какому-то признаку. Такие объединения называют слоем.
Драйверы добавляют пользовательские функции в механизм фильтрации пространства ядра и позволяют выполнять глубокую проверку и модификацию пакетов и потоков. После того как драйвер добавил функции в механизм фильтрации, приложение или любой драйвер могут добавить фильтры в процесс фильтрации.
Разработчик указывает функцию обратного вызова при регистрации правила фильтрации. Когда фильтр срабатывает, система вызывает переданный обработчик.
С некоторыми устаревшими сетевыми драйверами, которые не поддерживают последнюю версию API вызовов WFP, подсистема взаимодействует, используя редиректоры вызовов функций.
Редиректоры WFP — это фрагменты кода, которые перехватывают и перенаправляют вызовы к API устаревшего драйвера к более новому API WFP. Это позволяет драйверу работать с последней версией платформы, не требуя значительных изменений в коде. Использование этой прослойки помогает обеспечить обратную совместимость и более плавный переход к новым технологиям.
Оболочки — это компоненты режима ядра, которые находятся между сетевым стеком и подсистемой фильтрации. Оболочки раскрывают внутреннюю структуру пакета, как набор свойств на разных уровнях. По этим свойствам пакета может происходить фильтрация.
Ниже приведен список доступных оболочек:
• Application Layer Enforcement — оболочка для применения политик на уровне приложений.
• Модуль транспортного уровня.
• Модуль сетевого уровня.
• Модуль получения ошибок протокола ICMP.
• Работа с потоком.
• Оболочка для вызова RPС (удаленный вызов процедур).
Base Filtering Engine, или BFE, — служба, управляющая работой платформы фильтрации Windows. Она делает следующее:
• Получает фильтры и параметры конфигурации платформы.
• Применяет модель безопасности платформы. Например, локальный администратор может добавлять фильтры, но другие пользователи могут их только просматривать.
• Настраивает модули, например, отправляя политики IPsec в модули ключей IKE/AuthIP или фильтры в модули фильтрации.
• Сообщает текущее состояние системы, в том числе статистику.
Первое, что необходимо сделать для работы с WFP, — открыть сеанс. Для этого вызывается функция FwpmEngineOpen0():
#include <fwpmu.h>
typedef struct FWPM_DISPLAY_DATA0_
{
// Опциональное имя.
wchar_t *name;
// Описание.
wchar_t *description;
} FWPM_DISPLAY_DATA0;
// Состояние, связанное с сеансом клиента.
typedef struct FWPM_SESSION0_
{
// Уникальный идентификатор сеанса.
GUID sessionKey;
// Аннотация сессии в человекочитаемом виде.
FWPM_DISPLAY_DATA0 displayData;
// Флаги. FWPM_SESSION_FLAG_DYNAMIC включает автоудаление объектов сессии.
UINT32 flags;
// Время ожидания транзакции.
UINT32 txnWaitTimeoutInMSec;
DWORD processId;
SID *sid;
wchar_t *username;
// Истина для клиента ядра.
bool kernelMode;
} FWPM_SESSION0;
DWORD FwpmEngineOpen0(const wchar_t *serverName, UINT32 authnService,
SEC_WINNT_AUTH_IDENTITY_W *authIdentity, const FWPM_SESSION0 *session,
HANDLE *engineHandle);
Параметры функции FwpmEngineOpen0():
• engineHandle — дескриптор открытого сеанса в подсистеме фильтрации.
• serverName — строка, содержащая DNS-имя системы, в которой открыт сеанс фильтрации. В случае драйверов callout это значение нулевое.
• authnService — указывает используемую службу проверки подлинности:
• RPC_C_AUTHN_WINNT — NT-аутентификация.
• RPC_C_AUTHN_DEFAULT — аутентификация по умолчанию.
• authIdentity — учетные данные для аутентификации и авторизации для доступа к подсистеме фильтрации. Необязательный параметр.
• session — параметры открываемого сеанса. Необязательный параметр.
Функция возвращает STATUS_SUCCESS в случае успеха или код ошибки. Возвращаемое значение большинства других функций аналогичное.
Изменения производятся в транзакциях, которые начинает функция FwpmTransactionBegin0():
DWORD FwpmTransactionBegin0(HANDLE engineHandle, UINT32 flags);
Параметры функции FwpmTransactionBegin0():
• engineHandle — дескриптор открытого сеанса.
• flags — флаги транзакции:
• 0 — транзакция для чтения и записи;
• FWPM_TXN_READ_ONLY — транзакция только для чтения.
Далее можно, например, создать новый callout:
typedef struct FWPS_CALLOUT0_
{
// Уникальный идентификатор callout.
GUID calloutKey;
UINT32 flags;
// Указатель на классифицирующую функцию
FWPS_CALLOUT_CLASSIFY_FN0 classifyFn;
// Указатель на функцию, которая вызывается при добавлении
// или удалении фильтра.
FWPS_CALLOUT_NOTIFY_FN0 notifyFn;
// Указатель на функцию, которая вызывается при закрытии потока.
FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0 flowDeleteFn;
} FWPM_CALLOUT0;
NTSTATUS FwpsCalloutRegister0(
void *deviceObject, const FWPS_CALLOUT0 *callout, UINT32 *calloutId
);
Параметры функции FwpsCalloutRegister0():
• deviceObject — указатель на объект устройства, который ранее был создан драйвером callout.
• callout — идентификатор callout, флаги и указатели на функции:
• classifyFn — классификации;
• notifyFn — уведомления о добавлении/удалении фильтра;
• flowDeleteFn — уведомления о закрытии обрабатываемого потока.
• calloutId — указатель на переменную, которая получает идентификатор callout в движке фильтра.
Первые две функции в структуре являются обязательными, последняя нужна лишь в случае, если необходимо отслеживать сами пакеты, а не только соединения. По сути, эта функция регистрирует обработчики в движке фильтрации.
Зарегистрированный callout необходимо добавить в систему, привязав его к определенному уровню:
typedef struct FWPM_DISPLAY_DATA0_
{
// Опциональное имя.
wchar_t *name;
// Описание.
wchar_t *description;
} FWPM_DISPLAY_DATA0;
typedef struct FWPM_CALLOUT0_
{
// Уникальный идентификатор callout.
GUID calloutKey;
// Удобочитаемые аннотации, связанные с callout.
FWPM_DISPLAY_DATA0 displayData;
// Флаги. Например, сохранять ли callout между перезагрузками.
UINT32 flags;
// Необязательный GUID ассоциированного провайдера.
GUID *providerKey;
// Произвольные данные, передаваемые провайдеру, если тот задан.
FWP_BYTE_BLOB providerData;
// GUID уровня, связанного с callout.
// Пример: FWPM_LAYER_INBOUND_IPPACKET_V4
GUID applicableLayer;
// Идентификатор, возвращенный функцией регистрации.
UINT32 calloutId;
} FWPM_CALLOUT0;
DWORD FwpmCalloutAdd0(
HANDLE engineHandle, const FWPM_CALLOUT0 *callout,
PSECURITY_DESCRIPTOR sd, UINT32 *id
);
Параметры функции FwpmCalloutAdd0():
• engineHandle — дескриптор открытого сеанса в подсистеме фильтрации.
• callout — описатель callout.
• sd — необязательный дескриптор безопасности callout.
• id — идентификатор callout.
Если callout успешно добавлен в систему, необходимо указать, при каких условиях он будет вызываться, то есть создать фильтр, для чего служит следующая функция:
DWORD FwpmFilterAdd0(HANDLE engineHandle,
const FWPM_FILTER0 *filter,
PSECURITY_DESCRIPTOR sd, UINT64 *id);
Параметры функции FwpmFilterAdd0():
• engineHandle — дескриптор открытого сеанса в подсистеме фильтрации.
• filter — добавляемый объект фильтра.
• sd — сведения о безопасности, связанные с фильтром; необязательный параметр.
• id — идентификатор среды выполнения для фильтра; необязательный параметр.
Фильтр описывается следующей структурой:
typedef struct FWPM_FILTER0_
{
// Уникальный идентификатор фильтра.
GUID filterKey;
// Данные человекочитаемого описания фильтра.
FWPM_DISPLAY_DATA0 displayData;
// Флаги, например флаг персистентности фильтра.
UINT32 flags;
// Необязательный идентификатор провайдера политики для фильтра.
GUID *providerKey;
// Данные, отправляемые провайдеру.
FWP_BYTE_BLOB providerData;
// Идентификатор уровня, к которому подключается фильтр.
GUID layerKey;
// Идентификатор подуровня, например FWPM_SUBLAYER_IPSEC_TUNNEL.
GUID subLayerKey;
// Приоритет фильтра. Чем выше, тем больше.
FWP_VALUE0 weight;
// Количество структур filterCondition в массиве.
UINT32 numFilterConditions;
// Условия фильтра, каждое из которых должно быть выполнено.
FWPM_FILTER_CONDITION0 *filterCondition;
// Действие, выполняемое фильтром.
FWPM_ACTION0 action;
// Контекст провайдера, зависит от флага
// FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT в контекстной информации
// провайдера.
union
{
UINT64 rawContext;
GUID providerContextKey;
};
GUID *reserved;
// LUID, идентифицирующий фильтр.
UINT64 filterId;
// Эффективный вес провайдера.
FWP_VALUE0 effectiveWeight;
} FWPM_FILTER0;
Фильтр активируется, если все условия соблюдены. В этом случае, если, например, в типе действия указано FWP_ACTION_CALLOUT_UNKNOWN, то есть вызывать callout, который может разрешить или заблокировать пакет, может быть вызван соответствующий зарегистрированный обработчик из callout.
Структуры условия и действия:
// Условие фильтрации.
typedef struct FWPM_FILTER_CONDITION0_
{
// Значения какого типа будут содержаться в структуре FWP_CONDITION_VALUE.
GUID fieldKey;
// Каким будет сравнение. Например, FWP_MATCH_GREATER.
FWP_MATCH_TYPE matchType;
// Значение, с которым будет сравниваться значение, полученное фильтром.
// Содержит тип и объединение, которое и представляет значение.
FWP_CONDITION_VALUE0 conditionValue;
} FWPM_FILTER_CONDITION0;
// Действие, выполняемое фильтром.
typedef struct FWPM_ACTION0_
{
// Тип действия: FWP_ACTION_BLOCK, FWP_ACTION_PERMIT,
// FWP_ACTION_CALLOUT_TERMINATING, FWP_ACTION_CALLOUT_INSPECTION и т.п.
FWP_ACTION_TYPE type;
union
{
// Произвольный GUID, выданный провайдером политики.
GUID filterType;
// GUID callout.
GUID calloutKey;
};
} FWPM_ACTION0;
И наконец, функция FwpmTransactionCommit0()подтвердит изменения, сделанные в рамках транзакции:
DWORD FwpmTransactionCommit0(HANDLE engineHandle);
Параметр engineHandle — дескриптор открытого сеанса в подсистеме фильтрации.
Функций в WFP достаточно много. Например, существует функция для создания подписки, которая будет вызывать обработчик в случае прихода уведомления. Функции по значению похожи на перечисленные выше, но имеют другой набор аргументов. В этом разделе мы провели только краткий обзор данной системы и поэтому подробно не рассматриваем все API и возможности.
Windows Subsystem for Linux, или WSL, — подсистема Windows, которая позволяет запускать инструменты командной строки Linux, такие как Bash-скрипты и файлы ELF64, в ОС Windows без изменений.
Существует возможность установить X-сервер и запускать под WSL графические приложения.
Это уровень совместимости для запуска среды, которая ведет себя так же, как Linux. WSL позиционируется как инструмент для разработчиков и системных администраторов. Используя ее, можно установить различные дистрибутивы Linux и получить доступ к терминалу, файловой и сетевой подсистемам Linux без использования виртуальных машин. Также можно запускать Windows-приложения из Linux и, наоборот, обмениваться файлами и данными по сети между Windows и Linux, использовать одинаковые учетные записи пользователей и настройки.
WSL управляется через одноименную утилиту командной строки. Еще одна утилита, которую можно использовать для управления WSL, — wslconfig.
Для первоначальной установки WSL в режиме администратора необходимо запустить следующую команду:
C:\> wsl --install
WSL можно установить через PowerShell или просто включив компонент в GUI инсталлятора, как показано на рис. 20.6.
Дистрибутив Linux можно указать сразу при установке, например:
C:\> wsl --install -d Debian
Или выбрать позже из списка доступных в Microsoft Store:
C:\> wsl --list --online
...
NAME FRIENDLY NAME
* Ubuntu Ubuntu
Debian Debian GNU/Linux
kali-linux Kali Linux Rolling
openSUSE-42 openSUSE Leap 42
SLES-12 SUSE Linux Enterprise Server v12
Ubuntu-16.04 Ubuntu 16.04 LTS
Ubuntu-18.04 Ubuntu 18.04 LTS
Ubuntu-20.04 Ubuntu 20.04 LTS
Рис. 20.6. Включение WSL
Установка собственного дистрибутива также поддерживается. Он должен представлять собой пакет AppX в .
Полный список команд WSL приведен в разделе MSDN , а работа с WSL подробно описана в .
WSL 1 и WSL 2
Существуют версии WSL 1 и WSL 2, переключаться между которыми можно, используя команду --set-version утилиты wsl.
Первая версия не поддерживает запуск полного ядра Linux, некоторые системные вызовы и IPv6.
WSL 1 предоставляет сетевой API Linux, а код стека выполняется в режиме ядра внутри драйвера подсистемы Linux.
Схема работы сети в WSL 1 изображена на рис. 20.7.
Рис. 20.7. Схема сети в WSL 1
Драйвер работает поверх API ядра — WinSock Kernel или WSK. В основном сетевые драйверы WSL транслируют вызовы BSD-сокетов в вызовы WSK и выполняются сетевым стеком ОС Windows.
Версия 2, показанная на рис. 20.8, запускает ядро Linux в управляемой виртуальной машине, обеспечивая более полную реализацию поддержки Linux и возможность запуска большего количества приложений.
Рис. 20.8. Архитектура WSL 2
Имя узла запущенной копии Linux берется из имени PC в Windows, и его нельзя изменить: содержимое файла /etc/hostname или вызов команды hostnamectl set-hostname не учитываются.
В отличие от первой версии, в WSL 2 сеть работает через виртуальный свитч Hyper-V. Хостовая система Windows является для гостевого Linux шлюзом по умолчанию и сервером DNS. Поэтому ее адрес можно получить, используя разные способы, например команду ip либо вызовы Netlink, на которых эта команда основана.
Другой простой способ — разбор файла /etc/resolv.conf, который является динамически генерируемой символической ссылкой на файл в /run/systemd/resolve, такой как /run/systemd/resolve/stub-resolv.conf:
awk '/^[^#]*\s*nameserver/ {print($2); exit;}' /etc/resolv.conf
Полученный адрес может быть использован для подключения к приложениям на хостовой системе Windows.
Из хостовой системы обратиться к экземпляру Linux в WSL можно по его IP-адресу, который известен.
Помимо интернет-сокетов AF_INET, для связи между хостовой и гостевой системами будут работать именованные сокеты Unix, но доступ к ним из хостовой системы, очевидно, будет возможен, только если они расположены на смонтированных в хостовую систему ФС.
В WSL 1 файловые системы Linux были доступны в хостовой системе как обычные каталоги. Изменения в них могли не отразиться в гостевой системе, а доступ к ним был медленным.
В WSL 2 доступ к файловым системам осуществляется через файловый сервер Plan 9 по протоколу 9P. Этот вариант эффективен и не имеет проблем синхронизации.
Кроме того, стоит обратить внимание на, казалось бы, очевидное обстоятельство: сокеты в WSL не работают поверх WinSock API и не всегда работают поверх сетевого стека ОС Windows.
В случае WSL 2 сокеты реализованы в стеке Linux, а его выполнение осуществляется виртуальной машиной.
В ОС Windows включено большое число сетевых библиотек, предоставляющих высокоуровневые API. Так, для работы со стандартными интернет-протоколами, такими как Gopher, FTP и HTTP, существует WinINet API. Этот интерфейс предоставляет разработчикам функции, упрощающие взаимодействие с сетевыми ресурсами. Его серверной высокоуровневой альтернативой является WinHTTP API.
Если говорить о еще более высокоуровневых решениях, стоит отметить Microsoft Foundation Classes, которые включают в себя классы для работы с сокетами.
Класс CSocket предоставляет возможность синхронной, или блокирующей, работы, что может быть полезно в определенных сценариях. Класс CAsyncSocket предназначен для асинхронной работы с сетевыми соединениями, что позволяет избежать блокировки потока исполнения во время ожидания ответа от сервера. А класс CArchive выполняет сериализацию и десериализацию, позволяя разработчикам легко передавать данные по сети.
Windows Filtering Platform — универсальная технология фильтрации сетевых данных, которая охватывает все основные уровни сетевой модели, от транспортного до канального, что позволяет не только фильтровать, но и изменять сетевые данные в реальном времени. В некотором роде это аналог BPF в ОС Windows.
Платформа фильтрации состоит из подсистемы фильтрации, базового модуля фильтрации, драйверов, оболочек совместимости и внешних модулей.
Возможность писать собственные модули делает WFP мощным инструментом для разработчиков.
Наконец, в современных версиях операционных систем Windows появилась возможность исполнять Linux-приложения, то есть файлы ELF64, без каких-либо изменений. За это отвечает подсистема Windows Subsystem for Linux. Теперь инструменты и приложения Linux можно использовать прямо из Windows. Однако стоит отметить, что сокеты в WSL работают с определенными ограничениями. В более современной версии WSL 2 этих ограничений значительно меньше по сравнению с WSL 1, что упрощает интеграцию Linux-приложений.
1. Какие функции высокого уровня в ОС Windows могут использовать прикладные разработчики?
2. Для чего нужен Windows Internet API?
3. С какими протоколами работает Windows Internet API?
4. Как загрузить документ по HTTP, используя Windows Internet API?
5. В чем разница между функциями HttpOpenRequest() и HttpSendRequest()?
6. Каково назначение Windows HTTP API? Чем он отличается от Windows Internet API?
7. Какие примитивы для сетевой работы есть в MFC?
8. Какой класс сетевой подсистемы MFC поддерживает асинхронное взаимодействие?
9. Для чего в MFC используется класс CArchive?
10. Какой вызов в MFC создает подключение к FTP-серверу?
11. Как заставить MFC-сокет ожидать подключения? А как обработать новое подключение?
12. Из каких основных частей состоит WFP?
13. Для чего нужна система фильтрации WFP?
14. Что такое callout? Какие типы callout бывают?
15. Для чего может быть полезна WSL в контексте сетевого программирования?
16. Какие существуют ограничения на использование BSD-сокетов в WSL?
17. В чем отличие работы сетевых вызовов в WSL 1 и WSL 2?
18. Напишите HTTP-запрос POST, используя WinINet.