A control driver, not a NIC driver
WDM provides entry, dispatch, device creation and unload routines. There is no NDIS lightweight-filter attachment to a network adapter.
Implementation notes / C11 and Windows kernel
A WFP callout driver that observes IPv4 TCP/UDP connection attempts and blocks selected endpoints. A C controller configures the rule and reads metadata across a secured user/kernel boundary.
Connection authorization, not raw packet capture.
Educational implementation. Kernel VM validation still pending.
01 / The problem
A network application asks Windows to communicate with an endpoint. Where can a driver observe that request, reject a specific destination, and still cooperate with the rest of the firewall? This project explores that boundary using Windows Filtering Platform (WFP), the operating system’s network-filtering infrastructure.
The useful outcome is not a replacement for Windows Firewall. It is an implementation of the contracts behind a filter: registering callbacks, extracting typed metadata, handling concurrent requests, validating a control interface, and cleaning up every object the driver creates.
The driver works at the Application Layer Enforcement (ALE) authorization layers. It evaluates connection authorizations, not every packet. It does not inspect Ethernet frames, parse TCP payloads, decrypt traffic, or produce a PCAP file.
02 / Architecture
USER MODE
Validate arguments, open the device, send a rule, read stats or events.
Exercise shared policy functions and create repeatable TCP/UDP echo traffic.
KERNEL MODE
Versioned IOCTLs update one rule and drain a bounded metadata ring under a spin lock.
Two ALE callouts receive typed endpoints, check action rights, evaluate policy, and record a decision.
Network traffic does not pass through the controller. WFP invokes the driver independently; IOCTLs carry only control data and recorded metadata.
WDM provides entry, dispatch, device creation and unload routines. There is no NDIS lightweight-filter attachment to a network adapter.
The runtime callout holds callback pointers. A management callout associates its GUID with a layer. Filters make WFP invoke that callout for TCP and UDP.
The driver and controller compile the same pointer-free structures. Version, size, reserved fields and access bits define what crosses the boundary.
FWPM_LAYER_ALE_AUTH_CONNECT_V4The service port is the remote port: the service the local application wants to reach.
FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4The service port is the local port: the service listening on this machine.
03 / Driver lifecycle
Initialization starts in monitor-only mode. WFP management changes are transactional, and the session is dynamic: its policy objects belong to the engine handle rather than becoming persistent machine policy.
04 / Follow a rule
A rule is a conjunction: direction and protocol and remote IPv4 and service port must match, except fields marked any. At least an IP or port is required. Expand a case to trace the reasoning.
NetworkCtl block out tcp 127.0.0.1 8080These cases illustrate the implemented policy. This page neither loads the C driver nor receives network events.
All four rule fields match. If WFP grants FWPS_RIGHT_ACTION_WRITE, the driver returns FWP_ACTION_BLOCK and clears the action-write right. The event ring records this callout’s block decision.
Port 8081 does not match 8080, so this driver does not block. With action-write available it returns FWP_ACTION_CONTINUE, not a blanket permit. Windows Firewall or another provider can still reject the connection. UDP to 8080 would also miss this TCP-only rule.
Different rule for this case: NetworkCtl block in tcp 192.0.2.10 8080
The remote address matches the peer, while service port 8080 matches the local listener. The peer’s ephemeral port 50000 is not the service port. With action-write available, this matches and blocks. These addresses are reserved documentation examples, not public test targets.
Even if every rule field matches, the driver must not overwrite a decision when FWPS_RIGHT_ACTION_WRITE is absent. It leaves the classification output untouched and records ND_ACTION_NO_RIGHT. This label describes the callback’s authority, not a final allow or block result.
NetworkCtl monitor replaces the rule with a disabled one. Subsequent authorizations are observed without blocking by this driver. *Action-write still matters: if it is absent, the existing action is preserved and the event is NO-RIGHT instead.
Closing the controller or pressing Ctrl+C in a watcher does not clear an enabled rule. Use monitor or unload the driver.
Rule changes affect future authorization callbacks. They do not explicitly request reauthorization or disconnect existing sockets; use fresh sockets when testing.
05 / Observable state
The classify callback can run at DISPATCH_LEVEL, concurrently on multiple CPUs. Its path uses nonpaged static storage and a short spin-lock critical section. It does not allocate memory, wait on user mode, write files, or print a log line for every event.
When full, the ring overwrites the oldest event and increments Overwritten. Each read drains up to 32 records. Two watchers compete for the same queue; they are not independent subscribers.
06 / Permissions and trust
A protected DACL grants device access only to LocalSystem and elevated Builtin Administrators. FILE_DEVICE_SECURE_OPEN applies security to the device namespace; trailing file names are rejected.
Rule changes require write access on the handle. Stats and events require read access. The controller runs asInvoker; it does not silently elevate or install a service.
METHOD_BUFFERED supplies a captured system buffer. The driver checks lengths, ABI version, rule fields and reserved values, then returns only initialized bytes.
D:P(A;;GA;;;SY)(A;;GA;;;BA)D:P — protected discretionary ACL. SY — LocalSystem. BA — Builtin Administrators. The two allow entries grant generic-all access; an unelevated administrator token does not gain that grant.
No payload capture does not mean no sensitive data: IPs, ports, PIDs and timestamps can reveal activity. Test only on authorized systems, keep logs inside the lab, and redact them before publishing. An administrator-only sample is not a tamper-resistant security product.
07 / Design choices
| Choice | Why it is here | Tradeoff |
|---|---|---|
| ALE rather than raw packet layers | Typed endpoints and connection-level policy without parsing network buffers. | No per-packet inspection, payload capture, or byte counters. |
| One volatile rule | Atomic replacement and a simple matching contract; monitor-only after reload. | No ordered rule list, persistence, CIDR matching or DNS resolution. |
| One lock, bounded storage | Rule, counters and ring remain consistent without allocation in classify. | Shared-lock contention and intentional event loss under load; performance unmeasured. |
| Dynamic WFP session | Policy is tied to an engine handle, with transactional startup. | No automatic recovery after BFE session loss; restart is required. |
| CONTINUE on nonmatches | Cooperate with other WFP providers instead of broadly permitting traffic. | The event alone cannot tell whether the application ultimately connected. |
08 / Evidence, not assumptions
The repository records the following local development results with Visual Studio 2026 and WDK 10.0.28000.0. These are a documented validation snapshot, not a live CI badge and not evidence of successful filtering in a VM.
Debug and Release solution builds, including WDK INF/catalog checks.
Shared rule validation, matching, ABI sizes and IOCTL access bits.
Help and invalid-input exit codes, without opening the driver.
C echo listener/probe pairs without the driver loaded. Not a filtering test.
Driver loading, live blocking, device ACL enforcement, concurrent kernel operation, unload/reload, BFE lifecycle and Driver Verifier still require an isolated VM. ARM64 configurations exist but have not been built or runtime-tested in the recorded validation.
09 / Reproduce the work
Install Visual Studio 2026 with MSVC v145, a compatible Windows SDK/WDK and WDK integration. Open NetworkDriver/NetworkDriver.slnx, or use the 64-bit MSBuild host from a VS Developer Command Prompt.
cd NetworkDriver
"%VSINSTALLDIR%MSBuild\Current\Bin\amd64\MSBuild.exe" NetworkDriver.slnx /m /p:Configuration=Release /p:Platform=x64
bin\x64\Release\NetworkDriverTests.exeThe default tests run without a driver. Build outputs are unsigned by default. No certificate, service or boot setting is changed automatically.
Use a disposable Windows VM with a snapshot, console access and an authorized private test network. Follow the full guide for test signing, certificate trust and the explicit demand-start service setup.
Once the signed driver is running, use an elevated VM terminal. Start the C echo listener and a fresh probe for each trial; verify a matching BLOCK event rather than treating a timeout alone as evidence.
The controller commands below are instructions, not recorded output. For a UDP trial, change both the fixture and the rule protocol.
NetworkCtl monitor
NetworkCtl block out tcp 127.0.0.1 8080
NetworkCtl events
NetworkCtl stats
NetworkCtl monitorFinish by closing watchers and device handles, stopping the driver and removing the lab service. The guide covers rollback and certificate cleanup. Do not force-unload a driver.
10 / Scope and next steps
The lesson is not just how to return BLOCK. It is how to make a bounded, explainable decision without breaking the operating system’s ownership, lifetime, or permission rules.
11 / Read the implementation
Entry, secure device, IOCTL dispatch, WFP registration, classify and cleanup.
ABIFixed-width structures, access-specific IOCTLs, versions and compile-time sizes.
POLICYSmall, testable validation and matching functions shared with the C tests.
CONTROLLERStrict CLI parsing, least-access handles, UTC metadata and watch mode.
TESTSRule/ABI checks, opt-in device checks, and bounded TCP/UDP echo fixtures.
REFERENCEThe full command contract, build requirements, permission model and limitations.