From 1e2819d8f1f8f3edcebe2738e808b917973f5cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Vejn=C3=A1r?= Date: Thu, 27 May 2021 12:30:01 +0200 Subject: [PATCH] Initial commit --- .github/workflows/build.yaml | 15 ++ .gitignore | 1 + CMakeLists.txt | 20 ++ LICENSE | 17 ++ README.md | 27 +++ VERSION | 1 + src/cmdline.h | 176 ++++++++++++++ src/comptr.h | 83 +++++++ src/hr.h | 16 ++ src/main.cpp | 429 +++++++++++++++++++++++++++++++++++ src/ndisdump.rc.in | 16 ++ src/pcapng.h | 188 +++++++++++++++ src/registry.h | 78 +++++++ src/sigint.h | 90 ++++++++ src/utf8.h | 21 ++ 15 files changed, 1178 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 VERSION create mode 100644 src/cmdline.h create mode 100644 src/comptr.h create mode 100644 src/hr.h create mode 100644 src/main.cpp create mode 100644 src/ndisdump.rc.in create mode 100644 src/pcapng.h create mode 100644 src/registry.h create mode 100644 src/sigint.h create mode 100644 src/utf8.h diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..6632f0e --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,15 @@ +name: build +on: [push] + +jobs: + build: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + run: cmake -S . -B _build + + - name: Build + run: cmake --build _build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0945fe --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +_build*/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8d11f3c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.16) + +set(NDISDUMP_VERSION "0.0.0" CACHE STRING "ndisdump version number (major.minor.patch)") +project(ndisdump VERSION "${NDISDUMP_VERSION}") + +configure_file(src/ndisdump.rc.in ndisdump.rc) + +add_executable(ndisdump + src/main.cpp + src/cmdline.h + src/comptr.h + src/hr.h + src/pcapng.h + src/registry.h + src/sigint.h + src/utf8.h + "${CMAKE_CURRENT_BINARY_DIR}/ndisdump.rc" + ) +target_compile_features(ndisdump PUBLIC cxx_std_20) +target_link_libraries(ndisdump PUBLIC iphlpapi.lib) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..969d061 --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a36072 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# ndisdump + +A no-dependencies network packet capture tool for Windows. + +## Introduction + +Windows systems come with a pre-installed network filter, ndiscap.sys, +which is used by `netsh trace` command to perform network captures +into an .etl file. The file which must then be converted to .pcapng with +another tool. + +This repository contains `ndisdump`, a tool that uses ndiscap.sys +to perform network capture directly into .pcapng file. + +``` +Usage: ndisdump [-s SNAPLEN] -w FILE + +-w FILE The name of the output .pcapng file. +-s SNAPLEN Truncate packets to SNAPLEN to save disk space. +``` + +You can terminate the capture with Ctrl+C. + +## TODO + +The ultimate aim is for this tool to have the same command-line interface +as `tcpdump`, including the filter language. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9f8e9b6 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/src/cmdline.h b/src/cmdline.h new file mode 100644 index 0000000..d0bfc19 --- /dev/null +++ b/src/cmdline.h @@ -0,0 +1,176 @@ +#include "utf8.h" + +#include +#include +#include +#include +#include + +#include + +struct command_line_reader +{ + command_line_reader() + : command_line_reader(::GetCommandLineW()) + { + } + + command_line_reader(int argc, char const * const argv[]) + : command_line_reader(::GetCommandLineW()) + { + } + + explicit command_line_reader(wchar_t const * cmdline) + { + wchar_t ** argv = ::CommandLineToArgvW(cmdline, &_argc); + if (!argv) + { + DWORD err = ::GetLastError(); + throw std::system_error(err, std::system_category()); + } + + _argv.reset(argv); + + _arg0 = this->pop_path(); + } + + std::filesystem::path const & arg0() const noexcept + { + return _arg0; + } + + bool next() + { + if (_forced_arg) + throw std::runtime_error("XXX unexpected argument"); + + retry: + if (*_short_opts) + { + _opt.resize(2); + _opt[0] = '-'; + _opt[1] = (char)*_short_opts++; + + if (*_short_opts == '=') + { + _forced_arg = _short_opts + 1; + _short_opts = L""; + } + return true; + } + + if (_idx >= _argc) + return false; + + wchar_t const * cur = _argv[_idx++]; + if (_parse_opts && cur[0] == '-' && cur[1] != 0 && cur[1] != '=') + { + if (cur[1] == '-') + { + if (cur[2] == 0) + { + _parse_opts = false; + _opt.clear(); + _forced_arg = cur; + } + else + { + _apply_long_arg(cur); + } + } + else + { + _short_opts = cur + 1; + goto retry; + } + } + else + { + _opt.clear(); + _forced_arg = cur; + } + + return true; + } + + friend bool operator==(command_line_reader const & lhs, std::string_view rhs) noexcept + { + return lhs._opt == rhs; + } + + std::string pop_string() + { + if (_forced_arg) + return to_utf8(std::exchange(_forced_arg, nullptr)); + + if (_idx >= _argc) + throw std::runtime_error("XXX expected an argument"); + + return to_utf8(_argv[_idx++]); + } + + void pop_path(std::filesystem::path & out) + { + if (!out.empty()) + throw std::runtime_error("XXX argument already specified"); + + out = this->pop_path(); + if (out.empty()) + throw std::runtime_error("XXX argument must be non-empty"); + } + + std::filesystem::path pop_path() + { + if (_forced_arg) + return std::exchange(_forced_arg, nullptr); + + if (_idx >= _argc) + throw std::runtime_error("XXX expected an argument"); + + return _argv[_idx++]; + } + +private: + void _apply_long_arg(wchar_t const * arg) + { + wchar_t const * p = arg + 2; + for (;;) + { + switch (*p) + { + case '=': + _forced_arg = p + 1; + [[fallthrough]]; + + case 0: + _opt = to_utf8({ arg, size_t(p - arg) }); + return; + + default: + ++p; + } + } + } + + struct _win32_local_deleter + { + void operator()(void const * p) + { + if (p) + ::LocalFree((HLOCAL)p); + } + }; + + int _argc; + int _idx = 0; + std::unique_ptr _argv; + + bool _parse_opts = true; + wchar_t const * _short_opts = L""; + wchar_t const * _forced_arg = nullptr; + + std::string _opt; + std::filesystem::path _arg0; +}; + +#pragma once diff --git a/src/comptr.h b/src/comptr.h new file mode 100644 index 0000000..9eba69f --- /dev/null +++ b/src/comptr.h @@ -0,0 +1,83 @@ +#include "hr.h" +#include +#include + +template T> +struct comptr +{ + comptr() noexcept + : _ptr(nullptr) + { + } + + explicit comptr(T * ptr) noexcept + : _ptr(ptr) + { + } + + explicit operator bool() const noexcept + { + return _ptr != nullptr; + } + + T * get() const noexcept + { + return _ptr; + } + + T * operator->() const noexcept + { + return this->get(); + } + + T ** operator~() noexcept + { + this->reset(); + return &_ptr; + } + + template Q> + comptr query() const + { + if (!_ptr) + return {}; + void * r; + hrtry _ptr->QueryInterface(__uuidof(Q), &r); + return comptr(static_cast(r)); + } + + void reset(T * ptr = nullptr) noexcept + { + if (_ptr) + _ptr->Release(); + _ptr = ptr; + } + + ~comptr() + { + this->reset(); + } + + comptr(comptr const & o) noexcept + : _ptr(o._ptr) + { + if (_ptr) + _ptr->AddRef(); + } + + comptr(comptr && o) noexcept + : _ptr(std::exchange(o._ptr, nullptr)) + { + } + + comptr & operator=(comptr o) noexcept + { + std::swap(_ptr, o._ptr); + return *this; + } + +private: + T * _ptr; +}; + +#pragma once diff --git a/src/hr.h b/src/hr.h new file mode 100644 index 0000000..64ddad0 --- /dev/null +++ b/src/hr.h @@ -0,0 +1,16 @@ +#include +#include + +struct _hresult_checker +{ + HRESULT operator%(HRESULT hr) const + { + if (FAILED(hr)) + throw std::system_error(hr, std::system_category()); + return hr; + } +}; + +#define hrtry ::_hresult_checker() % + +#pragma once diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3c30efb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,429 @@ +#include "cmdline.h" +#include "comptr.h" +#include "hr.h" +#include "pcapng.h" +#include "registry.h" +#include "sigint.h" +#include "utf8.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace Microsoft_Windows_NDIS_PacketCapture { + static constexpr GUID id = { 0x2ED6006E, 0x4729, 0x4609, { 0xB4, 0x23, 0x3E, 0xE7, 0xBC, 0xD6, 0x78, 0xEF } }; + + enum: uint32_t + { + packet_fragment = 1001, + }; +} + +template +static T read_ne(std::span & data) +{ + if (data.size() < sizeof(T)) + throw std::runtime_error("invalid"); + T r; + memcpy(&r, data.data(), sizeof(T)); + data = data.subspan(sizeof(T)); + return r; +} + +struct ndis_packetcapture_consumer +{ + ndis_packetcapture_consumer(std::shared_ptr writer, size_t snaplen) + : _writer(std::move(writer)), _snaplen(snaplen) + { + } + + void push_trace(PEVENT_RECORD event) + { + if (event->EventHeader.ProviderId != Microsoft_Windows_NDIS_PacketCapture::id) + return; + + auto const & ed = event->EventHeader.EventDescriptor; + std::span data = { (std::byte const *)event->UserData, event->UserDataLength }; + + switch ((ed.Version << 16) | ed.Id) + { + case Microsoft_Windows_NDIS_PacketCapture::packet_fragment: + { + auto miniport_intf_index = read_ne(data); + auto lower_intf_index = read_ne(data); + auto fragment_size = read_ne(data); + + auto it = _intfs.find(miniport_intf_index); + if (it == _intfs.end()) + { + MIB_IFROW row = {}; + row.dwIndex = miniport_intf_index; + if (GetIfEntry(&row) != 0) + return; + + auto ifidx = _writer->add_interface((uint16_t)row.dwType, to_utf8(row.wszName), + (char const *)row.bDescr, _snaplen); + it = _intfs.emplace(miniport_intf_index, ifidx).first; + } + + if (data.size() < fragment_size) + throw std::runtime_error("invalid"); + + std::span fragment(data.data(), fragment_size); + data = data.subspan(fragment_size); + _writer->add_packet(it->second, (event->EventHeader.TimeStamp.QuadPart / 10) - 11644473600000000l, + fragment.subspan(0, (std::min)(fragment.size(), _snaplen)), fragment.size()); + break; + } + } + } + +private: + std::shared_ptr _writer; + std::map _intfs; + size_t _snaplen; +}; + + +template +void foreach_net_binding(LPCWSTR component_name, F && fn) +{ + comptr netcfg; + hrtry CoCreateInstance(CLSID_CNetCfg, nullptr, CLSCTX_INPROC_SERVER, IID_INetCfg, (void **)~netcfg); + + auto lock = netcfg.query(); + hrtry lock->AcquireWriteLock(5000, L"ndisdump", nullptr); + + hrtry netcfg->Initialize(nullptr); + + comptr ndiscap; + hrtry netcfg->FindComponent(component_name, ~ndiscap); + + auto bindings = ndiscap.query(); + + comptr binding_paths; + hrtry bindings->EnumBindingPaths(EBP_ABOVE, ~binding_paths); + + for (;;) + { + comptr path; + + ULONG fetched; + auto hr = hrtry binding_paths->Next(1, ~path, &fetched); + if (hr == S_FALSE) + break; + + fn(path); + } + + hrtry netcfg->Apply(); + hrtry lock->ReleaseWriteLock(); +} + +struct _ndiscap_sentry +{ + _ndiscap_sentry() + : _key(win32_reg_handle::open_key(HKEY_LOCAL_MACHINE, LR"(SYSTEM\CurrentControlSet\Services\NdisCap\Parameters)", KEY_QUERY_VALUE | KEY_SET_VALUE)) + { + uint32_t refcount = _key.query_dword(L"RefCount", 0); + _key.set_dword(L"RefCount", refcount + 1); + + foreach_net_binding(L"ms_ndiscap", [](comptr const & path) { + hrtry path->Enable(TRUE); + }); + } + + ~_ndiscap_sentry() + { + uint32_t refcount = _key.query_dword(L"RefCount", 0); + _key.set_dword(L"RefCount", refcount - 1); + + if (refcount == 1) + { + foreach_net_binding(L"ms_ndiscap", [](comptr const & path) { + hrtry path->Enable(FALSE); + }); + } + } + +private: + win32_reg_handle _key; +}; + +struct service_handle +{ + explicit service_handle(SC_HANDLE h) noexcept + : _h(h) + { + } + + explicit operator bool() const noexcept + { + return _h != nullptr; + } + + SC_HANDLE get() const noexcept + { + return _h; + } + + ~service_handle() + { + if (_h) + CloseServiceHandle(_h); + } + + service_handle(service_handle && o) noexcept + : _h(std::exchange(o._h, nullptr)) + { + } + + service_handle & operator=(service_handle o) noexcept + { + std::swap(_h, o._h); + return *this; + } + +private: + SC_HANDLE _h; +}; + +static void _start_service(LPCWSTR name) +{ + service_handle scman(OpenSCManagerW(nullptr, SERVICES_ACTIVE_DATABASEW, SC_MANAGER_CONNECT)); + if (!scman) + { + DWORD err = GetLastError(); + throw std::system_error(err, std::system_category()); + } + + service_handle service(OpenServiceW(scman.get(), name, SERVICE_QUERY_STATUS)); + if (!service) + { + DWORD err = GetLastError(); + throw std::system_error(err, std::system_category()); + } + + SERVICE_STATUS st = {}; + if (!QueryServiceStatus(service.get(), &st)) + { + DWORD err = GetLastError(); + throw std::system_error(err, std::system_category()); + } + + if (st.dwCurrentState == SERVICE_RUNNING) + return; + + service = service_handle(OpenServiceW(scman.get(), name, SERVICE_QUERY_STATUS | SERVICE_START)); + if (!service) + { + DWORD err = GetLastError(); + throw std::system_error(err, std::system_category()); + } + + while (st.dwCurrentState != SERVICE_RUNNING) + { + if (st.dwCurrentState == SERVICE_STOPPED) + { + if (!StartServiceW(service.get(), 0, nullptr)) + { + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_ALREADY_RUNNING) + throw std::system_error(err, std::system_category()); + } + } + + Sleep(st.dwWaitHint); + + if (!QueryServiceStatus(service.get(), &st)) + { + DWORD err = GetLastError(); + throw std::system_error(err, std::system_category()); + } + } +} + +static int _real_main(int argc, char * argv[]) +{ + hrtry CoInitialize(nullptr); + + std::filesystem::path out_path; + int snaplen = 262144; + std::string expr; + bool list_interfaces = false; + + command_line_reader clr(argc, argv); + auto print_help = [&] { + printf("Usage: %s [OPTIONS] -w FILE [EXPR ...]\n", clr.arg0().stem().string().c_str()); + }; + + while (clr.next()) + { + if (clr == "-D" || clr == "--list-interfaces") + { + list_interfaces = true; + } + else if (clr == "-w") + { + clr.pop_path(out_path); + } + else if (clr == "-s" || clr == "--snapshot-length") + { + snaplen = std::stoi(clr.pop_string()); + if (snaplen <= 0) + snaplen = 262144; + } + else if (clr == "") + { + if (!expr.empty()) + expr.push_back(' '); + expr.append(clr.pop_string()); + } + else if (clr == "-h" || clr == "--help") + { + print_help(); + return 0; + } + else + { + print_help(); + return 2; + } + } + + if (list_interfaces) + { + ULONG size; + DWORD err = GetIfTable(nullptr, &size, TRUE); + if (err != ERROR_INSUFFICIENT_BUFFER) + throw std::system_error(err, std::system_category()); + + std::vector buf(size); + err = GetIfTable((PMIB_IFTABLE)buf.data(), &size, TRUE); + if (err != NO_ERROR) + throw std::system_error(err, std::system_category()); + + PMIB_IFTABLE iftable = (PMIB_IFTABLE)buf.data(); + + for (DWORD i = 0; i != iftable->dwNumEntries; ++i) + { + MIB_IFROW const & row = iftable->table[i]; + if (row.dwType == IF_TYPE_ETHERNET_CSMACD) + printf("[%u] %s\n", row.dwIndex, row.bDescr); + } + + return 0; + } + + if (out_path.empty()) + { + print_help(); + return 2; + } + + _start_service(L"ndiscap"); + _ndiscap_sentry ndiscap; + + alignas(EVENT_TRACE_PROPERTIES) std::byte buf[sizeof(EVENT_TRACE_PROPERTIES) + 2048] = {}; + EVENT_TRACE_PROPERTIES * etp = (EVENT_TRACE_PROPERTIES *)buf; + + ULONG err; + TRACEHANDLE etw_session; + for (;;) + { + etp->Wnode.BufferSize = sizeof buf; + etp->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + etp->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + etp->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + etp->LoggerNameOffset = etp->LogFileNameOffset + 1024; + + err = StartTraceW(&etw_session, L"wncap", etp); + if (err == ERROR_ALREADY_EXISTS) + { + ControlTraceW(0, L"wncap", etp, EVENT_TRACE_CONTROL_STOP); + continue; + } + + if (err == ERROR_SUCCESS) + break; + + return err; + } + + err = EnableTraceEx(&Microsoft_Windows_NDIS_PacketCapture::id, nullptr, etw_session, TRUE, 0xff, 0xffff'ffff'ffff'ffff, 0, 0, nullptr); + + std::shared_ptr w; + w = std::make_shared(out_path); + + ndis_packetcapture_consumer consumer(w, snaplen); + + struct consume_ctx_t + { + TRACEHANDLE h; + ndis_packetcapture_consumer * consumer; + }; + + consume_ctx_t consume_ctx = { + .consumer = &consumer, + }; + + EVENT_TRACE_LOGFILEW logfile = {}; + logfile.LoggerName = (LPWSTR)L"wncap"; + logfile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; + logfile.Context = &consume_ctx; + logfile.EventRecordCallback = [](PEVENT_RECORD EventRecord) { + auto * ctx = (consume_ctx_t *)EventRecord->UserContext; + try + { + ctx->consumer->push_trace(EventRecord); + } + catch (std::exception const & e) + { + CloseTrace(ctx->h); + fprintf(stderr, "error: %s\n", e.what()); + } + }; + + { + consume_ctx.h = OpenTraceW(&logfile); + sigint_handler sigint([&] { + CloseTrace(consume_ctx.h); + }); + + ProcessTrace(&consume_ctx.h, 1, nullptr, nullptr); + } + + ControlTraceW(etw_session, nullptr, etp, EVENT_TRACE_CONTROL_STOP); + return 0; +} + +int main(int argc, char * argv[]) +{ + try + { + return _real_main(argc, argv); + } + catch (std::exception const & e) + { + fprintf(stderr, "error: %s\n", e.what()); + return 1; + } + catch (...) + { + fprintf(stderr, "error: unknown error\n"); + return 1; + } +} diff --git a/src/ndisdump.rc.in b/src/ndisdump.rc.in new file mode 100644 index 0000000..814e904 --- /dev/null +++ b/src/ndisdump.rc.in @@ -0,0 +1,16 @@ +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ${ndisdump_VERSION_MAJOR},${ndisdump_VERSION_MINOR},0,${ndisdump_VERSION_PATCH} + PRODUCTVERSION ${ndisdump_VERSION_MAJOR},${ndisdump_VERSION_MINOR},0,${ndisdump_VERSION_PATCH} + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifndef NDEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0 +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE VFT2_UNKNOWN +BEGIN +END diff --git a/src/pcapng.h b/src/pcapng.h new file mode 100644 index 0000000..883c8f1 --- /dev/null +++ b/src/pcapng.h @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +template +concept payload + = !std::convertible_to> + && !std::convertible_to; + +struct pcapng_writer +{ + pcapng_writer(std::filesystem::path const & path) + { + _h = CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, 0, nullptr); + if (_h == INVALID_HANDLE_VALUE) + throw std::system_error(GetLastError(), std::system_category()); + + _new_block(0x0a0d0d0a); + _append(_section_header_t{ + .magic = 0x1a2b3c4d, + .major_version = 1, + .minor_version = 0, + .section_length = -1, + }); + _end_block(); + } + + ~pcapng_writer() + { + if (_h != INVALID_HANDLE_VALUE) + CloseHandle(_h); + } + + uint32_t add_interface(uint16_t link_type, std::string name, std::string desc, size_t snaplen) + { + switch (link_type) + { + case 6: + link_type = 1; + break; + case 71: + link_type = 105; + break; + } + + uint32_t r = _intf_count++; + _interface_desc_t intf = { + .link_type = link_type, + .snaplen = (uint32_t)snaplen, + }; + + _new_block(1); + _append(intf); + _opt(2, name); + _opt(3, desc); + _opt(0, std::span{}); + _end_block(); + return r; + } + + void add_packet(uint32_t ifidx, uint64_t timestamp, std::span payload, size_t full_length) + { + _new_block(6); + _append(_enhanced_packet_t{ + .intf_id = ifidx, + .timestamp_hi = (uint32_t)(timestamp >> 32), + .timestamp_lo = (uint32_t)timestamp, + .captured_len = (uint32_t)payload.size(), + .packet_len = (uint32_t)full_length, + }); + _append(payload); + _pad(); + _opt(0, std::span{}); + _end_block(); + } + +private: + void _pad() + { + _buf.resize((_buf.size() + 3) & ~3); + } + + void _new_block(uint32_t type) + { + _buf.clear(); + this->_append(type); + this->_append((uint32_t)0); + } + + void _end_block() + { + uint32_t len = (uint32_t)(_buf.size() + 4); + _append(len); + *(uint32_t *)&_buf[4] = len; + + _write(_buf); + } + + void _append(std::span data) + { + _buf.insert(_buf.end(), data.begin(), data.end()); + } + + template + void _append(T const & t) + { + this->_append(std::as_bytes(std::span{ &t, 1 })); + } + + void _opt(uint16_t type, std::span payload) + { + this->_append(type); + this->_append((uint16_t)payload.size()); + this->_append(payload); + _pad(); + } + + template + void _opt(uint16_t type, T const & payload) + { + this->_opt(type, std::as_bytes({ &payload, 1 })); + } + + void _opt(uint16_t type, std::string_view payload) + { + this->_opt(type, std::as_bytes(std::span{ payload.data(), payload.size() })); + } + + template + void _write(T const & data) + { + this->_write(std::as_bytes(std::span{ &data, 1 })); + } + + void _write(std::span data) + { + OVERLAPPED ov = {}; + ov.Offset = 0xffffffff; + ov.OffsetHigh = 0xffffffff; + while (!data.empty()) + { + DWORD written; + if (!WriteFile(_h, data.data(), (DWORD)data.size(), &written, &ov)) + throw std::system_error(GetLastError(), std::system_category()); + + data = data.subspan(written); + } + } + + struct _section_header_t + { + uint32_t magic; + uint16_t major_version; + uint16_t minor_version; + int64_t section_length; + }; + + struct _interface_desc_t + { + uint16_t link_type; + uint16_t _0a; + uint32_t snaplen; + }; + + struct _enhanced_packet_t + { + uint32_t intf_id; + uint32_t timestamp_hi; + uint32_t timestamp_lo; + uint32_t captured_len; + uint32_t packet_len; + }; + + + std::vector _buf; + + HANDLE _h; + uint32_t _intf_count = 0; +}; + +#pragma once diff --git a/src/registry.h b/src/registry.h new file mode 100644 index 0000000..9d5bfb4 --- /dev/null +++ b/src/registry.h @@ -0,0 +1,78 @@ +#include +#include +#include + +struct win32_reg_handle +{ + static win32_reg_handle open_key(HKEY root, LPCWSTR key_name, DWORD desired_access) + { + HKEY key; + DWORD err = RegOpenKeyExW(root, key_name, 0, desired_access, &key); + if (err != ERROR_SUCCESS) + throw std::system_error(err, std::system_category()); + return win32_reg_handle(key); + } + + win32_reg_handle() noexcept + : _h(nullptr) + { + } + + explicit win32_reg_handle(HKEY h) noexcept + : _h(h) + { + } + + HKEY get() const noexcept + { + return _h; + } + + uint32_t query_dword(LPCWSTR value_name, uint32_t def) const + { + DWORD type; + uint32_t r; + DWORD size = sizeof r; + DWORD err = RegQueryValueExW(_h, value_name, nullptr, &type, (LPBYTE)&r, &size); + + if (err == ERROR_FILE_NOT_FOUND) + return def; + + if (err != ERROR_SUCCESS) + throw std::system_error(err, std::system_category()); + + if (type != REG_DWORD) + throw std::system_error(ERROR_INVALID_PARAMETER, std::system_category()); + + return r; + } + + void set_dword(LPCWSTR value_name, uint32_t value) + { + auto err = RegSetValueExW(_h, value_name, 0, REG_DWORD, (BYTE const *)&value, sizeof value); + if (err != ERROR_SUCCESS) + throw std::system_error(err, std::system_category()); + } + + ~win32_reg_handle() + { + if (_h) + RegCloseKey(_h); + } + + win32_reg_handle(win32_reg_handle && o) noexcept + : _h(std::exchange(o._h, nullptr)) + { + } + + win32_reg_handle & operator=(win32_reg_handle o) noexcept + { + std::swap(_h, o._h); + return *this; + } + +private: + HKEY _h; +}; + +#pragma once diff --git a/src/sigint.h b/src/sigint.h new file mode 100644 index 0000000..1db94ac --- /dev/null +++ b/src/sigint.h @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +#include + +struct sigint_handler +{ + sigint_handler(std::function cb) + : _cb(std::move(cb)), _is_armed(true) + { + _lock_t lock; + + bool need_handlers = _cbs.empty(); + + _cbs.push_back(this); + _cb_iter = std::prev(_cbs.end()); + + if (need_handlers) + { + if (!SetConsoleCtrlHandler(&console_ctrl_handler, TRUE)) + { + DWORD err = GetLastError(); + throw std::system_error(err, std::system_category()); + } + } + } + + ~sigint_handler() + { + _lock_t lock; + this->_disarm_locked(); + } + + sigint_handler(sigint_handler const &) = delete; + sigint_handler & operator=(sigint_handler const &) = delete; + +private: + static BOOL WINAPI console_ctrl_handler(DWORD dwCtrlType) + { + if (dwCtrlType != CTRL_C_EVENT) + return FALSE; + + _lock_t lock; + if (_cbs.empty()) + return FALSE; + + sigint_handler * h = _cbs.front(); + + h->_disarm_locked(); + h->_cb(); + return TRUE; + } + + void _disarm_locked() + { + if (_is_armed) + { + _cbs.erase(_cb_iter); + _is_armed = false; + } + + if (_cbs.empty()) + SetConsoleCtrlHandler(&console_ctrl_handler, FALSE); + } + + std::function _cb; + + bool _is_armed; + std::list::iterator _cb_iter; + + struct _lock_t + { + _lock_t() + { + AcquireSRWLockExclusive(&_cbs_mutex); + } + + ~_lock_t() + { + ReleaseSRWLockExclusive(&_cbs_mutex); + } + }; + + inline static SRWLOCK _cbs_mutex = {}; + inline static std::list _cbs = {}; +}; + +#pragma once diff --git a/src/utf8.h b/src/utf8.h new file mode 100644 index 0000000..4dfb3eb --- /dev/null +++ b/src/utf8.h @@ -0,0 +1,21 @@ +#include +#include +#include + +#include + +std::string to_utf8(std::wstring_view s) +{ + int r = WideCharToMultiByte(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0, nullptr, nullptr); + if (r < 0) + throw std::system_error(GetLastError(), std::system_category()); + std::string ss; + ss.resize(r + 1); + r = WideCharToMultiByte(CP_UTF8, 0, s.data(), (int)s.size(), ss.data(), (int)ss.size(), nullptr, nullptr); + if (r < 0) + throw std::system_error(GetLastError(), std::system_category()); + ss.resize(r); + return ss; +} + +#pragma once