A high-performance Windows USB packet capture library using the USBPcap driver. No Npcap/WinPcap required only Windows system libraries.
Table of Contents
- Quick Start
- Features
- Requirements
- Building
- API Reference
- Data Structures
- Code Examples
- Manual Threading
- Advanced Filtering
- Quick API Reference
- Troubleshooting
- Architecture
- License & Attribution
- Windows 7+ (run as Administrator)
- Visual Studio 2017+ or GCC 5.0+ or Clang 3.5+
- CMake 3.20+ (https://cmake.org/)
- USBPcap Driver (https://desowin.org/usbpcap/)
1. Download from: https://desowin.org/usbpcap/
2. Run installer and reboot
3. Verify installation
cd usbPcap_trial
mkdir build
cd build
cmake -B . -G "Ninja"
ninja#include <iostream>
#include "WIND_USB/wind_usb.hpp"
using namespace WIND_USB;
int main() {
auto& usb = USBCapture::instance();
auto devices = usb.list_devices();
for (const auto& dev : devices) {
if (dev.address == 0) continue;
std::cout << dev.friendlyName << " at " << (int)dev.address << "\n";
}
return 0;
}void on_packet(const USBPacket& pkt) {
++packet_count;
std::cout << "Packet: " << pkt.payload.size() << " bytes\n";
}
auto& usb = USBCapture::instance();
usb.start_capture(ifaces[0], on_packet);
std::this_thread::sleep_for(std::chrono::seconds(30));
usb.stop_capture();- Real-time USB Packet Capture: Capture all USB traffic on a system
- Device Enumeration: Detect and list all connected USB devices with full details
- Flexible Filtering: Filter by device address, endpoint, transfer type, direction, and status codes
- Multi-threaded: Producer-consumer architecture for non-blocking packet processing
- Zero External Dependencies: Uses only Windows APIs and C++17 standard library
- Singleton Pattern: Thread-safe global instance management
- PCAP Format Support: Packets in standard libpcap format with USBPcap extensions
- Comprehensive Packet Metadata: Transfer type, direction, endpoint, control stage, status codes
- Windows 7/8/10/11 (32-bit or 64-bit)
- Administrator Privileges required for packet capture
- USBPcap Driver (https://desowin.org/usbpcap/)
- C++17 Compiler (MSVC 2017+, GCC 5.0+, Clang 3.5+)
- CMake 3.20+ (for building)
setupapi.lib� Windows device enumeration APIws2_32.lib� Windows Socketscfgmgr32.lib� Configuration Manager
mkdir build
cd build
cmake -B . -G "Ninja"
ninjaIn CMakeLists.txt:
add_subdirectory(path/to/WIND_USB)
target_link_libraries(your_app PRIVATE wind_usb)cmake -B build -G Ninja
cmake --build buildMain interface. Access via USBCapture::instance().
std::vector<USBDevice> list_devices() constReturns all detected USB devices.
std::vector<std::string> list_capture_interfaces() constReturns available USBPcap interfaces.
bool start_capture(const std::string& iface, PacketCallback cb, bool autoThread = true)Start capturing packets.
iface: USBPcap interface name (e.g., "\\.\USBPcap1")cb: Callback function invoked for each packetautoThread: If true (default), automatically spawn producer/consumer threads. If false, you must callproducer_loop()andconsumer_loop()from your own threads- Returns: true on success, false on failure
Automatic threading (default):
usb.start_capture(ifaces[0], on_packet); // Threads started automaticallyManual threading:
usb.start_capture(ifaces[0], on_packet, false); // No threads started
std::thread prod([&]() { usb.producer_loop(ifaces[0]); });
std::thread cons([&]() { usb.consumer_loop(); });
// ... do work ...
usb.stop_capture();
prod.join(); cons.join();void producer_loop(const std::string& iface)Read packets from USBPcap device. Blocks until stop_capture() is called. Only use when autoThread=false in start_capture(). Call from your own thread.
void consumer_loop()Process packets from queue and invoke callback. Blocks until stop_capture() is called. Only use when autoThread=false in start_capture(). Call from your own thread.
void stop_capture()Stop capture and wait for threads. Safe to call from signal handlers. If you started producer/consumer manually, you must call stop_capture() and then join() your threads.
bool is_running() constReturns true if currently capturing.
void set_filter(const USBFilter& f)Set filter for packets. Thread-safe, applies immediately.
USBFilter get_filter() constGet current filter settings.
void filter_by_device(uint16_t address)Filter by device address only.
void filter_by_endpoint(uint8_t endpoint)Filter by endpoint only.
struct USBPacket {
std::chrono::system_clock::time_point timestamp;
USBDirection direction; // Up or Down
USBTransferType transferType; // Bulk, Control, Interrupt, Iso
uint16_t deviceAddress; // 1-127
uint8_t endpoint; // 0-15
bool endpointIn;
USBControlStage controlStage;
bool isSetup;
USB_SETUP_PACKET setup;
uint32_t usbd_status;
bool isSuccess, isACK, isSTALL, isNAK, isHandshake;
std::vector<uint8_t> payload;
};struct USBDevice {
std::string friendlyName;
std::string description;
std::string manufacturer;
std::string hardwareId;
uint16_t vendorId;
uint16_t productId;
uint16_t address; // USB address 1-127
uint8_t miNumber; // 0xFF if not composite
std::vector<uint8_t> endpoints;
std::string vidPidString() const;
std::string vidPidMiString() const;
std::string endpointListString() const;
};struct USBFilter {
bool showUpstream = true;
bool showDownstream = true;
bool showACK = true;
bool showSTALL = true;
bool showNAK = true;
bool showHandshake = true;
bool showControl = true;
bool showBulk = true;
bool showInterrupt = true;
bool showIsochronous = true;
uint16_t filterDeviceAddress = 0; // 0=all
uint8_t filterEndpoint = 0xFF; // 0xFF=all
};struct USB_SETUP_PACKET {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
};enum class USBDirection {
Down, // Host ? Device
Up // Device ? Host
};enum class USBTransferType : uint8_t {
Isochronous = 0,
Interrupt = 1,
Control = 2,
Bulk = 3
};enum class USBControlStage : uint8_t {
Setup = 0,
Data = 1,
Status = 2,
Complete = 3
};#include "WIND_USB/wind_usb.hpp"
using namespace WIND_USB;
int main() {
auto& usb = USBCapture::instance();
for (const auto& dev : usb.list_devices()) {
if (dev.address == 0) continue;
std::cout << dev.friendlyName << " at " << (int)dev.address << "\n";
}
return 0;
}std::atomic<int> count{0};
void on_packet(const USBPacket& pkt) {
++count;
}
auto& usb = USBCapture::instance();
usb.start_capture(ifaces[0], on_packet);
std::this_thread::sleep_for(std::chrono::seconds(10));
usb.stop_capture();
std::cout << "Packets: " << count << "\n";USBFilter filter;
filter.filterDeviceAddress = 5;
filter.filterEndpoint = 0x81;
usb.set_filter(filter);
usb.start_capture(ifaces[0], on_packet);void track_control(const USBPacket& pkt) {
if (pkt.transferType != USBTransferType::Control) return;
if (pkt.isSetup) {
std::cout << "SETUP request: 0x" << std::hex
<< (int)pkt.setup.bRequest << "\n";
}
}
USBFilter f;
f.showControl = true;
f.showBulk = false;
usb.set_filter(f);
usb.start_capture(ifaces[0], track_control);By default, start_capture() automatically spawns producer and consumer threads. For advanced use cases, you can take full control over thread management.
- Custom Thread Pools: Integrate capture into existing thread pool infrastructure
- CPU Affinity: Pin producer/consumer to specific cores
- Thread Priority: Set higher priority for producer thread
- Priority Scheduling: Control scheduling with other components
- Multi-Capture: Manage multiple captures with coordinated threads
- Integration: Embed capture in larger multi-threaded applications
#include "WIND_USB/wind_usb.hpp"
using namespace WIND_USB;
auto& usb = USBCapture::instance();
auto devices = usb.list_devices();
auto ifaces = usb.list_capture_interfaces();
// Define callback
void on_packet(const USBPacket& pkt) {
std::cout << "Packet: " << pkt.payload.size() << " bytes\n";
}
// Start capture WITHOUT automatic threads
usb.start_capture(ifaces[0], on_packet, false); // autoThread=false
// Create your own threads
std::thread producer([&ifaces]() {
USBCapture::instance().producer_loop(ifaces[0]);
});
std::thread consumer([]() {
USBCapture::instance().consumer_loop();
});
// Application runs...
std::this_thread::sleep_for(std::chrono::seconds(30));
// Stop capture
usb.stop_capture();
// Wait for threads to finish
producer.join();
consumer.join();#include <windows.h>
void set_thread_affinity(std::thread& t, int core) {
HANDLE h = (HANDLE)(t.native_handle());
SetThreadAffinityMask(h, 1LL << core);
}
// Start capture manually
usb.start_capture(ifaces[0], on_packet, false);
// Create threads with affinity control
std::thread producer([&]() {
USBCapture::instance().producer_loop(ifaces[0]);
});
std::thread consumer([]() {
USBCapture::instance().consumer_loop();
});
// Pin threads to specific cores (requires CPU >= 2 cores)
set_thread_affinity(producer, 0); // Core 0
set_thread_affinity(consumer, 1); // Core 1
// Rest of application...
usb.stop_capture();
producer.join();
consumer.join();| Method | Purpose | Thread-Safe? |
|---|---|---|
start_capture(..., false) |
Prepare capture without spawning threads | Yes |
producer_loop(iface) |
Read from device, parse packets, queue them | No (call once) |
consumer_loop() |
Process queue, filter, invoke callback | No (call once) |
stop_capture() |
Stop capture and signal threads to exit | Yes |
is_running() |
Check if capture is active | Yes (atomic) |
set_filter(f) |
Change filter live (works with manual threading) | Yes |
See examples/manual_threading/main.cpp for a full working example with:
- Thread naming for debuggers
- CPU affinity demonstration
- Statistics tracking
- Graceful shutdown
- 30-second auto-stop
Build and run:
cmake -B build -G Ninja
cmake --build build
./build/examples/usb_manual_threading_example.exeUSBFilter f;
f.showACK = true;
f.showNAK = f.showSTALL = false;
usb.set_filter(f);USBFilter f;
f.showBulk = true;
f.showControl = f.showInterrupt = f.showIsochronous = false;
usb.set_filter(f);USBFilter f;
f.showUpstream = true;
f.showDownstream = false;
usb.set_filter(f);usb.start_capture(iface, callback);
std::this_thread::sleep_for(std::chrono::seconds(5));
usb.filter_by_device(10); // Switch to device 10
std::this_thread::sleep_for(std::chrono::seconds(5));
usb.stop_capture();// Access singleton
auto& usb = USBCapture::instance();
// List devices
auto devices = usb.list_devices();
// Get interfaces
auto ifaces = usb.list_capture_interfaces();
// Define callback
void callback(const USBPacket& pkt) {
// Process packet
}
// Start capturing
usb.start_capture(ifaces[0], callback);
// While capturing, change filter
USBFilter f;
f.filterDeviceAddress = 5;
usb.set_filter(f);
// Stop capture
usb.stop_capture();| Goal | Code |
|---|---|
| Device 5 only | usb.filter_by_device(5); |
| Endpoint 1 IN only | usb.filter_by_endpoint(0x81); |
| Bulk only | f.showBulk=true; f.showControl=f.showInterrupt=f.showIsochronous=false; |
| Success only | f.showACK=true; f.showNAK=f.showSTALL=false; |
| Upstream only | f.showUpstream=true; f.showDownstream=false; |
- Device address: 0=root hub, 1-127=actual devices
- Endpoint bit 7: 1=IN, 0=OUT
- Endpoint 0: Control (always bidirectional)
- Filter 0: Means "no filter" (all)
- Filter 0xFF: Means "all" (no restriction)
Causes: Not installed, not admin, not enabled
Fix:
- Install from https://desowin.org/usbpcap/
- Run as Administrator
- Restart the application
Cause: Not running as Administrator
Fix: Right-click app ? Run as Administrator
Checks:
is_running()returns true?- Filter blocking packets?
- Device generating traffic?
Debug:
auto devices = usb.list_devices();
for (const auto& d : devices)
std::cout << d.friendlyName << " at " << (int)d.address << "\n";Causes:
- Capture not running
- Filter too restrictive
- No traffic on device
Solution: Reset filter to defaults
Cause: Callback too slow or packet rate very high
Solution: Optimize callback or add batching
- Producer: Reads USBPcap, parses PCAP, queues packets
- Consumer: Filters, invokes callback, backpressure (max 4096)
- Main: Calls start/stop/filter
- No packet storage (only callback)
- User responsible if callback saves packets
- Internal queue: max 4096 packets
- Standard PCAP with USBPcap extensions
- pcap_global_hdr (magic 0xa1b2c3d4)
- pcap_rec_hdr with timestamps
- USBPCAP_BUFFER_PACKET_HEADER
- Raw packet data
For a complete real-world USB capture application, see examples/auto_threading/main.cpp.
Build:
cmake -B build -G Ninja
cmake --build buildRun:
./build/examples/usb_console_example.exe # As AdministratorWIND_USB is licensed under the BSD 2-Clause License.
See LICENSE file for full text.
This library interfaces with and depends on:
- License: GPLv2 (GNU General Public License v2)
- Source: https://github.com/desowin/usbpcap
- Purpose: System driver for Windows USB packet capture
- License: BSD 2-Clause
- Source: https://github.com/desowin/usbpcap
- Purpose: Reference tools for USBPcap usage
When using WIND_USB library:
- The WIND_USB library code itself is provided under the permissive BSD 2-Clause license
- The USBPcap driver (distributed separately by the USBPcap project) is licensed under GPLv2
- ✅ Include the LICENSE file with the library
- ✅ Document the use of the GPLv2-licensed USBPcap driver
- ✅ Provide information about how to obtain the USBPcap source
- ✅ Acknowledge the USBPcap project in your documentation
- The BSD 2-Clause license permits commercial use
- Review GPLv2 terms if you're distributing the USBPcap driver with your application
- Consider consulting legal counsel regarding GPLv2 compliance
WIND_USB Library
©2025 WIND_USB Contributors
Uses USBPcap driver by Tomáš Kopeček (desowin)
https://desowin.org/usbpcap/
Your WIND_USB library is production-ready! ??