Development/Windows

#5 MiniFilter - FilterGetMessage 비동기I/O

geunyeong 2021. 12. 9. 23:09

Table of Contents

    Abstract

    FilterGetMessage 비동기 I/O 관련 검색어로 내 블로그에 들어온 기록이 있길래 생각난 김에 정리했다. 본 글에서는 OVERLAPPED를 이용해 FilterGetMessage를 비동기식으로 받는 방법을 알아본다.

    Port

    유저 모드 어플리케이션에서 미니 필터로 포트 연결은 앞선 글과 깃허브 예제를 참고하길 바란다.

    • #3 MiniFilter - Communication Port (user → minifilter)
    https://geun-yeong.tistory.com/53
    • #4 MiniFilter - Communication Port (minifilter → user)
    https://geun-yeong.tistory.com/54

    Create an Overlapped

    일반 File I/O에서 사용하는 Overlapped과 사용법이 동일하다. 본 예제에서는 이벤트 이름을 할당했지만 필요 없다면 nullptr 값을 할당해도 된다.

    OVERLAPPED overlapped;   ZeroMemory(&overlapped, sizeof(overlapped));
    
    overlapped.hEvent = CreateEvent(NULL, 
                                    FALSE, 
                                    FALSE, 
                                    L"Local\\FilterGetMessage_IsSuccess");
    if (!overlapped.hEvent) {
        fprintf(stderr, "CreateEvent failed (error = %u)\n", GetLastError());
        return 1;
    }

    Call a FilterGetMessage with Overlapped

    Overlapped에 이벤트 핸들을 할당했으면 FilterGetMessage에 Overlapped 구조체를 넘겨주면서 호출한다. Overlapped를 이용한 FilterGetMessage 호출이 성공하면 HRESULT 값으로 0x800730E5가 반환된다. I/O 작업이 진행중이라는 에러 메시지인데, IS_ERROR로 검사하면 에러라고 내뱉으니 그냥 이 값을 따로 검사하는 게 좋다.

    // get message from minifilter with async io
    h_result = FilterGetMessage(port_handle, &recv.hdr, sizeof(recv), &overlapped);
    if (h_result != 0x800703e5 && IS_ERROR(h_result)) {
        sprintf_s(exception_msg, 
                  128,
                  "FilterGetMessage failed (HRESULT = 0x%x)", 
                  h_result);
        throw exception(exception_msg);
    }

    Wait an I/O operation to finish

    이제 I/O 작업이 완료될 때 까지 다른 작업을 진행하면 된다. 본 예제에서는 특별히 할 일이 없으므로 WaitForSingleObject로 1초마다 I/O 작업 완료 여부를 검사하고 기다린 횟수를 출력하도록 작성했다.

    UINT wait_count = 0;
    while (WaitForSingleObject(overlapped.hEvent, 1000 /* 1 second */) == WAIT_TIMEOUT) {
        printf("wait for %u times...\n", ++wait_count);
    }

    Do it

    비동기 I/O 작업이 완료되면 WaitForSingleObject는 WAIT_OBJECT_0 값을 반환한다. 본 예제에서는 WAIT_TIMEOUT이 아니면 반복문을 끝내도록 했기 때문에 정상적으로 I/O 작업이 끝났다면 반복문을 탈출할 것이다. 비동기 I/O 작업이 완료되면 하고싶은 작업을 하면 된다.

    DWORD transferred = 0;
    if (GetOverlappedResult(port_handle, &overlapped, &transferred, FALSE)) {
        printf("FilterGetMessage was finished!!! (%ws)\n", recv.data.path);
        
        // ...(후략)...

    Result

    본 예제에서는 미니 필터에서 파일을 감시하다가 파일 명이 test.txt면 파일 전체 경로를 유저 모드 어플리케이션으로 전송하도록 했다. FsRtlIsNameInExpression은 와일드카드 문자를 사용한 문자열 비교를 할 수 있는 함수다.

    RtlInitUnicodeString(&test_txt_pattern, L"*TEST.TXT");
    RtlInitUnicodeString(&file_path, name_info->Name.Buffer);
    		
    if (!FsRtlIsNameInExpression(&test_txt_pattern, &file_path, TRUE, NULL)) {
        return FLT_POSTOP_FINISHED_PROCESSING;
    }
    
    FLT_TO_USER sent;        RtlZeroMemory(&sent, sizeof(sent));
    FLT_TO_USER_REPLY reply; RtlZeroMemory(&reply, sizeof(reply));
    ULONG returned_bytes = 0;
    
    // send the file path to client
    // if client reply to block accessing the file,
    // the minifilter return STATUS_ACCESS_DENIED to filter manager
    wcscpy_s(sent.path, ARRAYSIZE(sent.path), name_info->Name.Buffer);
    status = MinifltPortSendMessage(&sent, sizeof(sent), &reply, sizeof(reply), &returned_bytes);
    if (NT_SUCCESS(status) && returned_bytes > 0 && reply.block) {
        callback_data->IoStatus.Status = STATUS_ACCESS_DENIED;
    }

    아래 사진은 본 예제 코드를 실행한 모습이다. 유저 모드 어플리케이션은 1초마다 wait for ~ 메시지를 출력하며 비동기 I/O 작업을 기다리고, 미니 필터는 test.txt 파일에 대한 접근 요청이 발생하면 이를 유저 모드 어플리케이션에 전달한다. 그 결과 유저 모드 어플리케이션은 비동기 I/O 작업을 정상적으로 완료하고 파일의 전체 경로를 올바르게 출력하는 걸 볼 수 있다.

    Close

    이상으로 비동기 Filter I/O 사용 방법에 대해서 살펴봤다. 사실 일반적인 File I/O와 차이가 없어 사용하는 데 큰 불편이나 어색함은 없을 것이다.

    Github

    https://github.com/geun-yeong/minifilter-example