С Новым годом! Форум программистов, компьютерный форум, киберфорум
Программирование драйверов
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.90/235: Рейтинг темы: голосов - 235, средняя оценка - 4.90
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1

Краткий обзор Windows Filtering Platform

04.11.2014, 11:21. Показов 49237. Ответов 9
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здесь я постараюсь дать небольшой обзор WFP, а также затронуть некоторые
технические моменты. Без каких-либо претензий на полноту, точность и т.д.



Часть 1, Основы.

Что такое WFP ?

WFP расшифровывается как Windows Filtering Platform, платформа фильтрации Windows.
Внимание ! Не путать с технологией WPF из .NET !

WFP - это универсальная технология фильтрации сети, охватывающая все основные
уровни, от транспортного (TCP/UDP) до канального (Ethernet), и дающая разработчикам
массу интересных возможностей.



Почему WFP ?

До WFP разработчики фаерволов, спам-фильтров, родконтролей и других программ
такого плана были вынуждены использовать сложные, разрозненные, а часто просто
недокументированные способы, которые во многих отношениях не давали нужного
эффекта или требовали высокой квалификации и учета многочисленных деталей, с
которыми то и дело возникали проблемы при переносе на другие версии Windows.
Пример таких технологий - TDI, LSP, NDIS, а также старые Filter-Hook Drivers и
Firewall-Hook Drivers. В WFP многие типовые проблемы этих технологий были учтены,
некоторые решения вообще не имеют аналогов, либо тяжелореализуемы в TDI/LSP/NDIS.
Сама технология имеет достаточно простую и понятную объектную модель, хорошо
документирована и при желании ее может освоить человек весьма средней квалификации.

Добавлю, что WFP - единственная на сегодняшний день технология, позволяющая
полноценно фильтровать трафик от modern-приложений на Windows 8 и выше.
И не только. Например, TDI и LSP в настоящее время уже не справляются даже с
Internet Explorer 11 на Windows 7. Поэтому при выборе технологии для нового проекта
следует в первую очередь рассматривать WFP, и только потом (если есть причины,
например совместимость с XP или отсутствие каких-то важных функций WFP на
целевых версиях Windows) другие.



Краткая история.

Появилась в Windows Vista, с тех пор постепенно развивается и дополняется.
В Windows 7 появилась возможность фильтрации на уровне Ethernet, возможность
делать редиректы соединений (как исходящих, так и входящих), а также разные
вспомогательные функции, например для своевременной очистки ресурсов.
В Windows 8 появилось то, чего так долго ждали некоторые разработчики -
возможность многократно перекидывать соединение с одного прокси на другой.
Раньше из-за отсутствия этой возможности было почти нереальным иметь в системе,
к примеру, параллельно работающий фаервол и какой-нибудь спам-фильтр от
разных производителей. Есть и другие изменения, все они описаны в MSDN.



Ложка дегтя.

Лично для меня ложкой дегтя в WFP является то, что наиболее "вкусные" вещи в
ней появились только в Windows 7 - Windows 8 и для того, чтобы сделать
коммерческий продукт, реально совместимый со всей современной линейкой Windows,
не списывая со счетов XP и Vista, все равно приходится использовать TDI/LSP
вместе с WFP. Также я замечал, что документация местами недостаточно детальная, а
кое-где содержит ошибки. В итоге приходилось иногда закапываться надолго в
отладчик, чтобы узнать, в чем дело. И в WFP, как и в любой технологии, есть
недочеты и даже ошибки. Сам я с ними на практике не сталкивался, но не раз
слышал о проблемах с Teredo, например, в результате чего система выпадает в
синий экран (BSOD).



Документация и исходники.

Главный источник информации по WFP - конечно же, MSDN.
Обращаю внимание, что WFP имеет функции как в kernel mode, так и в user mode,
поэтому одну часть документации следует искать в разделе о программировании
драйверов, а вторую - в разделе "network programming" из WinAPI:

Windows Filtering Platform
http://msdn.microsoft.com/en-u... 85%29.aspx

Windows Filtering Platform Callout Drivers
http://msdn.microsoft.com/en-u... 85%29.aspx

Аналогично, исходники примеров для WFP есть как в Windows SDK Samples (diagevents и
msnfilter) и в Windows Driver Kit Samples (ddproxy, inspect, msnmntr и stmedit).
Вероятно, на MSDN Gallery можно найти и другие исходники, а где-нибудь на codeproject,
sourceforge и других опенсорсных хостингах - исходники проектов с использованием WFP.



В следующей части: Архитектура WFP, объектная модель, принципы работы.
11
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
04.11.2014, 11:21
Ответы с готовыми решениями:

Обсуждение "Краткий обзор Windows Filtering Platform"
Статья классная! Она еще актуальна на данный момент? И было бы здорово ссылочку на финальную версию получить.

Windows Filtering Platform Sniffer
На основе этой статьи пробую написать сниффер. Не отрабатывает классифицирущая функция Classify. Причину не могу найти. Исходный код: ...

Windows Filtering Platform траблы с портом из Fixed Value
Взял пример inspect из 8.1 из сборника Убежденного, читаю его статью здесь. Осваиваю WFP. Прописал в реестре ip хабра, все собрал...

9
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
04.11.2014, 15:19  [ТС]
Часть 2, Архитектура.



Введение.

Рассмотрим типичный жизненный цикл обмена информацией по протоколу TCP.

1. Приложение (клиент) вызывает функцию connect, указывая адрес и порт пункта
назначения, а также семейство и тип протокола. Или же (сервер) оно вызывает accept в
ожидании подключений.

2. Через некоторое время соединение установлено. Клиенту возвращается управление из
функции connect (ну или приходит соответствующий сигнал, как в случае с моделью
select или асинхронным I/O - это уже за рамками темы), серверу возвращается
новый сокет, связанный с клиентом.

3. Приложения обмениваются данными посредством функций send и recv.

4. Приложения закрывают свои концы соединений функцией shutdown.
Когда оба конца закрыты, связь завершается, соединение считается разомкнутым.

Представим, что мы пишем фильтр TCP-трафика, и нас на каждом шаге этой
последовательности интересуют определенные данные:

1. Адрес и порт пункта назначения, ID процесса, который выполнил connect/accept.
Имя и вообще контекст безопасности пользователя, которому принадлежит процесс.

2. Статус операции connect (успех или ошибка).

3. Буферы, передаваемые через send и recv.

Предположим, мы хотим блокировать нежелательные соединения (по набору запрещенных
IP-адресов и портов, а также по процессам или пользователям). Мы также хотим получать
уведомление о закрытии соединения (п.4), чтобы освободить все связанные с ним данные.

WFP для решения этой задачи предлагает несколько абстракций:

Layers - уровни фильтрации. Каждым уровнем обслуживается определенный этап обработки
соединения или передачи данных. Например, установка соединения обрабатывается на
уровне ALE (будет рассмотрен ниже), а работа с буферами send/recv - на уровне Stream.
У каждого уровня своя специфика и свой набор свойств, с которыми можно работать.
Часто ту информацию, что можно достать на одном уровне, уже нельзя увидеть на других.
Аналогично, не все функции, которые работают на одних уровнях, работают на других.

Sublayers - подуровни. Нужны для организации более гибкой стратегии фильтрации.
Например, можно зарегистрировать проверки на разных Sublayers и они будут выполняться
по очереди.

Filters - фильтры. Задают разные условия, при которых должна (или не должна) выполняться
фильтрация. Например, можно фильтровать только трафик, идущий на порт 80, а остальной
игнорировать. Фильтры определяют не только условия, но и действия, т.е. что делать с трафиком,
когда условие срабатывает. Основные операции: разрешить, заблокировать, задержать, либо
вызвать соответствующий Callout. Про Callout-ы будет написано ниже.

Conditions - условия, заданные в фильтре. В фильтре может быть несколько условий.

Shims - исполнительные объекты, расположенные во всех ключевых точках сетевого стека.
Если Filters и Conditions только задают правила, что делать с трафиком, то Shims отвечают за
само выполнение этих правил, т.е. блокировка, задержка трафика, вызов Callout-а и так далее.

Callout - блок функций, которые обрабатывают трафик и решают, что конкретно с ним делать.

Callout Driver - драйвер, реализующий один или несколько Callout-ов.

Provider - поставщик. Объединяет логически связанные Filters, Sublayers и Callouts,
принадлежащие одной программе, одной задаче/политике, или объединенные какими-то
другими общими признаками.

Filter Engine (Engine) - функциональное ядро WFP, которое управляет фильтрами, Shim-ами,
Callout-ами и остальными объектами технологии.

Base Filter Engine (BFE) - служба, отвечающая за координацию компонентов WFP, за
регистрацию новых фильтров, за хранение конфигурации, настройки безопасности и т.д.

Ссылки по теме:

WFP Architecture
http://msdn.microsoft.com/en-u... 85%29.aspx

Object Model
http://msdn.microsoft.com/en-u... 85%29.aspx



Как это работает.

Когда в сетевом стеке происходит какое-то событие, например установка или закрытие
соединения, приход дейтаграммы, получение буфера с данными и т.п., Filter Engine с
помощью Filters определяет, нужно ли как-то обрабатывать это событие. В терминах WFP
данный процесс называется классификацией (classify). Если хотя бы один Condition
срабатывает, вызывается Shim, который, в зависимости от того, что задано в фильтре,
выполняет нужное действие. В частности, Shim может вызвать ваш зарегистрированный
Callout, точнее, одну из его функций (их всего три).

Все остальное выполняет Callout, в соответствии с логикой фильтрации трафика.
Если, к примеру, Callout работает на уровне ALE, то в функцию ему будут переданы
адрес и порт пункта назначения и источника, тип протокола, ID приложения, полный
путь к exe и другая полезная информация. Если на уровне Stream, там будут другие
данные - направление трафика (входящий/исходящий), флаги и т.д. Далее Callout
может поступать с данными на свое усмотрение: блокировка, игнор, редирект, анализ,
задержка трафика и многое другое. Все ограничено лишь фантазией разработчика.

Выше была описана типичная последовательность операций при работе с TCP.
Давайте посмотрим, как она будет фильтроваться WFP.

1. Установка соединения (connect).
Здесь будут срабатывать фильтры на уровне FWPS_LAYER_ALE_AUTH_CONNECT_V4.
Здесь же можно заблокировать соединение.

2. Соединение установлено.
Сработают фильтры на уровне FWPS_LAYER_ALE_FLOW_ESTABLISHED_V4.
Добавлю, что на этом уровне блокировать коннект не рекомендуется, он создан
только для того, чтобы Callout получил уведомление о том, успешно или нет
создано соединение. Здесь же можно установить связь между коннектом и
потоком данных.

3. Передача данных.
Будут срабатывать фильтры на уровне FWPS_LAYER_STREAM_V4, причем как
для send (outgoing data), так и для recv (incoming data).

4. Закрытие соединения.
Если с потоком данных был ассоциирован контекст, будет вызвана одна из
функций Callout-а. Также на Windows 7 есть специальные уровни для
очистки ресурсов - FWPS_LAYER_ALE_RESOURCE_RELEASE_V4.



Еще определения.

Flow (поток данных). Каждое соединение - это отдельный двунаправленный поток данных.
Если, к примеру, программа создает два коннекта на один и тот же адрес, вы будете в
драйвере видеть два разных потока данных, каждый со своим состоянием.

Flow Context (контекст потока). 64-битное число, ассоциированное с определенным
потоком данных. Нужно, чтобы отличать потоки друг от друга.

Fixed Values. Фиксированный набор аргументов, приходящий в Callout.
Описан здесь:

Data Field Identifiers
http://msdn.microsoft.com/en-u... 85%29.aspx

Для каждого Layer-а свой набор аргументов.
Например, на уровне FWPS_LAYER_ALE_CONNECT_REDIRECT_V4 (установка соединения) через
Fixed Values можно получить имя пользователя, а на уровне FWPS_LAYER_STREAM_V4
его уже нет.

Meta Values. Аналогично Fixed Values.
Информация здесь:

Metadata Fields at Each Filtering Layer
http://msdn.microsoft.com/en-u... 85%29.aspx

Итак, Callout, заглядывая в Fixed Values и Meta Values, решает, что делать с
данным событием/трафиком и предпринимает определенные действия, после чего весь
цикл повторяется заново. Доступ к буферам с данными осуществляется через
структуру NET_BUFFER (будет описана в следующих частях).
13
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
05.11.2014, 22:56  [ТС]
Часть 3-а, Регистрация в системе.

Регистрация выполняется в режиме пользователя, обычно эта функция возлагается на
установщик программы. Впрочем, никто не мешает регистрировать драйвер каждый
раз при запуске программы, WFP дает такую возможность.

Регистрация начинается с функции FwpmEngineOpen0.

Кстати, небольшое отступление. В WFP, как и в некоторых других специфических
компонентах Windows, принята своя система именования: ПрефиксТипОбъектОперацияВерсия.

Префикс - Fwp, общий для подсистемы WFP.

Тип - означает тип операции, managed (m) или run-time (s).
Все, что managed, относится к user mode, а run-time относится к kernel mode.
Например, при регистрации из user mode в качестве Layer передается константа
FWPM_LAYER_INBOUND_IPPACKET_V4, а в kernel mode она же фигурирует под именем
FWPS_LAYER_INBOUND_IPPACKET_V4.

Объект - указывает сущность, к которой применяется операция.
В данном случае Engine.

Open - операция.

0 - номер версии.

Похожие соглашения можно встретить в Windows еще кое-где (например, в WDF).
Итак, FwpmEngineOpen0:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FWPM_SESSION0   Session;
HANDLE          hEngine;
 
RtlZeroMemory(&Session, sizeof (Session));
 
Session.displayData.name        = L"MyWfpSession";
Session.displayData.description = L"My WFP Session";
 
DWORD Status = FwpmEngineOpen0(
    NULL,
    RPC_C_AUTHN_DEFAULT,
    NULL,
    &Session,
    &hEngine
);
 
if (ERROR_SUCCESS != Status)
{
    THROW_EXC("FwpmEngineOpen0 failed with status 0x%.8lx.", Status);
}
Я не буду подробно разбирать параметры функции, т.к. все это есть в MSDN.
В простейшем случае Session достаточно заполнить нулями, указав только
displayData.name и displayData.description. Отдельно стоит отметить флаг
FWPM_SESSION_FLAG_DYNAMIC - при его использовании регистрация действует
до того момента, когда будет закрыт хэндл hEngine (или до того момента,
когда приложение завершится).

MSDN настоятельно рекомендует оборачивать операции установки и удаления
внутрь транзакции, и я не вижу причин, по которым не стоит следовать
этому совету. Для этого нам помогут функции FwpmTransactionBegin0 и
FwpmTransactionCommit0, а также FwpmTransactionAbort0. Все очень просто:
сразу после FwpmEngineOpen открываем транзакцию и все остальные объекты
регистрируем между Begin и Commit. Если что-то пошло не так, зовется
функция Abort, отменяющая все сделанные изменения.

Следующий необходимый шаг - регистрация провайдера.

Как нетрудно догадаться, для этого нужна функция FwpmProviderAdd0.
И здесь все достаточно тривиально, обойдемся без комментариев.
Обращаю внимание на флаг FWPM_PROVIDER_FLAG_PERSISTENT: если его не
поставить, то после перезагрузки компьютера можно с удивлением обнаружить,
что никакого провайдера в системе нет. Аналогичные флаги персистентности
есть у Filters, Callouts и Sublayers.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FWPM_PROVIDER0 Provider;
 
RtlZeroMemory(&Provider, sizeof (Provider));
 
Provider.providerKey             = GUID_MyWfpProvider;
Provider.displayData.name        = L"MyWfpProvider";
Provider.displayData.description = L"My WFP Provider for test application";
Provider.flags                   = FWPM_PROVIDER_FLAG_PERSISTENT;
 
Status = FwpmProviderAdd0(hEngine, &Provider, NULL);
 
if (ERROR_SUCCESS != Status)
{
    THROW_EXC("FwpmProviderAdd0 failed with status 0x%.8lx.", Status);
}
Добавляем Sublayer.

При чтении документации WFP становится не сразу понятно, зачем вообще нужны
подуровни и почему нельзя поставить свой фильтр на какой-нибудь существующий
уровень/подуровень. Ответ находится здесь:

Filter Arbitration
http://msdn.microsoft.com/en-u... 85%29.aspx

Если вкратце, смысл вот в чем. Представьте, что на одном и том же подуровне
сидят два фильтра от разных провайдеров, которые анализирут трафик и решают,
пропускать его или нет. В этом случае, если верхний фильтр (т.е. тот, который
вызывается раньше другого) явным образом разрешит прохождение трафика
(FWP_ACTION_PERMIT), нижний фильтр вообще не будет участвовать в "голосовании".
Почему - спросите у разработчиков WFP. Так задумано. Но в таком случае это поведение
нарушает логику работы второго фильтра, который при других обстоятельствах
запретил бы трафик. Впрочем, вы можете посадить фильтр на какой-нибудь
предопределенный подуровень и не мучиться. Только потом не удивляйтесь, если
ваш Callout не всегда будет срабатывать.

Добавить Sublayer очень просто, как и все остальное:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FWPM_SUBLAYER0 Sublayer;
RtlZeroMemory(&Sublayer, sizeof (Sublayer));
 
Sublayer.subLayerKey             = GUID_MyWfpSublayer;
Sublayer.displayData.name        = L"MyWfpSublayer";
Sublayer.displayData.description = L"My WFP Sublayer";
Sublayer.flags                   = FWPM_SUBLAYER_FLAG_PERSISTENT;
Sublayer.providerKey             = (GUID *)&GUID_MyWfpProvider;
Sublayer.weight                  = 0x123;
 
Status = FwpmSubLayerAdd0(hEngine, &Sublayer, NULL);
 
if (ERROR_SUCCESS != Status)
{
    THROW_EXC("FwpmSublayerAdd0 failed with status 0x%.8lx.", Status);
}
Обращаю внимание на поле weight. Здесь устанавливается "вес" Sublayer-а.
Более "тяжелые" Sublayer-ы обрабатываются первыми.
Этот параметр нужен для упорядочивания своих Sublayer-ов, а вовсе не
для того, чтобы записать туда 0xFFFF в надежде быть всегда первым.

Регистрируем Callout:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FWPM_CALLOUT0 Callout;
RtlZeroMemory(&Callout, sizeof (Callout));
 
Callout.calloutKey              = GUID_MyWfpConnectCallout;
Callout.displayData.name        = L"MyWfpConnectCallout";
Callout.displayData.description = L"My WFP Connection Callout";
Callout.flags                   = FWPM_CALLOUT_FLAG_PERSISTENT;
Callout.providerKey             = (GUID *)&GUID_MyWfpProvider;
Callout.applicableLayer         = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
 
UINT32 CalloutId;
Status = FwpmCalloutAdd0(hEngine, &Callout, NULL, &CalloutId);
 
if (ERROR_SUCCESS != Status)
{
    THROW_EXC("FwpmCalloutAdd0 failed with status 0x%.8lx.", Status);
}
И снова все тривиально. FwpmCalloutAdd0 возвращает ID Callout-а.
Здесь я зарегистрировал Callout на уровне FWPM_LAYER_ALE_AUTH_CONNECT_V4.
Вы можете использовать другой уровень, можете добавить еще один или
несколько Callout-ов или Sublayer-ов, WFP это позволяет.
13
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
05.11.2014, 22:59  [ТС]
Часть 3-б, Регистрация.

Регистрация фильтров.

Переходим к самому сложному этапу:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
UINT32 const NumConds = 2;
FWPM_FILTER_CONDITION0 Conds[NumConds];  
 
Conds[0].fieldKey               = FWPM_CONDITION_IP_PROTOCOL;
Conds[0].matchType              = FWP_MATCH_EQUAL;
Conds[0].conditionValue.type    = FWP_UINT8;
Conds[0].conditionValue.uint8   = IPPROTO_TCP;
 
Conds[1].fieldKey               = FWPM_CONDITION_IP_REMOTE_PORT;
Conds[1].matchType              = FWP_MATCH_EQUAL;
Conds[1].conditionValue.type    = FWP_UINT16;
Conds[1].conditionValue.uint16  = 80;
 
FWPM_FILTER0 Filter;
RtlZeroMemory(&Filter, sizeof (Filter));
 
Filter.providerKey             = NULL;
Filter.displayData.name        = L"MyWfpConnectFilter";
Filter.displayData.description = L"My WFP Connection Filter";
Filter.flags                   = FWPM_FILTER_FLAG_PERSISTENT;
Filter.layerKey                = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
Filter.subLayerKey             = GUID_MyWfpSublayer;
Filter.weight.type             = FWP_UINT8;
Filter.numFilterConditions     = NumConds;
Filter.filterCondition         = &Conds[0];
Filter.action.type             = FWP_ACTION_CALLOUT_INSPECTION;
Filter.action.calloutKey       = GUID_MyWfpConnectCallout;
 
UINT64 FilterId;
 
Filter.filterKey = GUID_MyWfpConnectFilter;
Filter.weight.uint8 = 1;
 
Status = FwpmFilterAdd0(hEngine, &Filter, NULL, &FilterId);
 
if (ERROR_SUCCESS != Status)
{
    THROW_EXC("FwpmFilterAdd0 failed with status 0x%.8lx.", Status);
}
Для данного фильтра мы создали два условия (Conditions): 1) протокол TCP;
2) порт 80. Таким образом, мы хотим ловить весь трафик, проходящий по TCP
на 80 порту (обычно HTTP).

В каждом условии fieldKey задает признак, который будет проверяться.
Их достаточно много:

Filtering Condition Identifiers
http://msdn.microsoft.com/en-u... 85%29.aspx

Кроме этого, можно проверять также различные флаги, например на Windows 8 с
помощью флага FWP_CONDITION_FLAG_IS_PROXY_CONNECTION можно проверить, является
ли соединение проксируемым. Полный список здесь:

Filtering Condition Flags
http://msdn.microsoft.com/en-u... 85%29.aspx

Но будьте осторожны !
Как и в случае с Fixed Values / Meta Values, конкретные свойства и флаги
доступны только на конкретных уровнях.

Filtering Conditions Available at Each Filtering Layer
http://msdn.microsoft.com/en-u... 85%29.aspx

matchType задает тип проверки - равно/не равно, больше/меньше, проверка диапазона и т.д.
Например, если бы я вместо FWP_MATCH_EQUAL поставил FWP_MATCH_GREATER, фильтр
стал бы срабатывать на всех соединениях с номером порта больше 80. Вот список
доступных типов проверки:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef enum FWP_MATCH_TYPE_ { 
  FWP_MATCH_EQUAL,
  FWP_MATCH_GREATER,
  FWP_MATCH_LESS,
  FWP_MATCH_GREATER_OR_EQUAL,
  FWP_MATCH_LESS_OR_EQUAL,
  FWP_MATCH_RANGE,
  FWP_MATCH_FLAGS_ALL_SET,
  FWP_MATCH_FLAGS_ANY_SET,
  FWP_MATCH_FLAGS_NONE_SET,
  FWP_MATCH_EQUAL_CASE_INSENSITIVE,
  FWP_MATCH_NOT_EQUAL,
  FWP_MATCH_TYPE_MAX
} FWP_MATCH_TYPE;
conditionValue.type задает тип данных, а в conditionValue передается само
значение, которое требуется сравнивать.

Фильтр устанавливается на конкретном Sublayer-е (Filter.subLayerKey) и,
как и другие объекты, имеет схожие флаги. "Вес" фильтра имеет значение,
когда у вас на одном Sublayer сидят два или более фильтров. В других
случаях конкретное значение роли не играет. Но на всякий случай все
равно советую заглянуть сюда:

Filter Weight Assignment
http://msdn.microsoft.com/en-u... 85%29.aspx

Filter Weight Identifiers
http://msdn.microsoft.com/en-u... 85%29.aspx

Ну и ключевой момент: в action.type указывается тип действия, которое
будет выполняться при срабатывании фильтра. Возможных значений всего пять:

FWP_ACTION_BLOCK

Заблокировать трафик.

FWP_ACTION_PERMIT

Разрешить трафик.

FWP_ACTION_CALLOUT_TERMINATING
FWP_ACTION_CALLOUT_INSPECTION
FWP_ACTION_CALLOUT_UNKNOWN

Вызвать Callout. Разница между TERMINATING, INSPECTION и UNKNOWN только в
том, что позволено делать Callout-у. TERMINATING-Callout обязан явным образом
либо разрешить, либо заблокировать соединение. INSPECTION-Callout может
только наблюдать. UNKNOWN-Callout может, но не обязан, блокировать или
разрешать трафик.

Если вы ассоциируете с фильтром Callout, то должны указать его GUID в
поле action.calloutKey. Пока Callout Driver еще не написан, так что
делать этот фильтр ничего полезного не будет.

Кстати, здесь может возникнуть вопрос: а зачем вообще нужен Callout, если
можно создать правило и установить в фильтре action.type = FWP_ACTION_BLOCK,
блокируя нежелательные соединения ? Правильно, так можно и нужно делать,
если этого достаточно для вашей задачи. Добавлю, что в WFP есть несколько
предопределенных Callout-ов для некоторых задач, собраны они здесь:

Built-in Callout Identifiers
http://msdn.microsoft.com/en-u... 85%29.aspx

Но все же FWP_ACTION_BLOCK и встроенных Callout-ов часто бывает недостаточно.
Если вам нужно анализировать, а тем более изменять трафик, работая с
буферами данных, то без написания Callout-драйвера не обойтись.

Итак, подытожим. Мы создаем хэндл Engine, открываем транзакцию, затем внутри
транзакции регистрируем своего провайдера, связываем с ним один или несколько
Sublayers, добавляем Callouts и Filters на нужные уровни/подуровни, остается
только позвать FwpmTransactionCommit0 и позакрывать хэндлы. С этого момента
ваши объекты вступают в "игру" в сетевом стеке Windows.

Отмена регистрации - обратная процедура, еще более простая.
Функции FwpmEngineOpen0 и FwpmTransactionBegin0 должны быть уже вам знакомы,
они и здесь используются. Для удаления фильтров используйте FwpmFilterDeleteByKey0,
для удаления Callout-ов - FwpmCalloutDeleteByKey0, затем можно удалить Sublayer -
FwpmSubLayerDeleteByKey, и наконец, провайдера - FwpmProviderDeleteByKey0,
после чего завершаете транзакцию и закрываете хэндлы.

В следующей части: реализация Callout в драйвере.
12
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
09.11.2014, 23:32  [ТС]
Часть 4-а, реализация Callout-драйвера.

Переходим к реализации Callout-драйвера.
Первое, что нужно сделать - создать устройство (DEVICE_OBJECT), для этого используется
функция IoCreateDevice. Далее вызываем FwpsCalloutRegister0 столько раз, сколько
Callout-ов нам требуется зарегистрировать:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
FWPS_CALLOUT0   CalloutInitData;
DEVICE_OBJECT * pDeviceObj;
NTSTATUS        Status;
    
Status = IoCreateDevice(
    pDriverObj,
    0,      // Size of device extension.
    NULL,   // Name of the device.
    FILE_DEVICE_UNKNOWN,
    0,      // Device characteristics.
    FALSE,  // Exclusive or not.
    &pDeviceObj
    );
        
if (!NT_SUCCESS(Status))
{
    LOG_ERROR(
        "IoCreateDevice failed with status 0x%.8lx.\r\n",
        Status
        );
    goto ErrorExit;
}
 
RtlZeroMemory(&CalloutInitData, sizeof (CalloutInitData));
 
CalloutInitData.calloutKey   = GUID_MyWfpConnectCallout;
CalloutInitData.flags        = 0; // Flags.
CalloutInitData.classifyFn   = OnClassify;
CalloutInitData.notifyFn     = OnNotify;
CalloutInitData.flowDeleteFn = OnFlowDelete;
    
Status = FwpsCalloutRegister0(
    pDeviceObj,
    &CalloutInitData,
    &ConnectCalloutId
    );
        
if (STATUS_SUCCESS != Status)
{
    LOG_ERROR(
        "FwpsCalloutRegister0 failed with status 0x%.8lx.\r\n",
        Status
        );
    goto ErrorExit;
}
При регистрации указывается GUID Callout-а, флаги, а также три стандартные функции:
classifyFn, notifyFn и flowDelete. Вы можете использовать одни и те же функции для
разных Callout-ов.

Кстати, в Windows 7 и выше доступна функция FwpsCalloutRegister1, а в Windows 8 -
FwpsCalloutRegister2. Они предоставляют более интересные возможности, например
редирект соединений.

Обращаю внимание на флаг FWP_CALLOUT_FLAG_CONDITIONAL_ON_FLOW: если установить
его, Callout будет срабатывать только для данных, с которыми связан определенный Flow Context.
Иными словами, можно выборочно контролировать определенный трафик, игнорируя остальные.

Также замечу, что если необходимые фильтры уже зарегистрированы, Callout начинает
действовать сразу же, еще до выхода из функции FwpsCalloutRegister0, так что если у вас есть
какие-то общие данные, разделяемые с Callout-ом, они должны быть инициализированы до того,
как эта функция будет вызвана.

Удалить Callout также просто: FwpsCalloutUnregisterById0, функция принимает единственный
аргумент - ID Callout-а. После удаления Callout-ов не забудьте удалить устройство,
созданное IoCreateDevice.
13
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
09.11.2014, 23:41  [ТС]
Часть 4-б, функции Callout-а.

Их всего три: classifyFn, notifyFn и flowDelete, причем notifyFn может быть реализована
очень просто, а flowDelete опциональна и вместо нее можно передать NULL (если вы не
работаете с Flow Context).

classifyFn
C
1
2
3
4
5
6
7
8
9
10
11
12
13
VOID
NTAPI
OnClassify(
    __in    FWPS_INCOMING_VALUES0 const           * pInFixedValues,
    __in    FWPS_INCOMING_METADATA_VALUES0 const  * pInMetaValues,
    __out   VOID                                  * pLayerData,
    __in    FWPS_FILTER0 const                    * pFilter,
    __in    UINT64                                  FlowContext,
    __out   FWPS_CLASSIFY_OUT0                    * pClassifyOut
    )
{
    // ...
}
В этой функции вы будете проводить большую часть времени, работая
над Callout-драйвером. Разберем по порядку параметры:

pInFixedValues - Массив Fixed Values (рассматривалось выше).

Вытаскивать данные из этого массива следует примерно так:
C
1
2
3
4
5
6
//
// Get remote IP address (ALE_FLOW_ESTABLISHED layer).
//
UINT32 Addr = pInFixedValues->incomingValue[
    FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS
    ].value.uint32;
Как я уже писал выше, состав Fixed Values для каждого уровня свой.

pInMetaValues - Массив Meta Values.

Перед тем, как обращаться к полям этого массива, следует проверить,
присутствует ли значение в нужном поле. Например:
C
1
2
3
4
5
6
if (FWPS_IS_METADATA_FIELD_PRESENT(pInMetaValues, FWPS_METADATA_FIELD_FLOW_HANDLE))
{
    //
    // Use pInMetaValues->flowHandle.
    //
}
pLayerData - указатель на данные (буферы).

Здесь может быть либо NULL, в зависимости от условий, либо указатель на
структуру FWPS_STREAM_CALLOUT_IO_PACKET0 (для stream-уровней, таких
как FWPS_LAYER_STREAM_V4), либо NET_BUFFER. Так или иначе все сведется к
работе с NET_BUFFER (будет описана в одной из следующих частей).

Состав данных, которые приходят в pLayerData, сильно зависит от уровня
фильтрации, на котором зарегистрирован Callout. Например, обрабатывая
TCP-поток на уровне FWPS_LAYER_STREAM_V4, вы не увидите в данных
заголовков IP-пакетов и т.п.

pFilter - указатель на Filter, по условию которого сработал ваш Callout.
Здесь находятся важные данные, которые нельзя проигнорировать.
Отнеситесь с вниманием к флагам FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT и
FWPS_FILTER_FLAG_PERMIT_IF_CALLOUT_UNREG ISTERED, как описано здесь:

FWPS_FILTER0 structure
http://msdn.microsoft.com/en-u... 85%29.aspx

Если FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT установлен, вы должны сбросить флаг
FWPS_RIGHT_ACTION_WRITE в соответствующей структуре (будет описана ниже),
если завершаете classifyFn с кодом FWP_ACTION_PERMIT (разрешить) или
FWP_ACTION_BLOCK (заблокировать). Обычно сбрасывать FWPS_RIGHT_ACTION_WRITE
требуется только при FWP_ACTION_BLOCK.

Флаг FWPS_FILTER_FLAG_PERMIT_IF_CALLOUT_UNREG ISTERED означает, что если
Callout не зарегистрирован, вам следует пропустить трафик дальше, не блокируя его.

В поле action фильтра будет указан тип Callout-а, в зависимости от которого
вам разрешается или нет использовать соответствующие коды завершения, об этом я
уже писал выше: FWP_ACTION_CALLOUT_TERMINATING требует, чтобы Callout установил
для трафика FWP_ACTION_PERMIT или FWP_ACTION_BLOCK, FWP_ACTION_CALLOUT_INSPECTION
может только наблюдать (FWP_ACTION_CONTINUE) и т.д.

Вы не должны нарушать эти требования, иначе правильная система фильтрации не
гарантируется. На таких принципах, кстати, построено очень многое во взаимодействии
фильтрующих драйверов, не только сетевых: вам дается большая гибкость и разнообразие
средств, но вместе с тем вы должны придерживаться определенных правил и не ставить
"палки в колеса" другим драйверам.

FlowContext - название говорит само за себя. Здесь передается значение, связанное с
потоком данных, при условии, что вы задали его где-то раньше. Это значение вы можете
использовать на свое усмотрение, например передавать через него адрес каких-то структур
данных или функций, связанных именно с этим потоком, либо что-то еще. Размер поля
FlowContext - 64 бита, хватит на все, единственное ограничение - нельзя иметь Flow Context,
равный нулю.

Про Flow Context и потоки данных будет написано в одной из следующих частей.

pClassifyOut - сюда вы указываете, как хотите поступить с трафиком.

Поле actionType:

FWP_ACTION_PERMIT - разрешить;

FWP_ACTION_BLOCK - заблокировать;

FWP_ACTION_CONTINUE - передать запрос на рассмотрение в другой фильтр.

Вы можете установить любой код, если он не противоречит условиям action фильтра,
описанным выше. Обратите внимание, что если флаг FWPS_RIGHT_ACTION_WRITE в
поле rights сброшен, то вы не имеете права ничего писать в actionType, кроме
FWP_ACTION_BLOCK. То есть, либо пишете FWP_ACTION_BLOCK, либо оставляете
поле как есть, без изменений.

Попробуем сложить все вместе. Пример ниже подразумевает, что в фильтре указан
action.type FWP_ACTION_CALLOUT_UNKNOWN, т.е. Callout может возвращать и permit, и
block, и continue:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
VOID
NTAPI
OnClassify(
    __in    FWPS_INCOMING_VALUES0 const           * pInFixedValues,
    __in    FWPS_INCOMING_METADATA_VALUES0 const  * pInMetaValues,
    __out   VOID                                  * pLayerData,
    __in    FWPS_FILTER0 const                    * pFilter,
    __in    UINT64                                  FlowContext,
    __out   FWPS_CLASSIFY_OUT0                    * pClassifyOut
    )
{
    //
    // Check layer.
    //
    if (FWPS_LAYER_STREAM_V4 == pInFixedValues->layerId)
    {
        //
        // Working with fixed values.
        //
        UINT32 IpAddr = pInFixedValues->incomingValue[
            FWPS_FIELD_STREAM_V4_IP_REMOTE_ADDRESS
            ].value.uint32;
            
        // ...
        
        //
        // Working with meta values.
        //
        if (FWPS_IS_METADATA_FIELD_PRESENT(pInMetaValues, FWPS_METADATA_FIELD_PROCESS_ID ))
        {
            UINT32 ProcessId = pInMetaValues->processId;
            
            // ...
        }
    }
    
    //
    // Working with layer data (buffers).
    //    
    pIoPacket = (FWPS_STREAM_CALLOUT_IO_PACKET0 *)pLayerData
    
    // ...
    
    //
    // Applying filter decision.
    //
    
    switch (Decision)
    {
        case DECISION_PERMIT_TRAFFIC:
        {
            if (IS_FLAG_ENABLED(pClassifyOut->rights, FWPS_RIGHT_ACTION_WRITE))
            {
                pClassifyOut->actionType = FWP_ACTION_PERMIT;
            }
            
            if (FLAG_ENABLED(pFilter->flags, FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT))
            {
                CLEAR_FLAG(pClassifyOut->rights, FWPS_RIGHT_ACTION_WRITE);
            }
        }
        break;
        
        case DECISION_BLOCK_TRAFFIC:
        {
            pClassifyOut->actionType = FWP_ACTION_BLOCK;
            
            if (IS_FLAG_ENABLED(pFilter->flags, FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT))
            {
                CLEAR_FLAG(pClassifyOut->rights, FWPS_RIGHT_ACTION_WRITE);
            }
        }
        break;
        
        case DECISION_CONTINUE:
        {
            if (IS_FLAG_ENABLED(pClassifyOut->rights, FWPS_RIGHT_ACTION_WRITE))
            {
                pClassifyOut->actionType = FWP_ACTION_CONTINUE;
            }                       
        }
        break;
    }    
}
Как видите, ничего заумного. Разумеется, все самое интересное, что будет
составлять суть вашей программы, кроется за комментариями "//...".

Напоследок добавлю, что вы не можете просто взять и изменить данные в pLayerData.
Для подобных вещей в WFP принято несколько схем: "close-reinject", "drop-reinject", и т.д.
То есть, чтобы изменить данные, следует сначала заблокировать (FWP_ACTION_BLOCK)
нужный фрагмент (см. структуру FWPS_STREAM_CALLOUT_IO_PACKET0), а затем вставить
новый - FwpsStreamInjectAsync0. Если требуется отложить обработку трафика, например
задать вопрос пользователю в интерактивном режиме, следует использовать специальные
функции: на ALE-уровнях есть FwpsPendOperation0/FwpsCompleteOperation0, на Stream-
уровнях есть FwpsCloneStreamData0/FwpsStreamInjectAsync0 и т.д.

Собственно, здесь все достаточно хорошо расписано, еще и с примерами:

Inspecting Packet and Stream Data
http://msdn.microsoft.com/en-u... 85%29.aspx

notifyFn
C
1
2
3
4
5
6
7
8
9
10
NTSTATUS
NTAPI
OnNotify(
    __in    FWPS_CALLOUT_NOTIFY_TYPE                NotifyType,
    __in    GUID const                            * pFilterKey,
    __in    FWPS_FILTER0 const                    * pFilter
    )
{
    // ...
}
Очень простая функция.
Сюда приходят уведомления о добавлении и удалении фильтров:
FWPS_CALLOUT_NOTIFY_ADD_FILTER и FWPS_CALLOUT_NOTIFY_DELETE_FILTER.

Все, что вам нужно - вернуть STATUS_SUCCESS, либо код ошибки,
если вы не хотите, чтобы фильтр был добавлен в Filter Engine.
Своего рода фильтр для фильтров.

flowDeleteFn
C
1
2
3
4
5
6
7
8
9
10
VOID
NTAPI
OnFlowDelete(
    __in    UINT16                                  LayerId,
    __in    UINT32                                  CalloutId,
    __in    UINT64                                  FlowContext
    )
{
    // ...
}
Эта функция вызывается, когда уничтожается поток данных.
Здесь последняя возможность очистить связанные с потоком данные, используя
значение FlowContext. Если вы не используете FlowContext, можете игнорировать
эту функцию или вообще установить ее в NULL при регистрации Callout-а.

Не забывайте, что все три функции Callout-а вызываются на IRQL <= DISPATCH_LEVEL.

В следующей части: потоки данных, работа с Flow Context,
как отличить данные одного соединения от другого.
13
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
12.11.2014, 21:33  [ТС]
Часть 5, Flow Context, потоки и их контексты.

Часто при написании WFP-драйверов возникает проблема: как в функции
classifyFn понять, с каким именно потоком данных мы работаем.
Проще говоря, к какому соединению этот поток данных относится.

Знатоки TCP/IP могут уверенно возразить, что TCP-соединение однозначно
идентифицируется четырьмя числами (4-tuple): IP-адрес источника, порт
источника, IP-адрес пункта назначения, порт пункта назначения.
Осталось только вытащить эти данные откуда-нибудь из Fixed Values или
Meta Values и всего делов. В голову приходит схема "ключ-значение": в
ключе у нас будет 4-tuple, в значении указатель на данные, связанные с
потоком, все это заворачиваем в какой-нибудь AVL Tree, защищаем
спин-локом и готово...

Но в WFP для этих целей уже существует простое и эффективное средство,
которое называется Flow Context. На определенных уровнях фильтрации, где
понятия "соединение" или "поток" имеют хоть какой-то смысл, вместе с данными
приходит хэндл потока (Flow Handle), и вы можете, используя этот хэндл,
связать с потоком произвольное 64-битное значение (обычно указатель).
Делается это функцией

FwpsFlowAssociateContext0 function
http://msdn.microsoft.com/en-u... 85%29.aspx

Базовая информация здесь:

Associating Context with a Data Flow
http://msdn.microsoft.com/en-u... 85%29.aspx

Flow Handle лежит в массиве Meta Values, поле так и называется - flowHandle.
Не забудьте проверить, что это поле доступно для уровня, на котором вы к
нему обращаетесь - FWPS_IS_METADATA_FIELD_PRESENT. В FwpsFlowAssociateContext0
вы должны также указать ID Callout-а, который будет работать с потоком, и
Layer, на котором этот Callout зарегистрирован. Ну и, собственно, 64-битное
значение на ваш выбор (кроме нуля), которое и называется Flow Context.

С этого момента classifyFn указанного Callout-а, обрабатывая данный поток,
будет давать вам Flow Context в одном из параметров.

Когда соединение будет закрыто, сработает функция Callout-а flowDeleteFn, в
которой вы можете освободить связанные с потоком данные, которые больше не нужны.

Если Flow Context нужно освободить где-то раньше, воспользуйтесь функцией

FwpsFlowRemoveContext0 function
http://msdn.microsoft.com/en-u... 85%29.aspx

При этом сработает flowDeleteFn, где вы сможете выполнить необходимую очистку.
После этого с потоком снова можно будет связать контекст. Обратите внимание
на ремарку: FwpsFlowRemoveContext0 может вернуть STATUS_PENDING, если потоком в
данный момент занята функция classifyFn, т.е. очистка Flow Context будет
отложена на некоторое время. Также добавлю, что внутри flowDeleteFn звать
FwpsFlowRemoveContext0 не нужно, т.к. Flow Context к этому моменту уже уничтожен.

Пример использования.

Представьте, что вы хотите вести статистику, сколько данных по каким TCP-
соединениям принято или отправлено. Можно поступить следующим образом:
зарегистрировать два Callout-а, один на FWPS_LAYER_ALE_FLOW_ESTABLISHED_V4,
второй на FWPS_LAYER_STREAM_V4. Когда сработает верхний Callout, т.е.
установлено новое соединение, вы создаете в памяти экземпляр некой
структуры для хранения статистики по данному соединению, и связываете его с
данным потоком через Flow Context. При этом в FwpsFlowAssociateContext0
следует указать ID нижнего Callout-а и уровень, на котором он работает.
В classifyFn нижнего Callout-а будет приходить Flow Context - вы
превращаете его в указатель и работаете с полями структуры.
Когда срабатывает flowDeleteFn, экземпляр структуры можно удалить.

Кстати, при регистрации нижнего Callout-а вы можете указать флаг
FWP_CALLOUT_FLAG_CONDITIONAL_ON_FLOW. В этом случае его classifyFn
вообще не будет вызываться для потоков, с которыми не связан Flow Context.

Некоторые могут спросить: не лучше ли здесь использовать
FWPS_LAYER_ALE_AUTH_CONNECT_V4 вместо FWPS_LAYER_ALE_FLOW_ESTABLISHED_V4 ?
Ведь на нем можно еще и блокировать соединения, если потребуется.

Дело в том, что на Windows Vista и Windows Server 2008 на уровне
FWPS_LAYER_ALE_AUTH_CONNECT_V4, вопреки официальной документации, хэндла
потока (Flow Handle) нет и вызывать FwpsFlowAssociateContext0 не с чем.

Metadata Fields at Each Filtering Layer
http://msdn.microsoft.com/en-u... 85%29.aspx
FWPS_LAYER_ALE_AUTH_CONNECT_V4
FWPS_LAYER_ALE_AUTH_CONNECT_V6

FWPS_METADATA_FIELD_FLOW_HANDLE
FWPS_METADATA_FIELD_PROCESS_PATH
FWPS_METADATA_FIELD_TOKEN
FWPS_METADATA_FIELD_PROCESS_ID
FWPS_METADATA_FIELD_RESERVED
FWPS_METADATA_FIELD_TRANSPORT_HEADER_SIZ E
FWPS_METADATA_FIELD_COMPARTMENT_ID
FWPS_METADATA_FIELD_COMPLETION_HANDLE
FWPS_METADATA_FIELD_TRANSPORT_ENDPOINT_H ANDLE
FWPS_METADATA_FIELD_REMOTE_SCOPE_ID
FWPS_METADATA_FIELD_PACKET_DIRECTION
FWPS_METADATA_FIELD_PARENT_ENDPOINT_HAND LE
FWPS_METADATA_FIELD_ICMP_ID_AND_SEQUENCE
FWPS_METADATA_FIELD_SUB_PROCESS_TAG
Flow handle not available at FWPM_LAYER_ALE_AUTH_CONNECT_V4 on Vista ?
https://social.msdn.microsoft.... ?forum=wfp
Hi,

I developed a wfp callout that works just fine on Win7. Now I'm testing it on Vista,
and face a weird issue. I couldn't find any similar problems anywhere, so it
could just be a chair-keyboard interface problem.

What happens is that in my ALE Connect classify function, I associate a flow context,
in a similar way that is described in this msdn article :
http://msdn.microsoft.com/en-u... s.85).aspx.

But, on Vista, checking if the flow handle field is available with
C
1
FWPS_IS_METADATA_FIELD_PRESENT(inMetaValues, FWPS_METADATA_FIELD_FLOW_HANDLE)
just returns FALSE. If I remove the check, the subsequent call to FwpsFlowAssociateContext0
fails with a 0xC0000225 (STATUS_NOT_FOUND) error.

--------

In Vista, the flow handle is only notified at the FLOW_ESTABLISHED layers as well as
post-ALE layers of DATAGRAM_DATA and STREAM. This was expanded in Windows 7 to
include ALE_AUTH_CONNECT/RECV_ACCEPT and the TRANSPORT layers.
You can read more about it by downloading the document at
http://www.microsoft.com/en-us... x?id=20431.
Если кто не верит - возьмите отладчик и убедитесь (я сам так часто поступаю).

В следующей части: NET_BUFFER - что это и как с ним работать.
12
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
20.11.2014, 22:43  [ТС]
Часть 6-а, архитектура NET_BUFFER, общие данные.

Эта часть, возможно, будет самой сложной для восприятия.
Для начала небольшое отступление.

Если вы знакомы с сетевой моделью OSI и TCP/IP и читали соответствующую
литературу, например Стивенса или Таненбаума, - а я надеюсь, что это так,
ибо в противном случае вам еще рано писать WFP-фильтры, - то наверняка вы
знаете, что данные в сетях не ходят "просто так", а проходят через стек
протоколов, от прикладного (верхний уровень) до канального (нижний) и
обратно, причем формат обмена на каждом уровне свой.

На транспортном уровне (TCP и UDP) весь поток данных заворачивается в
так называемые сегменты. Сегменты позволяют, в числе прочего, организовать
контроль доставки данных и их порядка, что для протокола TCP является
одним из главных требований, иначе это не TCP никакой вовсе. Например, если
сегмент с определенным номером не пришел в пункт назначения, то он будет
отправлен повторно. Структуру сегмента рассматривать не будем, здесь
достаточно знать, что у сегмента есть заголовок и данные.

Итак, данные при прохождении через транспортный уровень обрастают заголовками.
Пример (утрированный):

Code
1
2
3
4
5
Client
["Hello, World!"] --->
 
TCP
--->[TCP "Hell"][TCP "o, World!"]
Сетевой уровень (IP) оперирует понятием "пакет". Сегменты, которые пришли
сверху, с TCP/IP, будут разбиты на IP-пакеты и обрастут IP-заголовками:

Code
1
2
IP
[IP TCP "d!"][IP TCP "He"][IP TCP "ll"][IP TCP "ll"][IP TCP "o, Worl"]
Обратите внимание: пакеты могут быть перемешаны, иметь дубликаты и т.п.
Такова специфика данного уровня. Для программ, работающих с сетью на
прикладном уровне (SMTP, HTTP, FTP и т.д.) или на TCP, все это прозрачно:
они видят просто поток данных туда и сюда, никаких пакетов,
перемешивания и прочего.

Наконец, канальный уровень (Ethernet) делит поток на кадры (фреймы):

Code
1
2
Ethernet
[ETH IP TCP "o, Worl" IP TCP "He"][ETH IP TCP "ll"][ETH IP TCP "d!"][ETH IP TCP "ll"]
На этом уровне также возможна фрагментация, дубликаты и перемешивание.
Для большей наглядности всего "хаоса", который творится в сетевом стеке, я
специально запихнул два IP-пакета в один Ethernet-фрейм.

При приеме данных происходит обратная картина: сначала канальный уровень
формирует IP-пакеты, убирая свои заголовки, затем сетевой уровень из
пакетов составляет TCP-поток, после этого транспортный уровень очищает
поток от своих сегментов и отдает данные в клиентское приложение:

Code
1
2
3
4
5
6
7
8
9
10
11
Ethernet
[ETH IP TCP "ll"][ETH IP TCP "o, Worl" IP TCP "He"][ETH IP TCP "d!"][ETH IP TCP "ll"] --->
 
IP
---> [IP TCP "d!"][IP TCP "ll"][IP TCP "o, Worl"][IP TCP "He"][IP TCP "ll"] --->
 
TCP
---> [TCP "Hell"][TCP "o, World!"] --->
 
Client
---> ["Hello, World!"]
Сразу подчеркну, что нарисованная картинка далека от совершенства.
Здесь не затронуты промежуточные уровни (например, ICMP или IpSec) и другие
протоколы (например, для беспроводных или модемных соединений), не показано,
как данные сжимаются, шифруются, проходят через многочисленные сетевые
компоненты - прокси, маршрутизаторы и т.п.

Цель картинки - показать фрагментацию данных на каждом уровне, а также
обрастание заголовками и служебными данными.

На этом отступление можно закончить.



Часть 6-б, архитектура NET_BUFFER: need to go deeper.

NET_BUFFER.

Краткое определение: структура работы с данными, оптимизированная под
особенности сетевого стека Windows.

Ссылки:

NET_BUFFER Architecture
http://msdn.microsoft.com/en-u... 85%29.aspx

NDIS 6 Net Buffer Lists and Net Buffers
http://codemachine.com/article_ndis6nbls.html

Если NET_BUFFER для вас в новинку, то прежде чем читать дальше,
обязательно пройдите по ссылкам выше.



При первом знакомстве NET_BUFFER производит примерно такое впечатление:

- что за бред (WTF) ?
- почему так завернуто, крыша поедет ведь ?
- они что там, свихнулись все ?
- как прочесть хотя бы первый байт данных ?
- как хоть что-нибудь прочитать оттуда ?
- лю-ю-ю-ди-и-и-и-и ?!?..

Да, структура сложна в эксплуатации, с этим ничего не поделаешь.
Но давайте попробуем понять почему.

Представьте, что драйвер сетевого адаптера (NDIS) только что принял некую
порцию данных. Теперь ему нужно удалить оттуда всю служебную информацию, т.е.
Ethernet-заголовки. Это, упрощенно говоря, выливается в такую операцию:
"создать буфер 2, пройтись по буферу 1, копируя из него только нужную
информацию в буфер 2, удалить буфер 1, отдать буфер 2 на уровень выше".
Выше у нас уровень IP и там происходит то же самое: необходимо удалить
IP-заголовки, дубликаты пакетов и "выпрямить" TCP-поток, а это снова создание
промежуточных буферов и снова копирование. Далее "эстафету" принимает
транспортный уровень и там опять наблюдается та же картина. В итоге имеем
кучу лишних операций. Это отрицательно сказывается на производительности,
повышает загрузку CPU и т.д. Вспомните, к примеру, загрузку файла через
торрент-протокол в несколько потоков...

И вот здесь на сцену выходит NET_BUFFER.

NET_BUFFER организует данные в виде набора буферов, соединенных в систему
связных списков. Сами данные никуда не копируются и не перемещаются, если
только в этом нет острой необходимости. Когда нужно удалить или переместить
часть данных, в NET_BUFFER просто двигаются соответствующие указатели.
То же самое происходит, если требуется дописать что-то "сбоку".
Вставить что-нибудь в середину данных ? Не проблема. Ну, может быть, придется
перевыделить и скопировать парочку буферов справа и слева, но это все равно
намного лучше, чем выделять один большой буфер и снова выполнять дорогие
операции копирования. При движении данных с уровня на уровень заголовки не
уничтожаются физически, просто указатели в NET_BUFFER сдвигаются так, чтобы
заголовков не было видно. В этой оптимизации и есть смысл NET_BUFFER:

Code
1
2
3
4
5
6
7
8
9
10
11
12
Ethernet
 
P1               P2                                P3               P4
[ETH IP TCP "ll"][ETH IP TCP "o, Worl" IP TCP "He"][ETH IP TCP "d!"][ETH IP TCP "ll"]
 
IP
     P1               P2               P3               P4               P5
[ETH IP TCP "ll"][ETH IP TCP "o, Worl" IP TCP "He"][ETH IP TCP "d!"][ETH IP TCP "ll"]
 
TCP
                             P3           P1               P4               P2
[ETH IP TCP "ll"][ETH IP TCP "o, Worl" IP TCP "He"][ETH IP TCP "d!"][ETH IP TCP "ll"]
Как вы, надеюсь, поняли, данные на всех трех уровнях одни и те же, различаются
только указатели на буферы (P1, P2 и т.д.) и их позиции.

Перед тем, как пойти дальше, следует познакомиться с еще одной структурой.
10
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
20.11.2014, 22:44  [ТС]
Часть 6-в, NET_BUFFER: Memory Descriptor List (MDL).

NET_BUFFER опирается на структуру MDL (Memory Descriptor List), вы должны
представлять себе, что это такое, хотя бы в общих чертах.

Я очень надеюсь, что вы знакомы с устройством виртуальной памяти и
адресного пространства в Windows, иначе дальнейшее чтение вряд ли
имеет смысл. Начать знакомство можно отсюда:

Преодолевая ограничения Windows: виртуальная память
http://blogs.technet.com/b/mar... 82311.aspx

Преодолевая ограничения Windows: физическая память
http://blogs.technet.com/b/mar... 51288.aspx

Преодолевая ограничения Windows: выгружаемый и невыгружаемый пулы
http://blogs.technet.com/b/mar... 36407.aspx

Virtual address spaces
http://msdn.microsoft.com/en-u... 85%29.aspx

About Memory Management
http://msdn.microsoft.com/en-u... 85%29.aspx

Memory Management for Windows Drivers
http://msdn.microsoft.com/en-u... 85%29.aspx

MDL описывает физические страницы памяти и соответствующие им виртуальные адреса.
По сути, это мощнейший инструмент для работы с памятью и разного рода "выкрутасов".
Например, можно сделать отображение одной и той же физической страницы памяти на
разные виртуальные адреса, можно с помощью отображения писать в память, доступную
только для чтения, можно "залочить" в памяти пользовательский буфер, находящийся в
user mode, и затем работать с ним в контексте любого потока и на любом IRQL...
MDL могут соединяться в цепочки (MDL chain). В общем, что говорить, лучше один
раз прочитать и несколько раз попробовать:

Master of the Obvious -- MDLs are Lists that Describe Memory
http://www.osronline.com/article.cfm?id=423

What Is Really in That MDL?
http://msdn.microsoft.com/en-u... 85%29.aspx

Using MDLs
http://msdn.microsoft.com/en-u... 85%29.aspx

Несколько типичных функций, так или иначе связанных с MDL:

IoAllocateMdl - создает новый MDL для указанного буфера.

MmBuildMdlForNonPagedPool - "заполняет" MDL.

MmProbeAndLockPages - блокирует буфер в памяти, предотвращая его возможную
выгрузку на диск (paging), также проверяет права доступа.

MmMapLockedPagesSpecifyCache - создает отображение физических страниц,
описанных MDL, на виртуальный адрес.

MmProtectMdlSystemAddress - позволяет изменять защиту страниц.

MmGetSystemAddressForMdlSafe - возвращает ядерный указатель на память,
которую описывает MDL. Если это еще не сделано, создает отображение
буфера на ядерные адреса.

MmGetMdlByteCount, MmGetMdlByteOffset, MmGetMdlVirtualAddress -
вспомогательные функции для получения различной информации из MDL.
10
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
20.11.2014, 22:48  [ТС]
Часть 6-г, NET_BUFFER: финал.

Ну а теперь мы подошли к тому, ради чего написана эта часть.

Итак, на двоичном уровне архитектура NET_BUFFER представляет собой

связный список объектов NET_BUFFER_LIST, каждый из которых может
иметь связный список объектов NET_BUFFER, каждый из которых может
иметь связный список MDL, каждый из которых описывает конкретный
буфер в памяти, включая размер полезной нагрузки и ее смещение от
начала буфера.


Не слабо, да ? Написанное выше лучше проиллюстрировать графически:

(картинка позаимствована с сайта CodeMachine, ссылка есть выше).

На самом деле, все не так страшно. Ну представьте себе какой-нибудь
std::list из C++, у которого все элементы - тоже списки, а элементы
этих списков - снова списки: "list<list<list<type> > > MyList;".
Мудрено ? Да, пожалуй. Сложно ? Вряд ли.

Чтобы прочесть все данные из NET_BUFFER_LIST, нужно написать что-то
типа цикла с тройной вложенностью: на верхнем уровне обойти все
NET_BUFFER_LIST, на среднем все NET_BUFFER каждого NET_BUFFER_LIST,
на нижнем все MDL для каждого NET_BUFFER каждого NET_BUFFER_LIST.
Используйте вспомогательные макросы NET_BUFFER_DATA_LENGTH,
NET_BUFFER_NEXT_NB, NET_BUFFER_CURRENT_MDL и другие NET_BUFFER_XXXX.

Вы также можете найти где-нибудь готовый код, но я крайне не советую
этого делать. Во-первых, чужой, найденный неизвестно где код - почти
всегда полный отстой. Со временем вы сами в этом убедитесь, и не раз.
Во-вторых, написать парсер NET_BUFFER_LIST - прекрасное упражнение
для ума, а написав и хорошенько протестировав его однажды, вы сможете
использовать этот код в любых проектах, связанных с WFP и NDIS 6+.

Добавлю, что моя версия полного разбора NET_BUFFER_LIST занимает
примерно 100 строк кода. Так что не отчаивайтесь, если что ;)

А "для ленивых" существует удобная и простая функция:

NdisGetDataBuffer function
http://msdn.microsoft.com/en-u... 85%29.aspx
Call the NdisGetDataBuffer function to gain access to a contiguous block of data from a NET_BUFFER structure.
Лично я предпочитаю разбирать данные вручную, так как это помогает избежать
лишних копирований.
15
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
20.11.2014, 22:48
Помогаю со студенческими работами здесь

Windows Filtering Platform и сниффинг пакетов в соединении типа Мост
Можно ли с помощью Windows Filtering Platform получать/модифицировать сетевые пакеты, когда компьютер работает в режиме Мост, соединяющий 2...

Windows Filtering Platform (WFP)
Добрый день. Читаю документацию. Общий смысл понятен. Но вот при попытке перейти к практике - потерпел фиаско. Есть примеры. Есть...

Windows Filtering Platform blocking ports and adresses on the c#
Предистория: Недавно появилась необходимость блокировать порты и адреса под windows. В основном предлагали использовать WFP (windows...

Блокировка прокси соединения Windows Filtering Platform (WFP)
Блокирую ВСЕ соединения FWPM_LAYER_ALE_AUTH_CONNECT_V4; FWPM_LAYER_ALE_AUTH_CONNECT_V6 но если установлен прокси или фаервол...

краткий обзор методов спектрального оценивания
У нас на форуме часто возникают задачи прямо или косвенно связанные со спектральным оцениванием. Решил сделать по этому поводу краткий...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
10
Закрытая тема Создать тему
Новые блоги и статьи
Модель микоризы: классовый агентный подход 3
anaschu 06.01.2026
aa0a7f55b50dd51c5ec569d2d10c54f6/ O1rJuneU_ls https:/ / vkvideo. ru/ video-115721503_456239114
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR
ФедосеевПавел 06.01.2026
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR ВВЕДЕНИЕ Введу сокращения: аналоговый ПИД — ПИД регулятор с управляющим выходом в виде числа в диапазоне от 0% до. . .
Модель микоризы: классовый агентный подход 2
anaschu 06.01.2026
репозиторий https:/ / github. com/ shumilovas/ fungi ветка по-частям. коммит Create переделка под биомассу. txt вход sc, но sm считается внутри мицелия. кстати, обьем тоже должен там считаться. . . .
Расчёт токов в цепи постоянного тока
igorrr37 05.01.2026
/ * Дана цепь постоянного тока с сопротивлениями и напряжениями. Надо найти токи в ветвях. Программа составляет систему уравнений по 1 и 2 законам Кирхгофа и решает её. Последовательность действий:. . .
Новый CodeBlocs. Версия 25.03
palva 04.01.2026
Оказывается, недавно вышла новая версия CodeBlocks за номером 25. 03. Когда-то давно я возился с только что вышедшей тогда версией 20. 03. С тех пор я давно снёс всё с компьютера и забыл. Теперь. . .
Модель микоризы: классовый агентный подход
anaschu 02.01.2026
Раньше это было два гриба и бактерия. Теперь три гриба, растение. И на уровне агентов добавится между грибами или бактериями взаимодействий. До того я пробовал подход через многомерные массивы,. . .
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru