Как реализовать отлложенную обработку IRP пакетов при приеме данных?
Я перехватываю callback ClientEventReceive, в нем проверяю данные, а затем вызываю оригинальный обработчик
ClientEventReceive, адрес которого сохранил при перехвате.
Мне нужно приостановить на некоторое время прием данных.
Ожидать мы не можем т.к. данный callback вызывается на DISPATH_LEVEL.
Мне советовали сделать так: сохранить где-нибудь Tsdu, в BytesTaken записать 0 и вернуть STATUS_DATA_NOT_ACCEPTED.
Когда же мне понадобится возобновить обработку приема — надо сформировать IRP пакет TDI_RECEIVE и вызвать IoCallDriver.
Да процесс передачи данных тормозится надежно, но вот возобновить передачу данных после такого приема никак не получается.
Как организовать отложенную обработку IRP при приеме?
Заранее благодарен за любые ответы, советы, ссылки...
Sorry, что так долго не отвечал — тестировал код приведенный в скинутой мне ссылочке (спасибо за ссылочку).
В общем с ClientEventReceiv'ом ничего путного у меня не получилось — перепробовал все что можно.
Но зато получилось с обработчиком ClientEventConnect (что возможно даже лучше):
NTSTATUS ClientEventConnect(PVOID EventContext,LONG AddressLength,PVOID Address,
LONG UserDataLength,PVOID UserData,LONG OptionsLength,PVOID Options,
PVOID *ConnectionContext,PIRP*AcceptIrp)
{
//...
Status= OriginalConnectHandler(EventContext,AddressLength,Address,UserDataLength,UserData,
OptionsLength,Options,ConnectionContext,AcceptIrp);
IoCopyCurrentIrpStackLocationToNext(*AcceptIrp);
IoSetCompletionRoutine(*AcceptIrp, CompletionConnect, 0, TRUE, TRUE, TRUE);
IoSetNextIrpStackLocation(*AcceptIrp);
//...return Status;
}
//==========================================================================================
//completion routine
NTSTATUS CompletionConnect( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context )
{
if (Irp->PendingReturned)
IoMarkIrpPending(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
После такого приема соединение с удаленным клиентом надежно тормозится (находится в состоянии ожидания) — то что мне и надо.
И теперь когда мне нужно разрешить соединение, я делаю
Irp->IoStatus.Status= STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT); //здесь Irp == *AcceptIrp в ClientEventConnect
И далее происходит нормальная передача данных.
Вопрос: как запретить соединение в данном случае?
Я пробовал
— отменить Irp
— вызывать обработчик ClientEventDisconnect
— и то и другое.
Ничего не происходит. Точнее, удаленный клиент тупо тормозит (Я тестирую на простом клиент-сервере TCP),
а TCP-сервер начинает беситься, т.е. ф-ция read, которой он читает из сокета становится почему-то неждущей и он
в бесконечном цикле выводит сообщения. В общем неважно. Результат один — отменить соединение не получается.
Как запретить (разорвать) соединение с удаленным клиентом, когда в ClientEventConnect мы сделали ему (соединению) как бы pending?
Здравствуйте, onyx2, Вы писали:
O>Sorry, что так долго не отвечал — тестировал код приведенный в скинутой мне ссылочке (спасибо за ссылочку). O>В общем с ClientEventReceiv'ом ничего путного у меня не получилось — перепробовал все что можно.
O>Но зато получилось с обработчиком ClientEventConnect (что возможно даже лучше):
O>
O>После такого приема соединение с удаленным клиентом надежно тормозится (находится в состоянии ожидания) — то что мне и надо.
O>И теперь когда мне нужно разрешить соединение, я делаю O>
O>Вопрос: как запретить соединение в данном случае?
O>Я пробовал O> — отменить Irp O> — вызывать обработчик ClientEventDisconnect O> — и то и другое.
O>Ничего не происходит. Точнее, удаленный клиент тупо тормозит (Я тестирую на простом клиент-сервере TCP), O>а TCP-сервер начинает беситься, т.е. ф-ция read, которой он читает из сокета становится почему-то неждущей и он O>в бесконечном цикле выводит сообщения. В общем неважно. Результат один — отменить соединение не получается.
O>Как запретить (разорвать) соединение с удаленным клиентом, когда в ClientEventConnect мы сделали ему (соединению) как бы pending?
А очевидное решение в голову не приходило?
Irp->>IoStatus.Status= STATUS_INSUFFICIENT_RESOURCES; //как вариант STATUS_UNSUCCESSFUL
IoCompleteRequest(Irp, IO_NO_INCREMENT);
Отменять IRP не имеете права — не вы его создали
вызывать обработчик ClientEventDisconnect — нет особого смысла....
Зато нужно бы вызвать TDI_DISCONNECT — так как с точки зрения tcpip соединение принято, нужно ему сказать о закрытии
Кроме того, хотелось бы заметиь о порочности подобного подхода. Для удаленного хоста — соединение принято, он начнет слать запросы и не получая ответов, сам прикроет соединение.
Спасибо за критику. Именно от TarasCo я этого и ждал.
TC>А очевидное решение в голову не приходило?
TC>
Irp->>>IoStatus.Status= STATUS_INSUFFICIENT_RESOURCES; //как вариант STATUS_UNSUCCESSFUL
TC> IoCompleteRequest(Irp, IO_NO_INCREMENT);
TC>
Вот это не работает.
TC>Отменять IRP не имеете права — не вы его создали TC>вызывать обработчик ClientEventDisconnect — нет особого смысла....
Правильное замечание, я это понял чуть раньше. Я устанавливал CancelRoutine, а кто-то (tcpip.sys) обнулял поле Irp->CancelRoutine.
В общем — все верно.
TC>Зато нужно бы вызвать TDI_DISCONNECT — так как с точки зрения tcpip соединение принято, нужно ему сказать о закрытии
Тоже верно.
Я сформировал вручную TDI_DISCONNECT IRP. И такой вариант работает (соединение надежно обрывается):
TC>Кроме того, хотелось бы заметиь о порочности подобного подхода. Для удаленного хоста — соединение принято, он начнет слать запросы и не получая ответов, сам прикроет соединение.
Все нормально — главное данные к серверу попадать не будут.
Здравствуйте, onyx2, Вы писали:
O>Спасибо за критику. Именно от TarasCo я этого и ждал.
TC>>А очевидное решение в голову не приходило?
TC>>
Irp->>>>IoStatus.Status= STATUS_INSUFFICIENT_RESOURCES; //как вариант STATUS_UNSUCCESSFUL
TC>> IoCompleteRequest(Irp, IO_NO_INCREMENT);
TC>>
O>Вот это не работает.
Это должно работать для клиента ( afd.sys например ). Он послал IRP и должен занть о его не легкой судьбе . netstat -a естественно продожит показывть наличие установленного соединения, поскольку драйверу то мы ничего не сказали, поэтому:
O>
TC>>Кроме того, хотелось бы заметиь о порочности подобного подхода. Для удаленного хоста — соединение принято, он начнет слать запросы и не получая ответов, сам прикроет соединение.
O>Все нормально — главное данные к серверу попадать не будут.
Если надеятся на устойчивость tcpip.sys — то нормально. А так — прямой путь к DoS, что для серверов не желательно . Представте — атака с 1000 хостов на веб сервер (для примера). Устанавливаем соединение и сразу 200 килогшрамм мусора туда. Это все на сервере жрет несвопабельную память ( tcpip честно пытается держать принятые данные), вызвает лишнее копирование памяти ( tcpip честно пытается возрщать буфера драйверу сетевой карты ).
TC>Если надеятся на устойчивость tcpip.sys — то нормально. А так — прямой путь к DoS, что для серверов не желательно . Представте — атака с 1000 хостов на веб сервер (для примера). Устанавливаем соединение и сразу 200 килогшрамм мусора туда. Это все на сервере жрет несвопабельную память ( tcpip честно пытается держать принятые данные), вызвает лишнее копирование памяти ( tcpip честно пытается возрщать буфера драйверу сетевой карты ).
Посоветуйте тогда, что можно предпринять, чтобы таких ситуаций не возникало.
Здравствуйте, onyx2, Вы писали:
TC>>Если надеятся на устойчивость tcpip.sys — то нормально. А так — прямой путь к DoS, что для серверов не желательно . Представте — атака с 1000 хостов на веб сервер (для примера). Устанавливаем соединение и сразу 200 килогшрамм мусора туда. Это все на сервере жрет несвопабельную память ( tcpip честно пытается держать принятые данные), вызвает лишнее копирование памяти ( tcpip честно пытается возрщать буфера драйверу сетевой карты ).
O>Посоветуйте тогда, что можно предпринять, чтобы таких ситуаций не возникало.
1)Переформулировать задачу. Допустим, отложить IRP, но не надолго — т.е не блокировать его на неопределенный срок, а обработать во вспомогательном потоке например. В таком случае, ничего страшного не произойдет — для сети несколько десятков мс — нестрашная задержка.
2)Если нужно таки блокировать ( для подтверждения оператором например ) — надежным способом будет переход на NDIS уровень. Принятый пакет нужно проанализировать. Если это SYN TCP пакет — скопровать его в собственный буфер ( лучше всего, выделить заранее набор буферов в pagable памяти и заранее же их залочить ) и поставить в очередь ( желательно, ограничить ее по длине и по времени пребывания, высшим классом будет еще и защита от флуда ). Сетевой карте говорим, что пакет успешно принят, tcpip.sys — ничего не говорим. При подтверждении приема соединения — собираем свой пакет и отдаем его для tcpip.sys. При подтверждении от него, ничего не говорим минипорту сетевой платы.
Здравствуйте, TarasCo, Вы писали:
TC>1)Переформулировать задачу. Допустим, отложить IRP, но не надолго — т.е не блокировать его на неопределенный срок, а обработать во вспомогательном потоке например. В таком случае, ничего страшного не произойдет — для сети несколько десятков мс — нестрашная задержка.
А вот это не верно — тоесть не верно в случае с Майкрософтом — теоретически все правильно, а практически AFD имеет один баг. В этом случае клиенты WinInet будут получать некорректный поток (тоесть там данные будут местами перепутаны).
Здравствуйте, Mike_MS, Вы писали:
M_M>Здравствуйте, TarasCo, Вы писали:
TC>>1)Переформулировать задачу. Допустим, отложить IRP, но не надолго — т.е не блокировать его на неопределенный срок, а обработать во вспомогательном потоке например. В таком случае, ничего страшного не произойдет — для сети несколько десятков мс — нестрашная задержка.
M_M>А вот это не верно — тоесть не верно в случае с Майкрософтом — теоретически все правильно, а практически AFD имеет один баг. В этом случае клиенты WinInet будут получать некорректный поток (тоесть там данные будут местами перепутаны).
Сразу скажу, не проверял, просто соображения на этот счет:
Возможно, это не баг, а фича. tcpip.sys не дагадывается о нашей подлости, и считает, что клиент принял соединение. Естественно, при получении данных он вызовет нотификатор ClientEventReceive. А подтверждение о приеме соединения придет от нашего фильтра позже. Возможно, в этот момент у afd.sys снесет крышу . Как метод борьбы с этим могу предложить попробывать:
1) устанавливать ClientEventReceive = NULL до момента пропуска IRP об установлении соединения. Правда, в этом случае tcpip.sys может будет просто скидывать данные. Я не встречал нигде описания, должен ли драйвер в таком случае кешировать данные. По идее — должен. Ведь драйвер может "подумать", что клиент вызовет TDI_RECEIVE. Тем более, логика работы TCP предусматривает наличие буфера для сборки потока данных.
2) устанавливать ClientEventReceive = NULL и вызывать самому TDI_RECEIVE, самому же организовывать очередь. Короче написать свой аналог afd.sys с кешем и.т.д. Но это сложно и трудоемко
M_M>>А вот это не верно — тоесть не верно в случае с Майкрософтом — теоретически все правильно, а практически AFD имеет один баг. В этом случае клиенты WinInet будут получать некорректный поток (тоесть там данные будут местами перепутаны).
TC>Сразу скажу, не проверял, просто соображения на этот счет:
TC>Возможно, это не баг, а фича. tcpip.sys не дагадывается о нашей подлости, и считает, что клиент принял соединение. Естественно, при получении данных он вызовет нотификатор ClientEventReceive. А подтверждение о приеме соединения придет от нашего фильтра позже. Возможно, в этот момент у afd.sys снесет крышу . Как метод борьбы с этим могу предложить попробывать: TC>1) устанавливать ClientEventReceive = NULL до момента пропуска IRP об установлении соединения. Правда, в этом случае tcpip.sys может будет просто скидывать данные. Я не встречал нигде описания, должен ли драйвер в таком случае кешировать данные. По идее — должен. Ведь драйвер может "подумать", что клиент вызовет TDI_RECEIVE. Тем более, логика работы TCP предусматривает наличие буфера для сборки потока данных. TC>2) устанавливать ClientEventReceive = NULL и вызывать самому TDI_RECEIVE, самому же организовывать очередь. Короче написать свой аналог afd.sys с кешем и.т.д. Но это сложно и трудоемко
Да нет, там причина совсем другая. Тут у нас 3 недели была ескалация с Майкрософт по поводу этого вопроса. AFD для оптимизации делает некоторые вещи, которые потом вылазят боком для WinInet. Буферизация — один из методов как этого избежать — но есть другой подводный камень — data flow control в этом случае — на соединении с большой скоростью можем получить "снежный ком".
Все тот же callback ClientEventReceive.
Нужен совет, как поступить.
Сейчас опишу ситуацию.
Реализация callback'а примерно следующая:
//=============================================================================
// TDI_EVENT_RECEIVE
//
NTSTATUS HookEventReceive(
IN PVOID TdiEventContext,
IN CONNECTION_CONTEXT ConnectionContext,
IN ULONG ReceiveFlags,
IN ULONG BytesIndicated,
IN ULONG BytesAvailable,
OUT ULONG *BytesTaken,
IN PVOID Tsdu,
OUT PIRP *IoRequestPacket
)
{
//...
//call original handler
ntStatus = EventReceive(EventReceiveContext,
ConnectionContext,
ReceiveFlags,
BytesIndicated,
BytesAvailable,
BytesTaken,
Tsdu,
IoRequestPacket);
if( ntStatus == STATUS_MORE_PROCESSING_REQUIRED && IoRequestPacket && *IoRequestPacket ) {
PIO_STACK_LOCATION IrpSp = NULL;
PSF_DEVICE_EXTENSION pDeviceExtension = pConnInfo->DeviceExtension;
//
// Handle TDI_RECEIVE request passdown
// -----------------------------------
// The caller has provided a TDI_RECEIVE request at IoRequestPacket
// to obtain the remaining Tdsu data.
//
// We want to filter processing of the request using our own completion handler.
// The key steps are:
//
// 1.) Copy the current IRP stack location to next.
// 2.) Set our completion routine.
// 3.) Emulate the effect of IoCallDriver in advancing the
// IRP stack location.
//
// Get the current I/O stack location.
IrpSp = IoGetCurrentIrpStackLocation( (*IoRequestPacket) );
IoCopyCurrentIrpStackLocationToNext( (*IoRequestPacket) );
// set completion routine
IoSetCompletionRoutine(
(*IoRequestPacket),
HookEventReceiveCompletion,
pDeviceExtension, //ВАЖНО: вот здесь я сохраняю в качестве контекста указатель на pDeviceExtension
TRUE,
TRUE,
TRUE
);
IoSetNextIrpStackLocation(*IoRequestPacket);
}
//error or restrictif (STATUS_DATA_NOT_ACCEPTED == ntStatus) {
if (BytesTaken) { *BytesTaken = 0; }
}
return ntStatus;
}
//=============================================================================
// HookEventReceive IoCompletion
//
//note:
//
NTSTATUS HookEventReceiveCompletion( PDEVICE_OBJECT DeviceObject, PIRP Irp, void *Context )
{
PSF_DEVICE_EXTENSION pDeviceExtension = NULL;
if ( !DeviceObject || !Irp || !Context ) { return STATUS_SUCCESS; }
pDeviceExtension = (PSF_DEVICE_EXTENSION)(Context);
// propagate the IRP pending flagif (Irp->PendingReturned) {
IoMarkIrpPending(Irp);
}
//pDeviceExtension->pFilterDeviceObject - это мое фильтрующее устройство приатаченное к \Device\Tcpif (pDeviceExtension->pFilterDeviceObject != DeviceObject) {
//вот сюда очень часто попадаю!!!
DBG_PRINT(DBG_MSG_ERROR, "invalid device object pointer!");
return STATUS_SUCCESS;
}
//...
//always return STATUS_SUCCESS - для того чтобы другие драйвера, подписавшие IoCompletion для данного Irp, тоже получили управлениеreturn STATUS_SUCCESS;
}
Почему мне приходят Irp запросы с не моим DeviceObject?
И вопрос: как поступать в такой ситуации?
вариант 1: сразу же сваливать из HookEventReceiveCompletion (как я делаю это сейчас)
вариант 2: обрабатывать далее данный Irp (в принципе вполне реально, информации из Irp мне вполне достаточно, но меня смущает не мой DeviceObject)
Т.е. меня интересует: это нормальная ситуация (когда приходят Irp запросы с не моим DeviceObject)? Или ошибочная (и нужно немедленно прекращать обработку Irp)?