Windows Packet Filter
High-performance raw Ethernet frame filtering, packet inspection, and packet injection for Windows. Filter and manipulate network traffic in user mode without writing kernel drivers.
Complete Control Over Raw Ethernet Traffic
Windows Packet Filter intercepts network traffic directly between the protocol drivers (TCP/IP) and the network interface driver, granting bidirectional packet inspection and manipulation.
Capture
Intercept Layer 2 raw Ethernet frames before protocol stack processing.
Inspect
Parse full Ethernet, IPv4/IPv6, TCP/UDP headers and payloads.
Filter / Modify
Drop, pass, or rewrite frames in memory with kernel-level performance.
Inject
Re-inject packets into the network stack or forward to physical adapters.
Native APIs for C++, C# (.NET), Rust, and Go
Intercept packets from a TCP/IP-bound adapter and reinject them unchanged in both directions using tunnel mode. The same packet-processing model is available through the C++, .NET, Rust, and Go APIs.
#include <iostream>
#include <memory>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <tchar.h>
#include "common.h"
#include "ndisapi.h"
int main()
{
// Open WinpkFilter driver interface
auto api = std::make_unique<CNdisApi>();
if (!api->IsDriverLoaded()) {
std::cerr << "WinpkFilter driver is not loaded.\n";
return 1;
}
// Query TCP/IP bound network adapters
TCP_AdapterList adapters{};
if (!api->GetTcpipBoundAdaptersInfo(&adapters) || adapters.m_nAdapterCount == 0) {
std::cerr << "No TCP/IP bound adapters found.\n";
return 1;
}
HANDLE adapter = adapters.m_nAdapterHandle[0];
HANDLE event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
api->SetPacketEvent(adapter, event);
// Enter bidirectional tunnel mode (divert packets from both directions)
ADAPTER_MODE mode{};
mode.hAdapterHandle = adapter;
mode.dwFlags = MSTCP_FLAG_SENT_TUNNEL | MSTCP_FLAG_RECV_TUNNEL;
api->SetAdapterMode(&mode);
INTERMEDIATE_BUFFER packet{};
ETH_REQUEST request{};
request.hAdapterHandle = adapter;
request.EthPacket.Buffer = &packet;
// Process a bounded number of notification cycles for this minimal example
constexpr int DEMO_WAKEUPS = 100;
for (int i = 0; i < DEMO_WAKEUPS; ++i) {
WaitForSingleObject(event, INFINITE);
ResetEvent(event);
while (api->ReadPacket(&request)) {
if (packet.m_dwDeviceFlags == PACKET_FLAG_ON_SEND) {
// Outgoing from TCP/IP: reinject downward to network adapter
api->SendPacketToAdapter(&request);
} else {
// Incoming from network: reinject upward to TCP/IP stack
api->SendPacketToMstcp(&request);
}
}
}
// Restore normal adapter mode and release event
mode.dwFlags = 0;
api->SetAdapterMode(&mode);
api->SetPacketEvent(adapter, nullptr);
CloseHandle(event);
return 0;
}
using System;
using System.Linq;
using System.Threading;
using NdisApiDotNet;
using NativeApi = NdisApiDotNet.Native.NdisApi;
// Open WinpkFilter driver instance
using var filter = NdisApi.Open();
if (!filter.IsDriverLoaded())
{
Console.WriteLine("WinpkFilter driver is not loaded.");
return;
}
// Select the first valid TCP/IP bound network adapter
var adapter = filter.GetNetworkAdapters().FirstOrDefault(a => a.IsValid);
if (adapter == null)
{
Console.WriteLine("No valid network adapter found.");
return;
}
using var waitEvent = new AutoResetEvent(false);
filter.SetPacketEvent(adapter, waitEvent);
// Enter bidirectional tunnel mode
filter.SetAdapterMode(adapter,
NativeApi.MSTCP_FLAGS.MSTCP_FLAG_SENT_TUNNEL |
NativeApi.MSTCP_FLAGS.MSTCP_FLAG_RECV_TUNNEL);
unsafe
{
NativeApi.ETH_REQUEST* request = filter.CreateEthRequest();
request->hAdapterHandle = adapter.Handle;
// Process a bounded number of notification cycles for this minimal example
const int DemoWakeups = 100;
for (int i = 0; i < DemoWakeups; i++)
{
waitEvent.WaitOne();
while (filter.ReadPacket(ref *request))
{
var buffer = (NativeApi.INTERMEDIATE_BUFFER*)request->EthPacket.Buffer;
if (buffer->m_dwDeviceFlags == NativeApi.PACKET_FLAG.PACKET_FLAG_ON_SEND)
{
// Outgoing from TCP/IP: reinject downward to network adapter
filter.SendPacketToAdapter(ref *request);
}
else
{
// Incoming from network: reinject upward to TCP/IP stack
filter.SendPacketToMstcp(ref *request);
}
}
}
filter.DisposeObject(request);
}
// Restore normal adapter mode and release event
filter.ResetAdapterMode(adapter);
filter.ResetPacketEvent(adapter);
use ndisapi::{
DirectionFlags, EthRequest, EthRequestMut, FilterFlags, IntermediateBuffer, Ndisapi,
};
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::Threading::{CreateEventW, ResetEvent, WaitForSingleObject};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Open WinpkFilter driver instance via native driver handle
let driver = Ndisapi::new("NDISRD")
.map_err(|e| format!("Failed to connect to driver: {e:?}"))?;
// Query TCP/IP bound network adapters
let adapters = driver.get_tcpip_bound_adapters_info()?;
let adapter = adapters.first().ok_or("No TCP/IP bound adapters found")?;
let event = unsafe { CreateEventW(None, true, false, None)? };
driver.set_packet_event(adapter.get_handle(), event)?;
// Enter bidirectional tunnel mode
driver.set_adapter_mode(
adapter.get_handle(),
FilterFlags::MSTCP_FLAG_SENT_TUNNEL | FilterFlags::MSTCP_FLAG_RECV_TUNNEL,
)?;
let mut packet = IntermediateBuffer::default();
// Process a bounded number of notification cycles for this minimal example
const DEMO_WAKEUPS: usize = 100;
for _ in 0..DEMO_WAKEUPS {
unsafe { WaitForSingleObject(event, u32::MAX); }
loop {
let mut read_request = EthRequestMut::new(adapter.get_handle());
read_request.set_packet(&mut packet);
if driver.read_packet(&mut read_request).is_err() {
break;
}
let mut write_request = EthRequest::new(adapter.get_handle());
write_request.set_packet(&packet);
if packet.get_device_flags() == DirectionFlags::PACKET_FLAG_ON_SEND {
// Outgoing from TCP/IP: reinject downward to network adapter
driver.send_packet_to_adapter(&write_request)?;
} else {
// Incoming from network: reinject upward to TCP/IP stack
driver.send_packet_to_mstcp(&write_request)?;
}
}
unsafe { let _ = ResetEvent(event); }
}
// Restore normal adapter mode and release event
driver.set_adapter_mode(adapter.get_handle(), FilterFlags::default())?;
unsafe { let _ = CloseHandle(event); }
Ok(())
}
package main
import (
"log"
"github.com/wiresock/ndisapi-go"
"golang.org/x/sys/windows"
)
func main() {
// Open WinpkFilter driver instance
api, err := ndisapi.NewNdisApi()
if err != nil {
log.Fatalf("Failed to open NDIS API: %v", err)
}
defer api.Close()
if !api.IsDriverLoaded() {
log.Fatal("WinpkFilter driver is not loaded.")
}
// Query TCP/IP bound network adapters
adapters, err := api.GetTcpipBoundAdaptersInfo()
if err != nil || adapters.AdapterCount == 0 {
log.Fatal("No TCP/IP bound adapters found.")
}
adapter := adapters.AdapterHandle[0]
event, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
log.Fatalf("Failed to create event: %v", err)
}
defer windows.CloseHandle(event)
if err := api.SetPacketEvent(adapter, event); err != nil {
log.Fatalf("Failed to set packet event: %v", err)
}
// Enter bidirectional tunnel mode
mode := &ndisapi.AdapterMode{
AdapterHandle: adapter,
Flags: ndisapi.MSTCP_FLAG_SENT_TUNNEL | ndisapi.MSTCP_FLAG_RECV_TUNNEL,
}
if err := api.SetAdapterMode(mode); err != nil {
log.Fatalf("Failed to set adapter mode: %v", err)
}
var buffer ndisapi.IntermediateBuffer
request := &ndisapi.EtherRequest{
AdapterHandle: adapter,
EthernetPacket: ndisapi.EthernetPacket{
Buffer: &buffer,
},
}
// Process a bounded number of notification cycles for this minimal example
const demoWakeups = 100
for i := 0; i < demoWakeups; i++ {
_, _ = windows.WaitForSingleObject(event, windows.INFINITE)
_ = windows.ResetEvent(event)
for api.ReadPacket(request) {
if buffer.DeviceFlags == ndisapi.PACKET_FLAG_ON_SEND {
// Outgoing from TCP/IP: reinject downward to network adapter
_ = api.SendPacketToAdapter(request)
} else {
// Incoming from network: reinject upward to TCP/IP stack
_ = api.SendPacketToMstcp(request)
}
}
}
// Restore normal adapter mode and release event
mode.Flags = 0
_ = api.SetAdapterMode(mode)
_ = api.SetPacketEvent(adapter, 0)
}
Driver Position in the Windows Network Stack
WinpkFilter attaches as an NDIS 6.x Lightweight Filter (LWF) directly below all Windows protocol drivers (TCP/IP) and above the physical or virtual network interface drivers.
Standard IOCTL queues (ReadPackets / SendPackets) provide dependable, synchronous packet interception across all network adapter types.
ndisapi.dll
→
IOCTL Queues
→
ndisrd.sys
High-throughput path utilizing shared memory (FAST_IO_SECTION) between user space and kernel driver for low-latency packet processing.
ndisapi.dll
→
FAST_IO_SECTION
→
ndisrd.sys
Full Layer 2 Inspection
Because the driver sits below tcpip.sys, packets are captured with complete 802.3 Ethernet headers intact. You can filter non-IP protocols (ARP, PPPoE, custom ethertypes) that higher-layer platforms cannot see.
Bidirectional Injection
Inject generated or modified frames directly up towards the Windows protocol stack (simulating inbound wire traffic) or downstream towards the network adapter (transmitting to the physical wire).
Standard & Optional Fast I/O
Provides reliable standard IOCTL queues (ReadPackets / SendPackets) alongside an optional shared-memory mechanism (FAST_IO_SECTION) for low-latency, high-throughput packet processing.
Why an NDIS Lightweight Filter?
Selecting the right Windows network packet interception architecture depends on your required network layer, development model, and deployment constraints.
vs. Windows Filtering Platform (WFP)
While modern WFP provides Layer 2 MAC-layer filtering in addition to transport and socket layers, developing custom kernel callout drivers requires specialized Windows Driver Kit (WDK) development, complex kernel-mode debugging, and ongoing driver maintenance.
WinpkFilter provides a mature, pre-compiled NDIS 6 LWF driver. Applications can intercept, inspect, modify, and reinject network traffic through a straightforward user-mode API (C++, C#, Rust, Go), while Windows Packet Filter handles the low-level Windows networking integration.
vs. WinPcap & Npcap
WinPcap and Npcap are primarily designed for packet capture (promiscuous mode sniffing) and raw packet injection. They do not provide transparent, inline bidirectional packet modification or blocking for host network traffic.
WinpkFilter operates directly in the data path between protocol drivers and physical adapters, granting authoritative control to allow, drop, modify, or redirect every frame in real time.
Modern NDIS LWF vs. Legacy NDIS Techniques
Earlier Windows releases required packet-filtering solutions to use techniques such as NDIS hooking or NDIS Intermediate (IM) drivers. NDIS hooking intercepted internal driver dispatch tables, making it undocumented, brittle, and prone to instability across service packs. NDIS Intermediate drivers were complex to install and manage, introduced significant packet processing overhead, and could cause binding conflicts with other network components.
Modern Windows networking standardizes on the NDIS 6 Lightweight Filter (LWF) architecture, providing a clean, officially supported, high-performance filtering model in the network stack. WinpkFilter transitioned from earlier legacy drivers to a robust NDIS 6 LWF implementation, delivering superior reliability, lower latency, and full compatibility with current Windows and Windows Server releases.
Engineered for Demanding Systems Software
Battle-tested since 2002 across enterprise firewalls, secure VPN gateways, industrial protocol analyzers, and commercial cybersecurity products.
Shared-Memory Fast I/O & Low Latency
High-throughput shared-memory buffer pool between ndisrd.sys and user space minimizes per-packet IOCTL syscall overhead, efficiently handling Gigabit+ network traffic with minimal CPU overhead.
Bidirectional Packet Injection & Redirection
Transmit custom or modified packets in either direction: outbound to physical Ethernet/Wi-Fi media or inbound directly into the Windows TCP/IP stack. Transparently divert connections to local proxy sockets or encrypted tunnels.
System-Wide MTU Decrement & Jumbo Frames
Configurable system-wide MTU decrement allows tunnel solutions (VPN, encapsulation) to add protocol headers without triggering fragmentation or PMTU discovery black holes. Full support for 802.3 jumbo frames.
In-Kernel Rule Filtering & Buffer Pool
Programmatic rule engine inside ndisrd.sys allows developers to set pass, drop, or redirect rules directly in kernel space. Configurable intermediate buffer pool size optimizes memory allocation for bursty traffic.
Operating System & Hardware Support
WinpkFilter maintains a 20+ year lineage supporting modern enterprise Windows deployments while preserving legacy driver architectures.
Current Release (WinpkFilter 3.x)
NDIS 6.x LWFPre-compiled driver packages for supported Windows releases and CPU architectures:
| Platform Installer | Supported Windows Versions | Architecture |
|---|---|---|
| ARM64 Installer | Windows 11, Windows 10 | ARM64 |
| x64 Installer | Windows 11, 10, 8.1, 8, 7, Vista* | x64 (64-bit) |
| x86 Installer | Windows 10, 8.1, 8, 7, Vista* | x86 (32-bit) |
Compatible with 64-bit Windows Server editions sharing the equivalent client kernel / NDIS version.
* Windows Vista requires disabling driver signature enforcement due to industry-wide SHA-1 code signing certificate expiration.
Legacy Windows Driver Line
NDIS 3 – 5Dedicated legacy driver builds maintained for older operating systems and industrial environments:
| Operating System | Driver Model | Architectures |
|---|---|---|
| Windows Server 2003 / Windows XP | NDIS 5.1 Intermediate (IM) Filter | x86, x64 |
| Windows 2000 / Windows NT 4.0 | NDIS 4 Hooking Filter | x86 |
| Windows 95 / 98 / ME | NDIS 3.1 Virtual Device Driver (VxD) | x86 |
Archived driver packages for legacy system maintenance and long-term embedded deployment.
Supported Network Interfaces & Connection Media
Applicability & Use Cases
Developers use Windows Packet Filter across a wide spectrum of network engineering, diagnostic, and security applications.
Firewalls & Packet Filtering
- User-mode desktop firewalls without writing kernel driver code
- Custom in-kernel filtering routines using the Source Code license
- Application traffic isolation, domain blocking, and parental filters
- Network protocol validation and malformed packet discarding
VPNs & Encrypted Tunnels
- Custom WireGuard, IPsec, and SSL VPN client integration
- Network packet encapsulation and tunneling (SSL, SSH, HTTP)
- System-wide MTU decrementing to prevent tunnel packet fragmentation
- Selective routing of application traffic through encrypted tunnels
Transparent Proxies & Redirection
- Transparent SOCKS5 proxification of arbitrary Windows applications
- Layer 2/3 packet redirection to local analysis or proxy sockets
- SSL/TLS Server Name Indication (SNI) inspection and routing
- Local DNS interception and DNS query redirection
Traffic Shaping & Analysis
- Bandwidth metering, traffic shaping, and token bucket rate limiters
- High-speed packet capture and PCAP file logging
- Transparent software bridging between wired Ethernet and Wi-Fi
- Industrial & SCADA raw Ethernet frame inspection
Real-World Software & Developer Samples
WinpkFilter powers enterprise networking software and provides maintained language bindings and representative sample applications.
WireSock Secure Connect
High-performance WireGuard client for Windows. Uses Windows Packet Filter in both transparent and virtual adapter modes for application traffic routing and filtering. Transparent mode does not require a virtual network adapter.
Learn about WireSock Secure Connect →ProxiFyre SOCKS5 Client
Advanced SOCKS5 proxifier for Windows built on WinpkFilter. Transparently routes application TCP and UDP traffic through proxy instances without modifying client software.
Explore ProxiFyre →Representative Open-Source Samples
View all 14+ samples on GitHub →| Sample Application | Language | Functionality Description | Source |
|---|---|---|---|
| Capture Packet Filter | C++ | High-speed packet sniffer with pcap storage using Shared-Memory Fast I/O. | GitHub |
| DNS Proxy Server | C++ | Transparently redirects DNS UDP traffic through a local proxy socket. | GitHub |
| Static Packet Filter | C++ | Demonstrates in-kernel static packet filtering rules (pass, drop, or redirect to user space) using static filter tables. | GitHub |
| SNI Inspector | C++ | Extracts TLS Server Name Indication (SNI) and HTTP Host headers in real time using Fast I/O. | GitHub |
| ListAdapters | C++ | Enumerates TCP/IP bound network interfaces, MAC addresses, and adapter MTU. | GitHub |
Looking for Substantial Engineering Applications?
Explore dedicated architectural guides and source code for Internet Gateway (MFC NAT), WAN Emulator (latency injection), and Ethernet Bridge.
Licensing Options
Windows Packet Filter is free for personal or educational use, with transparent commercial licensing for software publishers.
Personal / Educational
Free for personal use, educational research, and non-profit organizations.
- ✓ Complete SDK & User-Mode APIs
- ✓ Official signed driver installer
- ✓ Access to all sample applications
- ✓ Community Forum Support
- ✕ Commercial redistribution
- ✕ Kernel driver source code
Binary License
For software publishers redistributing Windows Packet Filter binaries with one product.
- ✓ Royalty-free binary redistribution with one product
- ✓ Pre-compiled signed drivers (
ndisrd.sys) - ✓ C++, C# (.NET), Rust, and Go client libraries
- ✓ 1 Year of free updates & technical support
- ✓ 1 custom driver build included
- ✕ Kernel driver source code
Source Code License
Complete driver and user-mode library source code for custom kernel development.
- ✓ Full C/C++ driver source code (
ndisrd.sys) - ✓ Full user-mode DLL source code (
ndisapi.dll) - ✓ Custom in-kernel filtering routines
- ✓ Independent compilation with the WDK
- ✓ 1 custom driver build included (built to agreed configuration)
- ✓ 1 Year of free updates & developer support
- ✓ Royalty-free redistribution
Technical Support & Discussion
Have questions about NDIS 6 LWF behavior, packet injection edge cases, or multi-adapter configurations? Browse the support forum or ask directly.