WinDivert 1.0: Windows Packet Divert

Table of Contents


1. Introduction

WinDivert is a user-mode capture/sniffing/modification/blocking/re-injection package for Windows Vista, Windows Server 2008, and Windows 7. WinDivert can be used to implement user-mode packet filters, packet sniffers, firewalls, NAT, VPNs, tunneling applications, etc., without the need to write kernel-level code.

The main features of the WinDivert are:

WinDivert provides similar functionality to divert sockets in FreeBSD/MacOS, NETLINK sockets in Linux, and some commercial packages such as WinPkFilter for Windows. WinDivert also supports passive packet sniffing similar to Winpcap.


2. Building

The source code for WinDivert is available for download at

https://github.com/basil00/Divert
To build the WinDivert package from source:
  1. Download and install Windows Driver Kit 7.1.0.
  2. Open a Free Build Environment console (or Checked Build Environment for debugging).
  3. In the WinDivert package root directory, run the command:
    wddk-build.bat
    
    This will build the following files and place them in the install\WDDK subdirectory:
NOTE: The WinDivert.dll and WinDivert.lib files are only compatible with programs compiled with the WDDK compiler. See below for Visual Studio 2012 and MinGW support.

2.1 Driver Signing

Before the WinDivert package can be used, the WinDivert.sys driver must contain a valid digital signature. This is Microsoft policy for all kernel drivers in recent versions of Windows. See Driver Signing Requirements for Windows for more information.

2.2 Visual Studio 2012 Support

To build the WinDivert package for Visual Studio 2012:

  1. First build the driver by running wddk-build.bat as per the instructions above.
  2. Open a Visual Studio Command Prompt environment.
  3. In the WinDivert package root directory, run the command:
    msvc-build.bat
    
    This will build Visual Studio 2012 compatible files and place them in the install\MSVC subdirectory.

2.3 MinGW Support

To build the WinDivert package for MinGW:

  1. First build the driver by running wddk-build.bat as per the instructions above.
  2. In Linux (with the MinGW cross-compilers installed) and in the WinDivert package root directory, run the command:
    sh mingw-build.sh
    
    This will build MinGW compatible files and place them in the install\MINGW subdirectory.


3. Installing

WinDivert does not require any special installation. Simply place the WinDivert.dll, WinDivert.sys, WinDivert.inf, and WdfCoInstaller*.dll files are in your application's home directory.

The WinDivert driver is installed on demand, i.e., when your application makes a call to DivertOpen() from WinDivert.dll. The driver is installed silently. The calling application must be running with Administrator privileges.


4. Uninstalling

To uninstall, simply delete the WinDivert.dll, WinDivert.sys, WinDivert.inf, and WdfCoInstaller*.dll files. The WinDivert driver is silently uninstalled when the calling application terminates or unloads the WinDivert.dll library. The WinDivert driver can also be removed manually by issuing the following commands at the command prompt

sc stop WinDivert1.0
sc delete WinDivert1.0


5. Programming API

To use the WinDivert package, a program/application must:

  1. Include the divert.h header file
    #include "divert.h"
    
  2. Link or dynamically load the WinDivert.dll dynamic link library.

5.1 DIVERT_ADDRESS

typedef struct
{
    UINT32 IfIdx;
    UINT32 SubIfIdx;
    UINT8  Direction;
} DIVERT_ADDRESS, *PDIVERT_ADDRESS;

Fields

Remarks
The DIVERT_ADDRESS structure represents the "address" of a captured or injected packet. The address includes the packet's network interfaces and the packet direction.

5.2 DivertOpen

HANDLE DivertOpen(
    __in const char *filter,
    __in DIVERT_LAYER layer,
    __in INT16 priority,
    __in UINT64 flags
);

Parameters

Return Value
A valid WinDivert handle on success, or INVALID_HANDLE_VALUE if an error occurred. Use GetLastError() to get the reason for the error. Common errors include:

Name Code Description
ERROR_INVALID_PARAMETER 87 This indicates an invalid packet filter string, layer, priority, or flags.
ERROR_FILE_NOT_FOUND 2 One or more of the WinDivert.sys, WinDivert.inf, and WdfCoInstaller*.dll files were not found.
ERROR_ACCESS_DENIED 5 The calling application does not have Administrator privileges.
ERROR_INVALID_IMAGE_HASH 577 The WinDivert.sys driver does not have a valid digital signature (see the driver signing requirements above), or WinDivert.sys is the wrong version, i.e. 32-bit on 64-bit Windows or vice versa.

Remarks
Opens a WinDivert handle for the given filter. Unless otherwise specified by flags, any packet that matches the filter will be diverted to the handle. Diverted packets can be read by the application with DivertRecv().

A typical application is only interested in a subset of all network traffic. In this case the filter should match as closely as possible to the subset of interest. This avoids unnecessary overheads introduced by diverting packets to the user-mode application. See the filter language section for more information.

The layer of the WinDivert handle is determined by the layer parameter. Currently the following layers are supported.

Layer Description
DIVERT_LAYER_NETWORK = 0 The network layer. This is the default.
DIVERT_LAYER_NETWORK_FORWARD The network layer (forwarded packets).

Different WinDivert handles can be assigned different priorities by the priority parameter. Packets are diverted to higher priority handles before lower priority handles. Packets injected by a handle are then diverted to the next priority handle, and so on, provided the packet matches the handle's filter. A packet is only diverted once per priority level, so handles should not share priority levels unless they use mutually exclusive filters. Otherwise it is not defined which handle will receive the packet first. Lower priority values represent higher priorities, with -1000 being the highest priority, 0 the middle (and a good default) priority, and 1000 the lowest priority.

The following flags are supported.

Flag Description
DIVERT_FLAG_SNIFF This flag opens the WinDivert handle in packet sniffing mode. In packet sniffing mode the original packet is not dropped-and-diverted (the default) but copied-and-diverted. This mode is useful for implementing packet sniffing tools similar to those applications that currently use Winpcap.
DIVERT_FLAG_DROP This flag indicates that the user application does not intend to read matching packets with DivertRecv(), instead the packets should be silently dropped. This is useful for implementing simple packet filters using the WinDivert filter language.
If both DIVERT_FLAG_SNIFF and DIVERT_FLAG_DROP flags are set, the WinDivert handle will be in "passthru" mode that neither captures nor drops any packet. Such a handle is useful for applications that only require packet injection.

5.3 DivertRecv

BOOL DivertRecv(
    __in HANDLE handle,
    __out PVOID pPacket,
    __in UINT packetLen,
    __out_opt PDIVERT_ADDRESS pAddr,
    __out_opt UINT *recvLen
);

Parameters

Return Value
TRUE if a packet was successfully received, or FALSE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Receives a diverted packet that matched the filter passed to DivertOpen(). The received packet is guaranteed to match the filter.

The contents of the captured packet are written to pPacket. If the captured packet is larger than the pPacket buffer length, then the packet will be truncated. If recvLen is non-NULL, then the total number of bytes written to pPacket is placed there. If non-NULL, the address of the captured packet is written to pAddr.

An application should call DivertRecv() as soon as possible after a successful call to DivertOpen(). When a WinDivert handle is open, any packet that matches the filter will be captured and queued until handled by DivertRecv(). Packets are not queued indefinitely, and if not handled in a timely manner, any captured packet may be dropped. The amount of time a packet is queued can be controlled with the DivertSetParam() function.

DivertRecv() should not be used on any WinDivert handle created with the DIVERT_FLAG_DROP set.

5.4 DivertSend

BOOL DivertSend(
    __in HANDLE handle,
    __in PVOID pPacket,
    __in UINT packetLen,
    __in PDIVERT_ADDRESS pAddr,
    __out_opt UINT *sendLen
);

Parameters

Return Value
TRUE if a packet was successfully injected, or FALSE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Injects a packet into the network stack. The injected packet may be one received from DivertRecv(), or a modified version, or a completely new packet. Injected packets can be captured and diverted again by other WinDivert handles with lower priorities.

The pAddr parameter determines how the packet is injected. If the Direction field is DIVERT_DIRECTION_OUTBOUND, the packet is injected into the outbound path (i.e. a packet leaving this computer). Else, if Direction is DIVERT_DIRECTION_INBOUND, the packet is injected into the inbound path (i.e. a packet arriving at this computer). Note that the Direction field, and not the IP addresses in the injected packet, is used to determine the packet's direction.

For packets injected into the inbound path, the IfIdx and SubIfIdx fields are assumed to contain valid interface numbers. These may be retrieved from DivertRecv() (for packet modification), or from the IP Helper API.

For outbound injected packets, the IfIdx and SubIfIdx fields are currently ignored and may be arbitrary values. Injecting an inbound packet on the outbound path may work (for some types of packets), however this should be considered "undocumented" behavior, and may be changed in the future.

5.5 DivertClose

BOOL DivertClose(
    __in HANDLE handle
);

Parameters

Return Value
TRUE if successful, FALSE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Closes a WinDivert handle created by DivertOpen().

5.6 DivertSetParam

BOOL DivertSetParam(
    __in HANDLE handle,
    __in DIVERT_PARAM param,
    __in UINT64 value);

Parameters

Return Value
TRUE if successful, FALSE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Sets a WinDivert parameter. Currently, the following WinDivert parameters are defined.

Parameter Description
DIVERT_PARAM_QUEUE_LEN Sets the maximum length of the packet queue for DivertRecv(). Currently the default value is 512, the minimum is 1, and the maximum is 8192.
DIVERT_PARAM_QUEUE_TIME Sets the minimum time, in milliseconds, a packet can be queued before it is automatically dropped. Packets cannot be queued indefinitely, and ideally, packets should be processed by the application as soon as is possible. Note that this sets the minimum time a packet can be queued before it can be dropped. The actual time may be exceed this value. Currently the default value is 256, the minimum is 32, and the maximum is 1024.

5.7 DivertGetParam

BOOL DivertGetParam(
    __in HANDLE handle,
    __in DIVERT_PARAM param,
    __out UINT64 *pValue);

Parameters

Return Value
TRUE if successful, FALSE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Gets a WinDivert parameter. See DivertSetParam() for the list of parameters.


6. Helper Programming API

The WinDivert helper programming API is a collection of definitions and functions designed to make writing WinDivert applications easier. The use of the helper API is completely optional.

6.1 DIVERT_IPHDR

typedef struct
{
    UINT8  HdrLength:4;
    UINT8  Version:4;
    UINT8  TOS;
    UINT16 Length;
    UINT16 Id;
    UINT16 ...;
    UINT8  TTL;
    UINT8  Protocol;
    UINT16 Checksum;
    UINT32 SrcAddr;
    UINT32 DstAddr;
} DIVERT_IPHDR, *PDIVERT_IPHDR;

Fields
See
here for more information.

Remarks
IPv4 header definition.

The following fields can only be get/set using the following macro definitions:

6.2 DIVERT_IPV6HDR

typedef struct
{
    UINT32 Version:4;
    UINT32 ...:28;
    UINT16 Length;
    UINT8  NextHdr;
    UINT8  HopLimit;
    UINT32 SrcAddr[4];
    UINT32 DstAddr[4];
} DIVERT_IPV6HDR, *PDIVERT_IPV6HDR;
Fields
See here for more information.

Remarks
IPv6 header definition.

The following fields can only be get/set using the following macro definitions:

6.3 DIVERT_ICMPHDR

typedef struct
{
    UINT8  Type;
    UINT8  Code;
    UINT16 Checksum;
    UINT32 Body;
} DIVERT_ICMPHDR, *PDIVERT_ICMPHDR;
Fields
See here for more information.

Remarks
ICMP header definition.

6.4 DIVERT_ICMPV6HDR

typedef struct
{
    UINT8  Type;
    UINT8  Code;
    UINT16 Checksum;
    UINT32 Body;
} DIVERT_ICMPV6HDR, *PDIVERT_ICMPV6HDR;
Fields
See here for more information.

Remarks
ICMPv6 header definition.

6.5 DIVERT_TCPHDR

typedef struct
{
    UINT16 SrcPort;
    UINT16 DstPort;
    UINT32 SeqNum;
    UINT32 AckNum;
    UINT16 Reserved1:4;
    UINT16 HdrLength:4;
    UINT16 Fin:1;
    UINT16 Syn:1;
    UINT16 Rst:1;
    UINT16 Psh:1;
    UINT16 Ack:1;
    UINT16 Urg:1;
    UINT16 Reserved2:2;
    UINT16 Window;
    UINT16 Checksum;
    UINT16 UrgPtr;
} DIVERT_TCPHDR, *PDIVERT_TCPHDR;
Fields
See here for more information.

Remarks
TCP header definition.

6.6 DIVERT_UDPHDR

typedef struct
{
    UINT16 SrcPort;
    UINT16 DstPort;
    UINT16 Length;
    UINT16 Checksum;
} DIVERT_UDPHDR, *PDIVERT_UDPHDR;
Fields
See here for more information.

Remarks
UDP header definition.

6.7 DivertHelperParsePacket

BOOL DivertHelperParsePacket(
    __in PVOID pPacket,
    __in UINT packetLen,
    __out_opt PDIVERT_IPHDR *ppIpHdr,
    __out_opt PDIVERT_IPV6HDR *ppIpv6Hdr,
    __out_opt PDIVERT_ICMPHDR *ppIcmpHdr,
    __out_opt PDIVERT_ICMPV6HDR *ppIcmpv6Hdr,
    __out_opt PDIVERT_TCPHDR *ppTcpHdr,
    __out_opt PDIVERT_UDPHDR *ppUdpHdr,
    __out_opt PVOID *ppData,
    __out_opt UINT *pDataLen
);

Parameters

Return Value
TRUE if all expected (non-NULL) outputs were present, FALSE otherwise. Note that FALSE may sometimes be a legitimate return value, e.g., when both ppIpHdr and ppIpv6Hdr are non-NULL.

Remarks
Parses a raw packet (e.g. from DivertRecv()) into the various packet headers and/or payloads that may or may not be present.

Each output parameter may be NULL or non-NULL. For non-NULL parameters, this function will write the pointer to the corresponding header/payload if it exists, or will write NULL otherwise. Any non-NULL pointer that is returned

  1. Is a pointer into the original pPacket packet; and
  2. There is enough space in pPacket to fit the header.

This function does not do any verification of the header/payload contents beyond checking the header length and any other minimal information required for parsing.

6.8 DivertHelperParseIPv4Address

BOOL DivertHelperParseIPv4Address(
    __in const char *addrStr,
    __out_opt UINT32 *pAddr
);

Parameters

Return Value
TRUE if successful, FALSE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Parses an IPv4 address stored in addrStr. If non-NULL, the result is stored in pAddr.

6.9 DivertHelperParseIPv6Address

BOOL DivertHelperParseIPv6Address(
    __in const char *addrStr,
    __out_opt UINT32 *pAddr
);

Parameters

Return Value
TRUE if successful, FALSE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Parses an IPv6 address stored in addrStr. If non-NULL, the result is stored in pAddr. The pAddr parameter is assumed to point to a buffer large enough to hold a 16-byte IPv6 address.

6.10 DivertHelperCalcChecksums

UINT DivertHelperCalcChecksums(
    __inout PVOID pPacket,
    __in UINT packetLen,
    __in UINT64 flags
);

Parameters

Return Value
The number of checksums calculated.

Remarks
(Re)calculates the checksum for any IPv4/ICMP/ICMPv6/TCP/UDP checksum present in the given packet. Individual checksum calculations may be disabled via the appropriate flag. Typically this function should be invoked on a modified packet before it is injected with DivertSend().

This function will calculate each checksum from scratch, even if the existing checksum is correct. This may be inefficient for some applications. For better performance, incremental checksum calculations should be used instead (not provided by this API).


7. Filter Language

The DivertOpen() function accepts a string containing a filter expression. Only packets that match the filter expression are diverted. Any other packet is allowed to continue as per normal.

Filter allows an application to select only the subset of traffic that is of interest. For example, a URL blacklist filter would only be interested in packets that contain URLs. This could be achieved via the following filter.

HANDLE handle = DivertOpen(
    "outbound and "
    "tcp.PayloadLength > 0 and "
    "tcp.DstPort == 80", 0, 0, 0);
This filter specifies that we should only divert traffic that is
  1. outbound;
  2. contains a non-empty payload; and
  3. has TCP destination port 80 (i.e. HTTP web traffic).

A filter is a Boolean expression of the form:

        FILTER := true | false | FILTER and FILTER | FILTER or FILTER | (FILTER) | TEST
C-style syntax &&, ||, and ! may also be used instead of and, or, and not, respectively. A test is of the following form:
        TEST := TEST0 | not TEST0
        TEST0 := FIELD | FIELD op VAL
where op is one of the following:

OperatorDescription
== or =Equal
!=Not equal
<Less-than
>Greater-than
<=Less-than-or-equal
>=Greater-than-or-equal

and VAL is a decimal number, hexadecimal number, or IP address. If the "op VAL" is missing, the test is implicitly "FIELD != 0".

Finally a field is some property about the packet. The possible fields are:

FieldDescription
outboundIs outbound?
inboundIs inbound?
ifIdxInterface index
subIfIdxSub-interface index
ipIs IPv4?
ipv6Is IPv6?
icmpIs ICMP?
icmpv6Is ICMPv6?
tcpIs TCP?
udpIs UDP?
ip.*IPv4 fields (see DIVERT_IPHDR)
ipv6.*IPv6 fields (see DIVERT_IPV6HDR)
icmp.*ICMP fields (see DIVERT_ICMPHDR)
icmpv6.*ICMPV6 fields (see DIVERT_ICMPV6HDR)
tcp.*TCP fields (see DIVERT_TCPHDR)
tcp.PayloadLengthThe TCP payload length
udp.*UDP fields (see DIVERT_UDPHDR)
udp.PayloadLengthThe UDP payload length

A test also fails if the field is missing. E.g. the test "tcp.DstPort == 80" will fail if the packet does not contain a TCP header.

7.1 Filter Examples

  1. Divert all outbound web traffic:
    HANDLE handle = DivertOpen(
            "outbound and "
            "(tcp.DstPort == 80 or udp.DstPort == 53)",
            0, 0, 0
        );
    
  2. Divert all inbound TCP SYNs:
    HANDLE handle = DivertOpen(
            "inbound and "
            "tcp.Syn",
            0, 0, 0
        );
    
  3. Divert only (inbound) local traffic:
    HANDLE handle = DivertOpen(
            "inbound and ("
            "(ip.DstAddr >= 127.0.0.1 and ip.DstAddr <= 127.255.255.255) or"
            "ipv6.DstAddr == ::1)",
            0, 0, 0
        );
    
  4. Divert all traffic:
    HANDLE handle = DivertOpen("true", 0, 0, 0);
    
  5. Divert no traffic:
    HANDLE handle = DivertOpen("false", 0, 0, 0);
    
    This is useful for packet injection.

7.2 Filter Usage

The purpose of the filter is to help applications select the subset of all network traffic that the application is interested in. Ideally the filter should be

  1. As short as possible; and
  2. As selective as possible.
For some applications these two objectives can conflict. That is, a selective filter is not short, and a short filter is not selective. For such applications the developer should experiment with different filter configurations and carefully measure the performance impact to find the optimal solution.


8. Samples

Some samples have been provided to demonstrate the WinDivert API. The sample programs are:

The samples are intended for educational purposes only, and are not fully-featured applications.

The following basic template for a WinDivert application. The basic idea is to open a WinDivert handle, then enter a capture-modify-reinject loop:

    HANDLE handle;          // Divert handle
    DIVERT_ADDRESS addr;    // Packet address
    char packet[MAXBUF];    // Packet buffer
    UINT packetLen;

    handle = DivertOpen("...", 0, 0, 0);   // Open some filter
    if (handle == INVALID_HANDLE_VALUE)
    {
        // Handle error
        exit(1);
    }

    // Main capture-modify-inject loop:
    while (TRUE)
    {
        if (!DivertRecv(handle, packet, sizeof(packet), &addr, &packetLen))
        {
            // Handle recv error
            continue;
        }

        // Modify packet.

        if (!DivertSend(handle, packet, packetLen, &addr, NULL))
        {
            // Handle send error
            continue;
        }
    }
For applications that do not need to modify the packet, a better approach is to open the WinDivert handle with the DIVERT_FLAG_SNIFF flag set, and not re-inject the packet with DivertSend(). See the netdump.exe sample program for an example of this usage.


9. Known Issues

There are some limitations to the WinDivert package. They are


10. License

This package is distributed strictly under the GNU Lesser General Public License (GPL) Version 3. Please note the following:

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.