Freigeben über


Dynamische Schlüsselwörter der Firewall

Sie verwenden die apIs für dynamische Schlüsselwörter der Firewall, um dynamische Schlüsselwortadressen in Microsoft Defender Firewallzu verwalten. Eine dynamische Schlüsselwortadresse wird verwendet, um eine Reihe von IP-Adressen zu erstellen, auf die eine oder mehrere Firewallregeln verweisen können. Dynamische Schlüsselwortadressen unterstützen sowohl IPv4 als auch IPv6.

Anmerkung

Api-Referenzinhalte für die in diesem Thema eingeführten APIs finden Sie unter Referenz zu dynamischen Schlüsselwörtern der Firewall.

Vorgänge für dynamische Schlüsselwortadressen

Mit den dynamischen Schlüsselwörtern-APIs der Firewall können Sie die folgenden Vorgänge ausführen.

  • Hinzufügen dynamischer Schlüsselwortadressen
  • Dynamische Schlüsselwortadressen löschen
  • Aufzählen dynamischer Schlüsselwortadressen nach ID oder Typ
  • Dynamische Schlüsselwortadressen aktualisieren
  • Abonnieren und Behandeln von dynamischen Adressänderungsbenachrichtigungen für dynamische Schlüsselwortadressen

Es gibt Codebeispiele für alle diese Vorgänge weiter unten in diesem Thema.

Nachdem Sie eine dynamische Schlüsselwortadresse hinzugefügt haben, wird sie über Neustarts hinweg beibehalten. Sie müssen eine dynamische Schlüsselwortadresse löschen, sobald Sie mit dem Objekt fertig sind.

Es gibt zwei Klassen dynamischer Schlüsselwortadressen, wie in den nächsten beiden Abschnitten beschrieben.

Dynamische Schlüsselwortadressen für AutoResolve

Der erste Typ ist AutoResolve, wobei das Schlüsselwort Feld einen aufgelösten Namen darstellt, und die IP-Adressen werden beim Erstellen nicht definiert.

Diese Objekte sollen ihre IP-Adressen automatisch aufgelöst haben. Das heißt, nicht durch einen Administrator zur Objekterstellungszeit; auch nicht über das Betriebssystem selbst. Eine Komponente außerhalb des Firewalldiensts muss die IP-Adressauflösung für diese Objekte ausführen und entsprechend aktualisieren. Die Implementierung einer solchen Komponente liegt außerhalb des Inhaltsbereichs.

Eine dynamische Schlüsselwortadresse wird als AutoResolve- angegeben, indem das FW_DYNAMIC_KEYWORD_ADDRESS_FLAGS_AUTO_RESOLVE Flag im Objekt festgelegt wird, wenn die FWAddDynamicKeywordAddress0-Funktion aufgerufen wird. Das Schlüsselwort Felds sollte verwendet werden, um den aufgelösten Wert darzustellen, d. h. einen vollqualifizierten Domänennamen (FQDN) oder hostname. Die adressen Felds müssen zunächst NULL für diese Objekte sein. Diese Objekte haben ihre IP-Adressen während der Startzyklen nicht beibehalten, und Sie sollten ihre Adressen während des nächsten Startzyklus erneut auswerten/erneut auffüllen.

Anmerkung

AutoResolve dynamic keyword address objects trigger notifications on FWAddDynamicKeywordAddress0 and FWDeleteDynamicKeywordAddress0, but not FWUpdateDynamicKeywordAddress0.

Dynamische Schlüsselwortadressen ohne AutoResolve

Der zweite Typ ist nicht-AutoResolve-, wobei das Schlüsselwort Feld eine beliebige Zeichenfolge ist und die Adressen zur Erstellungszeit definiert werden.

Diese Objekte werden verwendet, um eine Gruppe von IP-Adressen, Subnetzen oder Bereichen zu speichern. Das Schlüsselwort Feld hier wird zur Verwaltung verwendet, und es kann auf eine beliebige Zeichenfolge festgelegt werden. Die Adressen Felds müssen bei der Erstellung nicht NULL sein. Adressen für diese Objekte werden über Neustarts hinweg beibehalten.

Anmerkung

Non-AutoResolve dynamic keyword address objects trigger notifications on FWAddDynamicKeywordAddress0, FWDeleteDynamicKeywordAddress0, and also FWUpdateDynamicKeywordAddress0.

Weitere Informationen zu dynamischen Schlüsselwortadressen

Alle dynamischen Schlüsselwortadressen müssen über eine eindeutige GUID--ID verfügen, um sie darzustellen.

Die FwpmDynamicKeywordSubscribe0-API liefert Benachrichtigungen an einen Client, wenn sich dynamische Schlüsselwortadressen ändern. Es gibt keine Nutzlast, die an den Client übermittelt wird, der genau beschreibt, was sich auf dem System geändert hat. Wenn Sie wissen müssen, welche Objekte geändert wurden, sollten Sie den aktuellen Status von Objekten auf dem System mithilfe der FWEnumDynamicKeywordAddressById0 oder FWEnumDynamicKeywordAddressesByType0-APIs abfragen. Sie können die verschiedenen Flags verwenden, um Benachrichtigungen nur für eine Teilmenge von Objekten anzufordern. Wenn Sie keine Kennzeichnungen verwenden, werden Änderungsbenachrichtigungen für alle Objekte übermittelt.

Eine Firewallregel kann dynamische Schlüsselwortadressen verwenden, anstatt explizit IP-Adressen für die Remoteadressenbedingung zu definieren. Eine Firewallregel kann sowohl dynamische Schlüsselwortadressen als auch statisch definierte Remoteadressen verwenden. Ein einzelnes dynamisches Schlüsselwortadressenobjekt kann über mehrere Firewallregeln hinweg wiederverwendet werden. Wenn eine Firewallregel keine konfigurierten Remoteadressen aufweist (d. h., konfiguriert mit nur AutoResolve-Objekten, die noch nicht aufgelöst wurden), wird die Regel nicht erzwungen. Wenn eine Regel mehrere dynamische Schlüsselwortadressen verwendet, wird die Regel außerdem für alle Adressen erzwungen, die derzeit aufgelöst werden, auch wenn noch keine anderen Objekte vorhanden sind, die noch nicht aufgelöst wurden. Wenn eine dynamische Schlüsselwortadresse aktualisiert wird, werden auch alle zugehörigen Regelobjekte mit ihren Remoteadressen aktualisiert.

Das Betriebssystem selbst erzwingt keine Abhängigkeiten zwischen einer Regel und einer dynamischen Schlüsselwortadresse. Dies bedeutet, dass beide Objekte zuerst erstellt werden können – die Regel kann auf dynamische Schlüsselwortadressen-IDs verweisen, die noch nicht vorhanden sind (in diesem Fall wird die Regel nicht erzwungen). Darüber hinaus können Sie eine dynamische Schlüsselwortadresse löschen, auch wenn sie von einer Firewallregel verwendet wird. In diesem Thema wird beschrieben, wie ein Administrator Regeln für die Verwendung dynamischer Schlüsselwortadressen konfigurieren kann.

Codebeispiele

Um jedes dieser Codebeispiele auszuprobieren, starten Sie zuerst Visual Studio, und erstellen Sie ein neues Projekt basierend auf der Projektvorlage Konsolen-App. Sie können einfach den Inhalt von main.cpp durch die Codeauflistung ersetzen.

In den meisten Codebeispielen werden die Windows-Implementierungsbibliotheken (WIL) verwendet. Eine bequeme Möglichkeit zum Installieren von WIL besteht darin, zu Visual Studio zu wechseln, auf Project>NuGet-Pakete verwalten...>Durchsuchen, eingeben oder einfügen Microsoft.Windows.ImplementationLibrary im Suchfeld, wählen Sie das Element in den Suchergebnissen aus, und klicken Sie dann auf Installieren, um das Paket für dieses Projekt zu installieren.

Anmerkung

Zeigertypen für die kostenlosen NetFw-Funktionen werden über NetFw.hveröffentlicht, aber eine statische Linkbibliothek wird nicht veröffentlicht. Verwenden Sie das LoadLibraryExW/GetProcAddress Muster zum Aufrufen dieser Funktionen, wie in diesen Codebeispielen gezeigt.

Hinzufügen einer dynamischen Schlüsselwortadresse

In diesem Beispiel wird gezeigt, wie Sie die funktion FWAddDynamicKeywordAddress0 verwenden.

// main.cpp in a Console App project.
#include <windows.h>
#include <wil/resource.h>
#include <netfw.h>

// {26548e4f-d486-4a1d-8a1d-22b0837cd53b}
const GUID DYNAMIC_KEYWORD_ADDRESS_ID_1 =
{
    0x26548e4f,
    0xd486,
    0x4a1d,
    {0x8a,0x1d,0x22,0xb0,0x83,0x7c,0xd5,0x3b}
};

// {e9d5c993-9369-4a96-8228-9c5c37aac51a}
const GUID DYNAMIC_KEYWORD_ADDRESS_ID_2 =
{
    0xe9d5c993,
    0x9369,
    0x4a96,
    {0x82,0x28,0x9c,0x5c,0x37,0xaa,0xc5,0x1a}
};

int main()
{
    DWORD error = ERROR_SUCCESS;
    PFN_FWADDDYNAMICKEYWORDADDRESS0 addDynamicKeywordAddressFn = NULL;
    HMODULE moduleHandle = NULL;
    FW_DYNAMIC_KEYWORD_ADDRESS0 autoResolveKeywordAddress = { 0 };
    FW_DYNAMIC_KEYWORD_ADDRESS0 nonAutoResolveKeywordAddress = { 0 };

    // Use LoadLibrary/GetProcAddress to invoke this function
    moduleHandle = LoadLibraryExW(L"firewallapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
    auto onExitFreeModuleHandle = wil::scope_exit([&]
        {
            if (moduleHandle)
            {
                FreeLibrary(moduleHandle);
            }
        });

    if (moduleHandle != NULL)
    {
        addDynamicKeywordAddressFn = (PFN_FWADDDYNAMICKEYWORDADDRESS0)GetProcAddress(
            moduleHandle,
            "FWAddDynamicKeywordAddress0"
        );
    }

    if (addDynamicKeywordAddressFn == NULL)
    {
        error = GetLastError();
        return error;
    }

    // Ensure the ID is unique. If not, the add operation will fail with ERROR_ALREADY_EXISTS
    // and you should invoke the API with a new ID.

    // Initialize and add an auto-resolve dynamic keyword address
    autoResolveKeywordAddress.id = DYNAMIC_KEYWORD_ADDRESS_ID_1;
    autoResolveKeywordAddress.keyword = L"bing.com";
    autoResolveKeywordAddress.flags = FW_DYNAMIC_KEYWORD_ADDRESS_FLAGS_AUTO_RESOLVE;
    // must be NULL as we have set the auto resolve flag
    autoResolveKeywordAddress.addresses = NULL;

    error = addDynamicKeywordAddressFn(&autoResolveKeywordAddress);
    if (error != ERROR_SUCCESS)
    {
        return error;
    }

    // Initialize and add a non auto-resolve dynamic keyword address
    nonAutoResolveKeywordAddress.id = DYNAMIC_KEYWORD_ADDRESS_ID_2;
    nonAutoResolveKeywordAddress.keyword = L"myServerIPs";
    nonAutoResolveKeywordAddress.flags = 0;
    nonAutoResolveKeywordAddress.addresses = L"10.0.0.5,20.0.0.0/24,30.0.0.0-40.0.0.0";

    error = addDynamicKeywordAddressFn(&nonAutoResolveKeywordAddress);
    if (error != ERROR_SUCCESS)
    {
        return error;
    }
    return error;
}

Löschen einer dynamischen Schlüsselwortadresse

In diesem Beispiel wird gezeigt, wie Sie die funktion FWDeleteDynamicKeywordAddress0 verwenden.

// main.cpp in a Console App project.
#include <windows.h>
#include <wil/resource.h>
#include <netfw.h>

// {26548e4f-d486-4a1d-8a1d-22b0837cd53b}
const GUID DYNAMIC_KEYWORD_ADDRESS_ID_1 =
{
    0x26548e4f,
    0xd486,
    0x4a1d,
    {0x8a,0x1d,0x22,0xb0,0x83,0x7c,0xd5,0x3b}
};


// {e9d5c993-9369-4a96-8228-9c5c37aac51a}
const GUID DYNAMIC_KEYWORD_ADDRESS_ID_2 =
{
    0xe9d5c993,
    0x9369,
    0x4a96,
    {0x82,0x28,0x9c,0x5c,0x37,0xaa,0xc5,0x1a}
};

int main()
{
    DWORD error = ERROR_SUCCESS;
    PFN_FWDELETEDYNAMICKEYWORDADDRESS0 deleteDynamicKeywordAddressFn = NULL;
    HMODULE moduleHandle = NULL;

    // Use LoadLibrary/GetProcAddress to invoke this function
    moduleHandle = LoadLibraryExW(L"firewallapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
    auto onExitFreeModuleHandle = wil::scope_exit([&]
        {
            if (moduleHandle)
            {
                FreeLibrary(moduleHandle);
            }
        });


    if (moduleHandle != NULL)
    {
        deleteDynamicKeywordAddressFn = (PFN_FWDELETEDYNAMICKEYWORDADDRESS0)GetProcAddress(
            moduleHandle,
            "FWDeleteDynamicKeywordAddress0"
        );
    }

    if (deleteDynamicKeywordAddressFn == NULL)
    {
        error = GetLastError();
        return error;
    }

    // Invoke the functions
    error = deleteDynamicKeywordAddressFn(DYNAMIC_KEYWORD_ADDRESS_ID_1);
    if (error != ERROR_SUCCESS)
    {
        wprintf(L"Failed to delete object with ID 1, err=[%d]", error);
    }

    error = deleteDynamicKeywordAddressFn(DYNAMIC_KEYWORD_ADDRESS_ID_2);
    if (error != ERROR_SUCCESS)
    {
        wprintf(L"Failed to delete object with ID 2, err=[%d]", error);
    }

    return error;
}

Aufzählen und Freigeben dynamischer Schlüsselwortadressen nach ID

In diesem Beispiel wird gezeigt, wie Sie die funktionen FWEnumDynamicKeywordAddressById0 und FWFreeDynamicKeywordAddressData0 verwenden.

// main.cpp in a Console App project.
#include <windows.h>
#include <wil/resource.h>
#include <netfw.h>

// {26548e4f-d486-4a1d-8a1d-22b0837cd53b}
const GUID DYNAMIC_KEYWORD_ADDRESS_ID_1 =
{
    0x26548e4f,
    0xd486,
    0x4a1d,
    {0x8a,0x1d,0x22,0xb0,0x83,0x7c,0xd5,0x3b}
};

// {e9d5c993-9369-4a96-8228-9c5c37aac51a}
const GUID DYNAMIC_KEYWORD_ADDRESS_ID_2 =
{
    0xe9d5c993,
    0x9369,
    0x4a96,
    {0x82,0x28,0x9c,0x5c,0x37,0xaa,0xc5,0x1a}
};

int main()
{
    DWORD error = ERROR_SUCCESS;
    PFN_FWENUMDYNAMICKEYWORDADDRESSBYID0 enumDynamicKeywordAddressByIdFn = NULL;
    PFN_FWFREEDYNAMICKEYWORDADDRESSDATA0 freeDynamicKeywordAddressDataFn = NULL;
    HMODULE moduleHandle = NULL;
    PFW_DYNAMIC_KEYWORD_ADDRESS_DATA0 dynamicKeywordAddressData = NULL;

    // Use LoadLibrary/GetProcAddress to invoke this function
    moduleHandle = LoadLibraryExW(L"firewallapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
    auto onExitFreeModuleHandle = wil::scope_exit([&]
        {
            if (moduleHandle)
            {
                FreeLibrary(moduleHandle);
            }
        });

    if (moduleHandle != NULL)
    {
        enumDynamicKeywordAddressByIdFn = (PFN_FWENUMDYNAMICKEYWORDADDRESSBYID0)GetProcAddress(
            moduleHandle,
            "FWEnumDynamicKeywordAddressById0"
        );
        freeDynamicKeywordAddressDataFn = (PFN_FWFREEDYNAMICKEYWORDADDRESSDATA0)GetProcAddress(
            moduleHandle,
            "FWFreeDynamicKeywordAddressData0"
        );
    }

    if (enumDynamicKeywordAddressByIdFn == NULL ||
        freeDynamicKeywordAddressDataFn == NULL)
    {
        error = GetLastError();
        return error;
    }

    error = enumDynamicKeywordAddressByIdFn(
        DYNAMIC_KEYWORD_ADDRESS_ID_1,
        &dynamicKeywordAddressData
    );
    if (error != ERROR_SUCCESS)
    {
        return error;
    }

    if (dynamicKeywordAddressData != NULL)
    {
        // Process this dynamic keyword address
    }

    // Free the dynamic keyword address
    freeDynamicKeywordAddressDataFn(dynamicKeywordAddressData);
    return error;
}

Aufzählen und freie dynamische Schlüsselwortadressen nach Typ

In diesem Beispiel wird gezeigt, wie Sie die funktionen FWEnumDynamicKeywordAddressesByType0 und FWFreeDynamicKeywordAddressData0 verwenden.

// main.cpp in a Console App project.
#include <windows.h>
#include <wil/resource.h>
#include <netfw.h>

int main()
{
    DWORD error = ERROR_SUCCESS;
    PFN_FWENUMDYNAMICKEYWORDADDRESSESBYTYPE0 enumDynamicKeywordAddressesByTypeFn = NULL;
    PFN_FWFREEDYNAMICKEYWORDADDRESSDATA0 freeDynamicKeywordAddressDataFn = NULL;
    HMODULE moduleHandle = NULL;

    PFW_DYNAMIC_KEYWORD_ADDRESS_DATA0 dynamicKeywordAddressData = NULL;
    PFW_DYNAMIC_KEYWORD_ADDRESS_DATA0 currDynamicKeywordAddressData = NULL;

    // Use LoadLibrary/GetProcAddress to invoke this function
    moduleHandle = LoadLibraryExW(L"firewallapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
    auto onExitFreeModuleHandle = wil::scope_exit([&]
        {
            if (moduleHandle)
            {
                FreeLibrary(moduleHandle);
            }
        });

    if (moduleHandle != NULL)
    {
        enumDynamicKeywordAddressesByTypeFn = (PFN_FWENUMDYNAMICKEYWORDADDRESSESBYTYPE0)GetProcAddress(
            moduleHandle,
            "FWEnumDynamicKeywordAddressesByType0"
        );
        freeDynamicKeywordAddressDataFn = (PFN_FWFREEDYNAMICKEYWORDADDRESSDATA0)GetProcAddress(
            moduleHandle,
            "FWFreeDynamicKeywordAddressData0"
        );
    }

    if (enumDynamicKeywordAddressesByTypeFn == NULL ||
        freeDynamicKeywordAddressDataFn == NULL)
    {
        error = GetLastError();
        return error;
    }

    // Invoke enum for ALL dynamic keyword addresses
    error = enumDynamicKeywordAddressesByTypeFn(
        FW_DYNAMIC_KEYWORD_ADDRESS_ENUM_FLAGS_ALL,
        &dynamicKeywordAddressData
    );
    if (error != ERROR_SUCCESS)
    {
        return error;
    }

    currDynamicKeywordAddressData = dynamicKeywordAddressData;
    while (currDynamicKeywordAddressData != NULL)
    {
        // Process this dynamic keyword address

        // iterate to the next one in the list
        currDynamicKeywordAddressData = currDynamicKeywordAddressData->next;
    }

    // Free the dynamic keyword addresses
    freeDynamicKeywordAddressDataFn(dynamicKeywordAddressData);

    return error;
}

Dynamische Schlüsselwortadressen aktualisieren

In diesem Beispiel wird gezeigt, wie Sie die FWUpdateDynamicKeywordAddress0-Funktion verwenden.

// main.cpp in a Console App project.
#include <windows.h>
#include <wil/resource.h>
#include <netfw.h>

// {26548e4f-d486-4a1d-8a1d-22b0837cd53b}
const GUID DYNAMIC_KEYWORD_ADDRESS_ID_1 =
{
    0x26548e4f,
    0xd486,
    0x4a1d,
    {0x8a,0x1d,0x22,0xb0,0x83,0x7c,0xd5,0x3b}
};

int main()
{
    DWORD error = ERROR_SUCCESS;
    PFN_FWUPDATEDYNAMICKEYWORDADDRESS0 updateDynamicKeywordAddressFn = NULL;
    HMODULE moduleHandle = NULL;
    BOOL appendToCurrentAddresses = TRUE;

    // Use LoadLibrary/GetProcAddress to invoke this function
    moduleHandle = LoadLibraryExW(L"firewallapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
    auto onExitFreeModuleHandle = wil::scope_exit([&]
        {
            if (moduleHandle)
            {
                FreeLibrary(moduleHandle);
            }
        });

    if (moduleHandle != NULL)
    {
        updateDynamicKeywordAddressFn = (PFN_FWUPDATEDYNAMICKEYWORDADDRESS0)GetProcAddress(
            moduleHandle,
            "FWUpdateDynamicKeywordAddress0"
        );
    }

    if (updateDynamicKeywordAddressFn == NULL)
    {
        error = GetLastError();
        return error;
    }

    // Invoke the function
    error = updateDynamicKeywordAddressFn(
        DYNAMIC_KEYWORD_ADDRESS_ID_1,
        L"20.0.0.5",
        appendToCurrentAddresses);
    return error;
}

Abonnieren und Behandeln von dynamischen Adressänderungsbenachrichtigungen für dynamische Schlüsselwortadressen

In diesem Beispiel wird gezeigt, wie Sie die FwpmDynamicKeywordSubscribe0 und FwpmDynamicKeywordUnsubscribe0-Funktionen und den FWPM_DYNAMIC_KEYWORD_CALLBACK0 Rückruf verwenden.

// main.cpp in a Console App project.
#include <windows.h>
#include <netfw.h>
#include <fwpmu.h>
#pragma comment(lib, "Fwpuclnt")

void CALLBACK TestCallback(_Inout_ VOID* /*pNotification*/, _Inout_ VOID* pContext)
{
    DWORD error = ERROR_SUCCESS;
    PFN_FWENUMDYNAMICKEYWORDADDRESSESBYTYPE0 enumDynamicKeywordAddressesByTypeFn = NULL;
    PFN_FWFREEDYNAMICKEYWORDADDRESSDATA0 freeDynamicKeywordAddressDataFn = NULL;
    HMODULE moduleHandle = NULL;

    PFW_DYNAMIC_KEYWORD_ADDRESS_DATA0 dynamicKeywordAddressData = NULL;
    PFW_DYNAMIC_KEYWORD_ADDRESS_DATA0 currDynamicKeywordAddressData = NULL;
    HANDLE* waitHandle = (HANDLE*)pContext;

    // Use LoadLibrary/GetProcAddress to invoke this function
    moduleHandle = LoadLibraryW(L"firewallapi.dll");
    if (moduleHandle != NULL)
    {
        enumDynamicKeywordAddressesByTypeFn = (PFN_FWENUMDYNAMICKEYWORDADDRESSESBYTYPE0)GetProcAddress(
            moduleHandle,
            "FWEnumDynamicKeywordAddressesByType0"
        );
        freeDynamicKeywordAddressDataFn = (PFN_FWFREEDYNAMICKEYWORDADDRESSDATA0)GetProcAddress(
            moduleHandle,
            "FWFreeDynamicKeywordAddressData0"
        );
    }

    if (enumDynamicKeywordAddressesByTypeFn == NULL ||
        freeDynamicKeywordAddressDataFn == NULL)
    {
        return;
    }

    // Invoke enum for ALL AutoResolve dynamic keyword addresses
    error = enumDynamicKeywordAddressesByTypeFn(
        FW_DYNAMIC_KEYWORD_ADDRESS_ENUM_FLAGS_AUTO_RESOLVE,
        &dynamicKeywordAddressData
    );
    if (error != ERROR_SUCCESS)
    {
        return;
    }

    currDynamicKeywordAddressData = dynamicKeywordAddressData;
    while (currDynamicKeywordAddressData != NULL)
    {
        // Process this dynamic keyword address

        currDynamicKeywordAddressData = currDynamicKeywordAddressData->next;
    }

    // Free the dynamic keyword addresses
    freeDynamicKeywordAddressDataFn(dynamicKeywordAddressData);

    SetEvent(*waitHandle);
}

int main()
{
    DWORD error = ERROR_SUCCESS;
    HANDLE notifyHandle;
    HANDLE waitHandle;

    waitHandle = CreateEventW(
        NULL,
        TRUE,
        FALSE,
        L"subscriptionWaitEvent"
    );


    // Subscribe for change notifications
    error = FwpmDynamicKeywordSubscribe0(
        FWPM_NOTIFY_ADDRESSES_AUTO_RESOLVE,
        TestCallback,
        &waitHandle,
        &notifyHandle);
    if (error != ERROR_SUCCESS)
    {
        return error;
    }

    WaitForSingleObject(waitHandle, INFINITE);

    // When client is ready to unsubscribe
    error = FwpmDynamicKeywordUnsubscribe0(notifyHandle);

    return error;
}