Divert: Windows Packet Divert

Table of Contents


1. Introduction

This package (divert) provides user-mode packet capture/modification/blocking/re-injection for Windows Vista and later.

The main features of the divert package are:

The functionality provided by divert is very similar to DIVERT sockets in FreeBSD/MacOS and NETLINK sockets in Linux, as well as some commercial packages, e.g. WinPktFilter, for Windows.


2. Building

To build the divert package from source, you must follow these steps:

  1. Download and install the latest Windows Driver Kit.
  2. Open a Free Build Environment console (or Checked Build Environment for debugging).
  3. In the divert root directory, run the command:
    build -cZg
    
    This will build the following files and place them in the divert\install subdirectory:

2.2 Driver Signing

Before the divert package can be used, the divert.sys driver must be digitally signed. See Driver Signing Requirements for Windows for more information.

Driver signing is not provided by this package, although this is something we wish to change in the future. If you wish to use this package, you must sign the driver yourself.

If you wish to test this package, you can set up a test certificate. See Test-Signing Driver Packages for more information.


3. Installing

The divert package does not require any special installation. Simply ensure that the divert.dll, divert.sys, divert.inf, and WdfCoInstaller*.dll files are in your application's home directory.

The divert driver is installed on demand, i.e., when your application makes a call to DivertOpen() from divert.dll (see programming API below).

The WdfCoInstaller*.dll file is relatively bloated compared to the other files. Blame Microsoft.


4. Uninstalling

To uninstall, simply delete the divert.dll, divert.sys, divert.inf, and WdfCoInstaller*.dll files. If the divert driver was already demand-started, it will be removed automatically after the next reboot. To immediately remove it, your uninstaller can issue the following commands:

sc stop divert
sc delete divert
Note however this is not recommended, as this may interfere with other applications using the divert package.


5. Programming API

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

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

5.1 DIVERT_PACKET

typedef struct
{
    UINT8  Reserved[7];
    UINT8  Direction;
    UINT32 IfIdx;
    UINT32 SubIfIdx;
} DIVERT_PACKET, *PDIVERT_PACKET;

Fields

Remarks
The DIVERT_PACKET structure represents a captured or injected packet. The packet's contents, i.e., IP/TCP/UDP headers and data, immediately follow a DIVERT_PACKET header in memory.

5.2 DivertOpen

HANDLE DivertOpen(
    __in const char *filter
);

Parameters

Return Value
A valid divert HANDLE on success, or INVALID_HANDLE_VALUE if an error occurred. Use GetLastError() to get the reason for the error.

Remarks
Opens a divert packet capture handle for the given filter. Any packet that matches the filter will be diverted to the handle, and can be read by calling DivertRecv().

A typical application is only interested in a subset of all traffic. In this case the filter should match this subset as closely as possible. This avoids unnecessary overheads introduced by diverting packets to an application, only to have the application re-inject them.

Calling DivertOpen() for the first time will automatically load the divert.sys driver. This driver will remain installed until the next reboot, or if the driver is explicitly removed, e.g. by issuing the following commands:

sc stop divert
sc delete divert
This model helps ensure the driver is not loaded unless it is required to be.

5.3 DivertRecv

BOOL DivertRecv(
    __in HANDLE handle,
    __out PDIVERT_PACKET pPacket,
    __in UINT packetLen,
    __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 matches the filter passed to DivertOpen(). The received packet is guaranteed to match the filter.

The pPacket parameter is intended to be a buffer large enough to store a DIVERT_PACKET header, and enough space to store the diverted packet. This would typically be achieved by the following declarations:

    char packet[MAX_SIZE];                              // packet buffer space
    PDIVERT_PACKET pPacket = (PDIVERT_PACKET)packet;    // cast packet to a PDIVERT_PACKET
    ...
    if (!DivertRecv(handle, pPacket, sizeof(packet), &recvLen))
    {
        // Recv error
    }
    ...

An application should call DivertRecv() as soon as possible after a successful call to DivertOpen(). When a divert handle is open, any packet that matches the filter will be captured and queued until handled by DivertRecv(). The packet queue never exceeds a fixed length (currently 1024 packets), after which packets are dropped. Furthermore packets are not queued indefinitely. A packet that has been queued longer than a specific time (currently 100ms) will be dropped. To avoid packets being dropped that application must process packets as fast as possible.

5.4 DivertSend

BOOL DivertSend(
    __in HANDLE handle,
    __in PDIVERT_PACKET pPacket,
    __in UINT packetLen,
    __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 cannot be read again by DivertRecv().

The DIVERT_PACKET header determines how the packet is injected. If the Direction field is DIVERT_PACKET_DIRECTION_OUTBOUND, the packet is injected into the outbound path (i.e. a packet leaving this computer). Else, if Direction is DIVERT_PACKET_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 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 change in 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 handle created by DivertOpen().


6. Helper Programming API

The divert helper programming API is a collection of definitions and functions designed to make writing divert 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 DivertHelperParse

BOOL DivertHelperParse(
    __in PDIVERT_PACKET 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. one captured using 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, other length and the minimal information required to parse the headers themselves.

6.8 DivertHelperCalcChecksums

UINT DivertHelperCalcChecksums(
    __inout PDIVERT_PACKET 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 used before a packet is injected.

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 "
    "data and "
    "tcp.DstPort == 80");
This filter specifies that we should only divert traffic that is
  1. outbound;
  2. contains data; 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:

== 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:

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)"
        );
    
  2. Divert all inbound TCP SYNs:
    HANDLE handle = DivertOpen(
            "inbound and "
            "tcp.Syn"
        );
    
  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)"
        );
    
  4. Divert all traffic:
    HANDLE handle = DivertOpen("true");
    
  5. Divert no traffic:
    HANDLE handle = DivertOpen("false");
    
    (This is not very useful).

8. Samples

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

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


9. Known Issues

There are some limitations to the divert package. They are


10. License

This package is distributed strictly under the GNU 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 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 General Public License for more details.

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

Other licenses (including commercial licenses) may be available on request. For more information please contact:
basil AT reqrypt DOT org