Прошу прощения за избыточные посты. Незнакомый интерфейс форума.
Предыдущие два комментария модератор может удалить.
Вот несколько более полный текст в переводе на русский язык:
Добрый день. Я пытаюсь написать код режима ядра, который использует интерфейс TDI для приёма входящих TCP-соединений. И это не удаётся. Схематический код приведён ниже. Драйвер создает прослушивающий сокет на определённом порту. Попытка подключения с помощью Telnet в первый раз после запуска драйвера успешна — Irp->IoStatus.Status == STATUS_SUCCESS, но когда я пытаюсь отправить/получить данные через это соединение, я получаю STATUS_INVALID_DEVICE_STATE (0xC0000184). При этом Process Explorer показывает прослушивающий сокет и установленное TCP-соединение с этим же портом. Последующие попытки подключения дают Irp->IoStatus.Status == STATUS_CONNECTION_INVALID (0xC000023A). Почему — понять не могу. Может быть, я не понимаю самого механизма? Документация MSDN трудна для понимания. Примеров очень мало. Я был бы признателен за помощь. Возможно, TarasCo сможет дать комментарии?
//-------------------------------------------------------------------------------------
/* Прослушивающий сокет имеет два списка - список сокетов, которые ожидают подключения - Backlog, насколько я понимаю,
** и список сокетов, которые уже подключены. Доступ к спискам регулируется Queued-SpinLock --
** для того, чтобы оперировать списком было возможно при IRQL <= DISPATCH_LEVEL.
** Интерфейсная функция KernelSocketAccept ожидает событие SocketConnectedEvent и, когда подключенный сокет появляется
** в списке ConnectedSockets, изымает этот сокет из этого списка и возвращает указатель на него в выходном параметре.
*/
//Сокет режима ядра
typedef struct _KERNEL_SOCKET {
//Флаг, показывающий, что сокет связан с локальным адресом
unsigned_native_int Bound;
//Для TCP-сокета: флаг, показывающий, что соединение установлено
unsigned_native_int Connected;
//Для Listen-сокета: флаг, показывающий, что сокет переведён в режим прослушивания
unsigned_native_int Listening;
//Указатель на следующий сокет в односвязном списке сокетов, либо NULL, если этот сокет в списке последний
struct _KERNEL_SOCKET * NextSocket;
//Указатель на устройство транспортного драйвера - без увеличения счётчика ссылок
PDEVICE_OBJECT TransportDeviceObject;
//Указатель на имя устройства транспортного драйвера
wchar_t * TransportDeviceName;
//Файл подключения к устройству подлежащего транспортного драйвера - только для TCP-сокетов
struct _TransportDeviceFile{
//Описатель
HANDLE Handle;
//И указатель на объект
PFILE_OBJECT Object;
} TransportDeviceFile;
//Файл локального адреса, связанного с сокетом
struct _LocalAddressFile{
//Описатель
HANDLE Handle;
//И указатель на объект
PFILE_OBJECT Object;
} LocalAddressFile;
/* Список сокетов, ожидающих соединения, и список сокетов, для которых соединение установлено.
** Используются только в Listen-сокетах.
*/
KERNEL_SOCKET_LIST WaitingSockets;
KERNEL_SOCKET_LIST ConnectedSockets;
//Событие, сообщающее, что входящее соединение принято и соединённый сокет помещён в список ConnectedSockets - только для Listen-сокетов
KEVENT SocketConnectedEvent;
} KERNEL_SOCKET, *PKERNEL_SOCKET;
//-------------------------------------------------------------------------------------
//Контекстная структура, передаваемая с TDI_ACCEPT IRP
typedef struct _TDI_ACCEPT_IRP_CONTEXT{
//Указатель на Listen-сокет
PKERNEL_SOCKET ListeningSocket;
//Указатель на сокет, принимающий соединение
PKERNEL_SOCKET AcceptSocket;
TDI_CONNECTION_INFORMATION RequestConnectionInformation;
TA_IP_ADDRESS RequestRemoteAddressIPv4;
TA_IP6_ADDRESS RequestRemoteAddressIPv6;
} TDI_ACCEPT_IRP_CONTEXT, *PTDI_ACCEPT_IRP_CONTEXT;
//***********************************************************************************************************************************************************************************************************************************************************************
//TDI_ACCEPT IRP Completion Routine
IO_COMPLETION_ROUTINE TDIAcceptIRPCompletionRoutine;
static NTSTATUS TDIAcceptIRPCompletionRoutine(__in PDEVICE_OBJECT DeviceObject, __in PIRP Irp, __in PVOID Context){
/* В качестве указателя на контекст IRP передаётся указатель на блок памяти в нестраничном пуле,
** содержащий структуру TDI_ACCEPT_IRP_CONTEXT.
*/
PTDI_ACCEPT_IRP_CONTEXT TDIAcceptIRPContext = (PTDI_ACCEPT_IRP_CONTEXT)Context;
//Если указатель TDIAcceptIRPContext корректен
if(TDIAcceptIRPContext){
//Если корректны указатели ListeningSocket и AcceptSocket
if(TDIAcceptIRPContext->ListeningSocket && TDIAcceptIRPContext->AcceptSocket){
//Если соединение успешно установлено
if(Irp->IoStatus.Status == STATUS_SUCCESS){
//Устанавливаем соответствующий флаг в сокете
TDIAcceptIRPContext->AcceptSocket->Connected = true;
//Добавляем сокет в список соединенных сокетов прослушивающего сокета
AppendSocketToSocketList(&TDIAcceptIRPContext->ListeningSocket->ConnectedSockets, TDIAcceptIRPContext->AcceptSocket);
//Устанавливаем событие, сообщающее, что входящее соединение принято и соединённый сокет помещён в список ConnectedSockets
KeSetEvent(&TDIAcceptIRPContext->ListeningSocket->SocketConnectedEvent, IO_NO_INCREMENT, FALSE);
}
}
//Освобождаем память, содержащую контекстные данные, в нестраничном пуле
ExFreeToNPagedLookasideList(&KernelSocketsModule.DataBufferLookAsideList, TDIAcceptIRPContext);
}
IoFreeIrp(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
//***********************************************************************************************************************************************************************************************************************************************************************
//ClientEventConnect
static NTSTATUS TDIClientEventIncomingConnection(IN PVOID TdiEventContext, IN LONG RemoteAddressLength, IN PVOID RemoteAddress, IN LONG UserDataLength, IN PVOID UserData, IN LONG OptionsLength, IN PVOID Options, OUT CONNECTION_CONTEXT * ConnectionContext, OUT PIRP * AcceptIrp){
//Результат этой функции
NTSTATUS ThisFunctionResult = STATUS_CONNECTION_REFUSED;
*ConnectionContext = NULL;
*AcceptIrp = NULL;
//TdiEventContext - это указатель на KERNEL_SOCKET, для которого была установлена эта Callback-процедура
PKERNEL_SOCKET Socket = (PKERNEL_SOCKET)TdiEventContext;
//Если указатель RemoteAddress корректен
if(RemoteAddress){
//Если длина буфера, на который указывает RemoteAddress, корректна
if(RemoteAddressLength >= sizeof(TA_IP_ADDRESS)){
//Если корректен тип адреса, указанный в буфере
if(((PTA_IP_ADDRESS)RemoteAddress)->Address[0].AddressType == TDI_ADDRESS_TYPE_IP){
//Создаём IRP
PIRP AcceptConnectionIRP = IoAllocateIrp(Socket->TransportDeviceObject->StackSize + 1, FALSE);
//Если создать IRP удалось
if(AcceptConnectionIRP){
//Выделяем память для контекстной структуры в нестраничном пуле
PTDI_ACCEPT_IRP_CONTEXT TDIAcceptIRPContext = (PTDI_ACCEPT_IRP_CONTEXT)ExAllocateFromNPagedLookasideList(&KernelSocketsModule.DataBufferLookAsideList);
//Если выделить память удалось
if(TDIAcceptIRPContext){
//Инициализируем контекстную структуру
local_memset(TDIAcceptIRPContext, 0x00, sizeof(TDI_ACCEPT_IRP_CONTEXT));
//В структурах - один адрес
TDIAcceptIRPContext->RequestRemoteAddressIPv4.TAAddressCount = 1;
//Длина адресов
TDIAcceptIRPContext->RequestRemoteAddressIPv4.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
//Тип адресов
TDIAcceptIRPContext->RequestRemoteAddressIPv4.Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
//Адрес IPv4 - 4 байта
TDIAcceptIRPContext->RequestRemoteAddressIPv4.Address[0].Address[0].in_addr = ((PTA_IP_ADDRESS)RemoteAddress)->Address[0].Address[0].in_addr;
//Номер порта - 2 байта
TDIAcceptIRPContext->RequestRemoteAddressIPv4.Address[0].Address[0].sin_port = ((PTA_IP_ADDRESS)RemoteAddress)->Address[0].Address[0].sin_port;
//Указываем длину и адрес удалённого узла
TDIAcceptIRPContext->RequestConnectionInformation.RemoteAddressLength = sizeof(TA_IP_ADDRESS);
TDIAcceptIRPContext->RequestConnectionInformation.RemoteAddress = &TDIAcceptIRPContext->RequestRemoteAddressIPv4;
//Извлекаем сокет из списка сокетов, ожидающих подключения
PKERNEL_SOCKET SocketFromTheWaitingList = TakeSocketFromSocketList(&Socket->WaitingSockets);
//Если это удалось
if(SocketFromTheWaitingList){
//Вносим в контекстную структуру указатели на прослушивающий сокет и сокет, принимающий это соединение
TDIAcceptIRPContext->ListeningSocket = Socket;
TDIAcceptIRPContext->AcceptSocket = SocketFromTheWaitingList;
//Формируем IRP - TDI_ACCEPT
TdiBuildAccept(AcceptConnectionIRP, Socket->TransportDeviceObject, SocketFromTheWaitingList->TransportDeviceFile.Object, TDIAcceptIRPCompletionRoutine, TDIAcceptIRPContext, &TDIAcceptIRPContext->RequestConnectionInformation, NULL);
IoSetNextIrpStackLocation(AcceptConnectionIRP);
//Устанавливаем возвращаемое функцией значение, сообщающее, что соединение принимается, и выходные указатели
*ConnectionContext = (CONNECTION_CONTEXT)Socket;
*AcceptIrp = AcceptConnectionIRP;
ThisFunctionResult = STATUS_MORE_PROCESSING_REQUIRED;
}
}
}
}
}
}
/* Возвращаем результат этой функции. Один из следующего:
**
** STATUS_MORE_PROCESSING_REQUIRED - соединение принято, AcceptIrp предоставлен.
** STATUS_CONNECTION_REFUSED - соединение отклонено.
** STATUS_INSUFFICIENT_RESOURCES - недостаточно ресурсов, соединение будет отклонено.
*/
return ThisFunctionResult;
}
//-------------------------------------------------------------------------------------
//Интерфейсная функция модуля
NTSTATUS KernelSocketListen(__in PKERNEL_SOCKET Socket){
/* Создание некоторого числа сокетов для использования в ClientEventConnect - Backlog.
** Создание заключается в открытии соединения с транспортом драйвером - ZwCreateFile()
** и последующим TDI_ASSOCIATE_ADDRESS.
** Помещает эти сокеты в список, доступ к которому регулирует Queued-SpinLock -
** для того, чтобы оперировать списком было возможно при IRQL <= DISPATCH_LEVEL.
** Этот список привязывается к текущиму прослушивающему сокету - для которого вызывается эта функция.
** Все эти действия полностью успешны.
*/
/* Затем формируем IRP - TDI_SET_EVENT_HANDLER с EventType == TDI_EVENT_CONNECT и
** вызываем IoCallDriver(). В качестве PFILE_OBJECT FileObj передаём объект локального адреса,
** связанного с этим прослушивающим сокетом.
** Возвращает STATUS_SUCCESS.
*/
/* Кажется, в этой функции все работает правильно */
return ThisFunctionResult;
}
//-------------------------------------------------------------------------------------
//Интерфейсная функция модуля
NTSTATUS KernelSocketAccept(__in PKERNEL_SOCKET Socket, __out PKERNEL_SOCKET * AcceptedConnectionSocket){
//Результат этой функции
NTSTATUS ThisFunctionResult = STATUS_UNSUCCESSFUL;
/* Функция ожидает событие SocketConnectedEvent прослушивающего сокета, указатель на который передан.
** При возникновении этого события (signaled) изымает сокет из списка подключенных сокетов - ConnectedSockets.
** Если в списке сокетов больше нет - сбрасывает событие SocketConnectedEvent. Восполняет список сокетов
** ожидающих подключения - WaitingSockets - необходимым количеством сокетов. И возвращает указатель на
** подключенный сокет в выходном параметре AcceptedConnectionSocket.
*/
//Возвращаем результат этой функции
return ThisFunctionResult;
}
//-------------------------------------------------------------------------------------