├── .dockerignore ├── .github └── workflows │ ├── ci.yaml │ └── docs.yaml ├── .gitignore ├── .gitmodules ├── .nvmrc ├── CMakeLists.txt ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── assets └── s1.svg ├── cxx ├── CMakeLists.txt ├── Sys.hpp ├── arp │ ├── Arp.cpp │ ├── Arp.hpp │ ├── CMakeLists.txt │ ├── Platform.cpp │ ├── macOS.cpp │ ├── macOS.hpp │ ├── win32.cpp │ └── win32.hpp ├── bpf-filter │ ├── BpfFilter.cpp │ ├── BpfFilter.hpp │ └── CMakeLists.txt ├── checksums │ ├── CMakeLists.txt │ ├── Checksums.cpp │ └── Checksums.hpp ├── common.hpp ├── converters │ ├── CMakeLists.txt │ ├── Converters.cpp │ └── Converters.hpp ├── enums │ ├── CMakeLists.txt │ ├── Enums.cpp │ └── Enums.hpp ├── error │ ├── CMakeLists.txt │ ├── Error.cpp │ └── Error.hpp ├── example │ ├── CMakeLists.txt │ ├── Example.cpp │ └── Example.hpp ├── main.cpp ├── routing │ ├── CMakeLists.txt │ ├── Platform.cpp │ ├── Routing.cpp │ ├── Routing.hpp │ ├── macOS.cpp │ ├── macOS.hpp │ ├── win32.cpp │ └── win32.hpp ├── toCxx.cpp └── transports │ ├── pcap │ ├── CMakeLists.txt │ ├── Pcap.cpp │ └── Pcap.hpp │ └── socket │ ├── CMakeLists.txt │ ├── Enums │ ├── Enums.cpp │ ├── Enums.hpp │ ├── Linux.cpp │ ├── MacOS.cpp │ └── Windows.cpp │ ├── InputPackets.cpp │ ├── InputPackets.hpp │ ├── Packets.cpp │ ├── Packets.hpp │ ├── SockAddr.cpp │ ├── SockAddr.hpp │ ├── Socket.cpp │ └── Socket.hpp ├── jsdoc.json ├── lib ├── .gitignore ├── af.js ├── arp.js ├── bindings.js ├── bpfFilter.js ├── buffer.js ├── converters.js ├── defaults.js ├── enums.js ├── index.js ├── layers │ ├── ARP.js │ ├── Ethernet.js │ ├── IPv4.js │ ├── Payload.js │ ├── TCP.js │ ├── TLV.js │ ├── UDP.js │ ├── child.js │ ├── enums.js │ ├── exampleLayer.js │ ├── index.js │ ├── mac.js │ ├── mixins.js │ └── osi.js ├── layersList.js ├── liveDevice.js ├── packet.js ├── pcapFile │ ├── index.js │ ├── pcap.js │ ├── pcapng │ │ ├── const.js │ │ ├── index.js │ │ ├── readers │ │ │ ├── empty.js │ │ │ ├── enhancedPacket.js │ │ │ ├── index.js │ │ │ ├── interfaceDescription.js │ │ │ ├── option.js │ │ │ ├── packetBlock.js │ │ │ ├── pcapng.js │ │ │ ├── sectionBlock.js │ │ │ └── simplePacket.js │ │ └── tsresol.js │ ├── reader.js │ └── structs.js ├── pick.js ├── routing.js ├── socket.js ├── struct.js └── timestamp.js ├── package-lock.json ├── package.json └── test ├── Ethernet.test.js ├── IPv4.test.js ├── TCP.test.js ├── TLV.test.js ├── UDP.test.js ├── af.test.js ├── arp.test.js ├── arpTable.test.js ├── bpfFilter.test.js ├── converters.test.js ├── data ├── example1.pcap ├── example1.pcapng └── example2.pcapng ├── liveDevice.test.js ├── packet.test.js ├── pcapFile.test.js ├── routing.test.js ├── test.pcapng ├── timestamp.test.js └── tshark.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.md 3 | assets 4 | build 5 | Dockerfile 6 | docs 7 | memory.log 8 | node_modules 9 | notes.txt 10 | out 11 | prebuilds 12 | scr.sh 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: "build" 2 | 3 | on: 4 | push: 5 | branches: 6 | - release 7 | 8 | jobs: 9 | draft_release: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | outputs: 13 | upload_url: ${{ steps.create_release.outputs.upload_url }} 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | - name: Create Release 18 | id: create_release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: Release ${{ github.ref }} 25 | body: | 26 | 🚀 This release contains necessary binaries to install over-the-wire on different platforms. See README.md of this repo for more details. 27 | draft: true 28 | prerelease: false 29 | 30 | build: 31 | needs: [draft_release] 32 | name: "Build" 33 | strategy: 34 | matrix: 35 | os: [ubuntu-latest, windows-latest, macos-latest, ubuntu-24.04-arm] 36 | runs-on: ${{ matrix.os }} 37 | steps: 38 | - name: Checkout code 39 | uses: actions/checkout@v4 40 | 41 | - name: Install coreutils for macOS 42 | if: matrix.os == 'macos-latest' 43 | run: brew install coreutils 44 | 45 | - name: Install Zip 46 | if: matrix.os == 'windows-latest' 47 | run: choco install zip 48 | 49 | - name: Set up Python 50 | uses: actions/setup-python@v5 51 | with: 52 | python-version: '3.x' 53 | 54 | - name: Install libpcap on Ubuntu 55 | if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm' 56 | run: | 57 | sudo apt-get update 58 | sudo apt-get install -y libpcap-dev 59 | 60 | - name: Install libpcap on macOS 61 | if: matrix.os == 'macos-latest' 62 | run: | 63 | brew update 64 | brew install libpcap 65 | 66 | - name: Install npcap on Windows 67 | if: matrix.os == 'windows-latest' 68 | run: | 69 | choco install wget --no-progress 70 | mkdir -p npcap 71 | wget "https://npcap.com/dist/npcap-sdk-1.13.zip" 72 | unzip npcap-sdk-1.13.zip -d npcap 73 | shell: bash 74 | 75 | - name: Install npcap on Windows (step 2) 76 | if: matrix.os == 'windows-latest' 77 | run: | 78 | set NPCAP_FILE=npcap-0.96.exe 79 | curl -L https://npcap.com/dist/%NPCAP_FILE% --output %NPCAP_FILE% 80 | %NPCAP_FILE% /S /winpcap_mode 81 | xcopy C:\Windows\System32\Npcap\*.dll C:\Windows\System32 82 | xcopy C:\Windows\SysWOW64\Npcap\*.dll C:\Windows\SysWOW64 83 | shell: cmd 84 | 85 | - name: Submodule update 86 | run: git submodule update --init --recursive 87 | shell: bash 88 | 89 | - name: Build and Test (Windows) 90 | if: matrix.os == 'windows-latest' 91 | run: | 92 | ls "$(pwd)/npcap" 93 | PCAP_ROOT="$(pwd)/npcap" npm i 94 | PCAP_ROOT="$(pwd)/npcap" npm run build 95 | node --test test/liveDevice.test.js test/arpTable.test.js test/routing.test.js 96 | shell: bash 97 | 98 | - name: Build and Test 99 | if: matrix.os != 'windows-latest' 100 | run: | 101 | npm i 102 | npm run build 103 | npm run test 104 | shell: bash 105 | 106 | - name: Prebuild (Windows) 107 | if: matrix.os == 'windows-latest' 108 | run: | 109 | ls "$(pwd)/npcap" 110 | PCAP_ROOT="$(pwd)/npcap" npm run precompile 111 | ls prebuilds 112 | zip -r prebuilds-${{ matrix.os }}.zip prebuilds 113 | shell: bash 114 | 115 | - name: Prebuild 116 | if: matrix.os != 'windows-latest' 117 | run: | 118 | npm run precompile 119 | ls prebuilds 120 | zip -r prebuilds-${{ matrix.os }}.zip prebuilds 121 | shell: bash 122 | 123 | - name: ls 124 | run: ls 125 | shell: bash 126 | 127 | - name: Upload 128 | uses: actions/upload-release-asset@v1 129 | env: 130 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 131 | with: 132 | upload_url: ${{ needs.draft_release.outputs.upload_url }} 133 | asset_path: prebuilds-${{ matrix.os }}.zip 134 | asset_name: prebuilds-${{ matrix.os }}.zip 135 | asset_content_type: application/zip 136 | 137 | alpine: 138 | needs: [draft_release] 139 | name: "Build Alpine" 140 | strategy: 141 | matrix: 142 | os: [ubuntu-latest, ubuntu-24.04-arm] 143 | runs-on: ${{ matrix.os }} 144 | steps: 145 | - name: Checkout code 146 | uses: actions/checkout@v4 147 | 148 | - name: Submodule update 149 | run: git submodule update --init --recursive 150 | shell: bash 151 | 152 | - name: Prebuild 153 | run: | 154 | docker buildx build . --output prebuilds 155 | ls prebuilds 156 | zip -r prebuilds-${{ matrix.os }}.zip prebuilds 157 | 158 | - name: Upload 159 | uses: actions/upload-release-asset@v1 160 | env: 161 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 162 | with: 163 | upload_url: ${{ needs.draft_release.outputs.upload_url }} 164 | asset_path: prebuilds-${{ matrix.os }}.zip 165 | asset_name: prebuilds-${{ matrix.os }}.zip 166 | asset_content_type: application/zip 167 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Generate and Deploy JSDoc 2 | 3 | on: 4 | push: 5 | branches: 6 | - release 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | docs: 15 | environment: 16 | name: github-pages 17 | url: ${{ steps.deployment.outputs.page_url }} 18 | 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v3 24 | 25 | - name: Install libpcap on Ubuntu 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install -y libpcap-dev 29 | 30 | - name: Set up Node.js 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: '18' 34 | 35 | - name: Submodule update 36 | run: git submodule update --init --recursive 37 | shell: bash 38 | 39 | - name: Install dependencies 40 | run: npm ci 41 | 42 | - name: Generate JSDoc 43 | run: npm run generate-docs 44 | 45 | - name: Copy assets 46 | run: cp -r assets docs 47 | 48 | - name: Setup Pages 49 | uses: actions/configure-pages@v5 50 | 51 | - name: Upload artifact 52 | uses: actions/upload-pages-artifact@v3 53 | with: 54 | path: './docs' 55 | - name: Deploy to GitHub Pages 56 | id: deployment 57 | uses: actions/deploy-pages@v4 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | scr.sh 4 | memory.log 5 | notes.txt 6 | out 7 | docs 8 | prebuilds 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cxx_modules/PcapPlusPlus"] 2 | path = cxx_modules/PcapPlusPlus 3 | url = https://github.com/seladb/PcapPlusPlus/ 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v21.5.0 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | cmake_policy(SET CMP0042 NEW) 3 | set (CMAKE_CXX_STANDARD 17) 4 | 5 | execute_process(COMMAND node -p "require('node-addon-api').include" 6 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 7 | OUTPUT_VARIABLE NODE_ADDON_API_DIR 8 | ) 9 | 10 | execute_process(COMMAND node -p "require('os').arch()" 11 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 12 | OUTPUT_VARIABLE _CURRENT_ARCH 13 | ) 14 | 15 | string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) 16 | string(REGEX REPLACE "[\r\n\"]" "" _CURRENT_ARCH ${_CURRENT_ARCH}) 17 | 18 | Message("NODE_ADDON_API_DIR: " ${NODE_ADDON_API_DIR}) 19 | Message("_CURRENT_ARCH: " ${_CURRENT_ARCH}) 20 | 21 | include_directories(${CMAKE_JS_INC}) 22 | 23 | project (over-the-wire) 24 | 25 | if (WIN32) 26 | add_compile_options(/WX-) 27 | add_definitions(-D_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS=1) 28 | message("Silencing MS warnings") 29 | endif() 30 | 31 | add_library(${PROJECT_NAME} SHARED ${CMAKE_JS_SRC}) 32 | target_include_directories(${PROJECT_NAME} PRIVATE "${NODE_ADDON_API_DIR}") 33 | target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/cxx") 34 | 35 | if(DEFINED ENV{PCAP_ROOT}) 36 | set(PCAP_ROOT $ENV{PCAP_ROOT}) 37 | message("PCAP_ROOT: ${PCAP_ROOT}") 38 | add_definitions(-DPCAP_ROOT="${PCAP_ROOT}") 39 | else() 40 | message(WARNING "PCAP_ROOT is not set in the environment. Default paths will be used.") 41 | endif() 42 | 43 | if(PCAP_ROOT) 44 | target_include_directories(${PROJECT_NAME} PRIVATE "${PCAP_ROOT}/Include") 45 | 46 | set(PACKET_LIB "${PCAP_ROOT}/Lib/${_CURRENT_ARCH}/Packet.lib") 47 | set(WPCAP_LIB "${PCAP_ROOT}/Lib/${_CURRENT_ARCH}/wpcap.lib") 48 | 49 | file(TO_NATIVE_PATH "${PACKET_LIB}" PACKET_LIB) 50 | file(TO_NATIVE_PATH "${WPCAP_LIB}" WPCAP_LIB) 51 | 52 | message("PACKET_LIB: " ${PACKET_LIB}) 53 | message("WPCAP_LIB: " ${WPCAP_LIB}) 54 | 55 | if(EXISTS ${PACKET_LIB} AND EXISTS ${WPCAP_LIB}) 56 | message("LINKING WITH: ${PACKET_LIB} ${WPCAP_LIB}") 57 | #target_link_libraries(${PROJECT_NAME} PUBLIC "${PACKET_LIB}" "${WPCAP_LIB}") 58 | else() 59 | message(FATAL_ERROR "Required libraries not found. Ensure that PCAP_ROOT is set correctly.") 60 | endif() 61 | 62 | endif() 63 | 64 | # PcapPlusPlus 65 | add_subdirectory(cxx_modules/PcapPlusPlus) 66 | add_dependencies(${PROJECT_NAME} Pcap++) 67 | include_directories(cxx_modules/PcapPlusPlus/Pcap++/header) 68 | include_directories(cxx_modules/PcapPlusPlus/Packet++/header) 69 | include_directories(cxx_modules/PcapPlusPlus/Common++/header) 70 | target_link_libraries(${PROJECT_NAME} PUBLIC Pcap++) 71 | 72 | add_subdirectory(cxx/) 73 | add_subdirectory(cxx/example) 74 | add_subdirectory(cxx/transports/pcap) 75 | add_subdirectory(cxx/transports/socket) 76 | add_subdirectory(cxx/bpf-filter) 77 | add_subdirectory(cxx/enums) 78 | add_subdirectory(cxx/checksums) 79 | add_subdirectory(cxx/arp) 80 | add_subdirectory(cxx/routing) 81 | add_subdirectory(cxx/error) 82 | add_subdirectory(cxx/converters) 83 | 84 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") 85 | 86 | target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_JS_LIB}) 87 | 88 | # define NPI_VERSION 89 | add_definitions(-DNAPI_VERSION=6) 90 | 91 | if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET) 92 | # Generate node.lib 93 | execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) 94 | endif() 95 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Over-the-Wire 2 | 3 | We're thrilled that you're interested in helping with the development of Over-the-Wire! This document provides guidelines for contributing to the project and how to submit changes. 4 | 5 | ## Reporting Bugs or Requesting Features 6 | 7 | If you spot a bug or want to request a new feature, please start by searching our [Issues](https://github.com/vaguue/over-the-wire/issues) to see if it has already been reported. If it hasn't, feel free to open a new issue. Please provide as much information as possible to help us understand the problem or the nature of the feature you're proposing. 8 | 9 | ## Contributing Code 10 | 11 | Want to contribute code to fix a bug or add a new feature? Great! Here's how you can do it: 12 | 13 | 1. **Find an Issue**: Look through the [open issues](https://github.com/vaguue/over-the-wire/issues) for bugs or features that need help. If you're new to the project, look for issues tagged with "good first issue." 14 | 15 | 2. **Create an Issue**: If you want to add a feature or fix something that isn't yet reported, please open a new issue first. This lets us discuss the potential changes and how they fit into the project's direction. 16 | 17 | 3. **Fork & Clone**: Once you've identified an issue you'd like to tackle, fork the repository and clone it to your local machine. 18 | 19 | 4. **Make Your Changes**: Work on the changes as described in the issue. Make sure to keep your changes focused on resolving the issue or adding the feature discussed. 20 | 21 | 5. **Write Tests**: We use the native test runner of Node.js, so it's recommended to use Node.js v21.5.0 for development. All tests are located in the `test` folder. It's highly recommended to write tests for new features to ensure they work as expected and to help maintain the quality of the project. 22 | 23 | 6. **Submit a Pull Request (PR)**: Push your changes to your fork and then submit a pull request to the main repository. In your PR, include a description of the changes and reference the issue number (e.g., "Fixes #123"). 24 | 25 | ## Future Plans for Bounties 26 | 27 | We're considering implementing a bounty system for certain issues to reward contributors for their hard work. Stay tuned for more details on this initiative. 28 | 29 | ## Code of Conduct 30 | 31 | Please note that this project is released with a Contributor Code of Conduct. By participating in this project, you agree to abide by its terms. 32 | 33 | Thank you for contributing to Over-the-Wire! We appreciate every contribution, big or small, and we look forward to working together to make Over-the-Wire even better. 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.14.0-alpine3.21 AS build 2 | RUN sed -i 's/https/http/' /etc/apk/repositories 3 | RUN apk add --no-cache \ 4 | cmake \ 5 | g++ \ 6 | jq \ 7 | less \ 8 | libpcap-dev \ 9 | make \ 10 | tshark 11 | WORKDIR /app 12 | COPY package*.json ./ 13 | RUN npm ci --ignore-scripts 14 | COPY . . 15 | RUN CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) npm run precompile 16 | RUN npm test 17 | 18 | FROM scratch 19 | COPY --from=build /app/prebuilds/ . 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Hacker spider 3 |

4 | 5 | 6 | # Over-the-wire [![GitHub license](https://img.shields.io/github/license/vaguue/over-the-wire?style=flat)](https://github.com/vaguue/over-the-wire/blob/main/LICENSE) [![npm](https://img.shields.io/npm/v/over-the-wire)](https://www.npmjs.com/package/over-the-wire) ![Development Status](https://img.shields.io/badge/status-in_development-orange) 7 | 8 | *The project is currently under active development.* 9 | 10 | ## Overview 11 | `over-the-wire` is a Node.js packet manipulation library supporting: 12 | - Packet crafting and parsing 13 | - Capturing network traffic and sending packets in all formats 14 | - Parsing and serializing pcap and pcapng file formats 15 | - Creating custom non-TCP/UDP socket instances 16 | 17 | ## System Requirements 18 | - Libpcap/WinPcap/Npcap library installed (if Wireshark is installed on your system, you are good to go) 19 | - Node.js version 16.10.0 or higher recommended 20 | - C++ compiler, if there are no prebuilt bindings for your system 21 | 22 | ## Installation 23 | 24 | ```bash 25 | npm install over-the-wire --save 26 | ``` 27 | 28 | ## Getting started 29 | 30 | ```javascript 31 | const fs = require('fs'); 32 | const { Pcap, Packet } = require('over-the-wire'); 33 | 34 | const dev = new Pcap.LiveDevice({ 35 | iface: 'en0', 36 | direction: 'inout', 37 | filter: 'src port 443', 38 | }); 39 | 40 | // Get info about interface 41 | console.log('[*] Interface: ', dev.iface); 42 | 43 | // Save captured packets to a pcapng file 44 | const dump = Pcap.createWriteStream({ format: 'pcapng' }); 45 | dump.pipe(fs.createWriteStream('dump.pcapng')); 46 | 47 | dev.on('data', pkt => { 48 | if (pkt.layers.IPv4) { 49 | console.log(`[*] ${pkt.layers.IPv4.src} -> ${pkt.layers.IPv4.dst}`); 50 | } 51 | dump.write(pkt); 52 | }); 53 | 54 | // Create and inject a packet 55 | const pkt = new Packet({ iface: dev.iface }) 56 | .Ethernet() 57 | .IPv4({ dst: '192.168.1.1' }) 58 | .ICMP(); 59 | dev.write(pkt); 60 | ``` 61 | 62 | ## Documentation 63 | 64 | [Here :)](https://vaguue.github.io/over-the-wire) 65 | 66 | ## Questions or Suggestions 67 | Feel free to open any issue in the Issues section of this repository. Currently, there are no restrictions on the format. 68 | -------------------------------------------------------------------------------- /cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MAIN_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" 3 | "${CMAKE_CURRENT_SOURCE_DIR}/toCxx.cpp" 4 | ) 5 | 6 | source_group("Source Files\\Main" FILES ${MAIN_SRC}) 7 | 8 | target_sources(${PROJECT_NAME} PRIVATE ${MAIN_SRC}) 9 | -------------------------------------------------------------------------------- /cxx/Sys.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #define WIN32_LEAN_AND_MEAN 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #define SOCKET_OPT_TYPE char * 11 | #define SOCKET_LEN_TYPE int 12 | #else 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #define SOCKET int 22 | #define closesocket close 23 | #define SOCKET_OPT_TYPE void * 24 | #define SOCKET_LEN_TYPE socklen_t 25 | #endif 26 | 27 | //from https://github.com/libuv/libuv/blob/v1.x/src/unix/internal.h 28 | #if defined(_AIX) || \ 29 | defined(__APPLE__) || \ 30 | defined(__DragonFly__) || \ 31 | defined(__FreeBSD__) || \ 32 | defined(__linux__) || \ 33 | defined(__OpenBSD__) || \ 34 | defined(__NetBSD__) 35 | #define USE_IOCTL 1 36 | #else 37 | #define USE_IOCTL 0 38 | #endif 39 | -------------------------------------------------------------------------------- /cxx/arp/Arp.cpp: -------------------------------------------------------------------------------- 1 | #include "Arp.hpp" 2 | 3 | namespace OverTheWire::Arp { 4 | 5 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 6 | exports.Set("getArpTable", Napi::Function::New(env, getArpTable)); 7 | return exports; 8 | } 9 | 10 | ArpWorker::ArpWorker(Napi::Function& callback) : AsyncWorker{callback}, callback{callback} {} 11 | 12 | ArpWorker::~ArpWorker() {} 13 | 14 | void ArpWorker::Execute() { 15 | DEBUG_OUTPUT("ArpWorker::Execute"); 16 | std::string err; 17 | std::tie(err, res) = fromSys(); 18 | if (err.size() > 0) { 19 | SetError(err); 20 | } 21 | } 22 | 23 | void ArpWorker::OnOK() { 24 | Napi::HandleScope scope(Env()); 25 | 26 | Napi::Object obj = Napi::Object::New(Env()); 27 | 28 | for (auto& [k, v] : res) { 29 | Napi::Array arr = Napi::Array::New(Env(), v.size()); 30 | for (size_t i{}; i < v.size(); ++i) { 31 | Napi::Object rec = Napi::Object::New(Env()); 32 | 33 | rec.Set("ipAddr", Napi::String::New(Env(), v[i].ipAddr)); 34 | rec.Set("hwAddr", Napi::String::New(Env(), v[i].hwAddr)); 35 | rec.Set("hwType", Napi::Number::New(Env(), v[i].hwType)); 36 | 37 | auto flags = Napi::Array::New(Env(), v[i].flags.size()); 38 | rec.Set("flags", flags); 39 | for (size_t j{}; j < v[i].flags.size(); ++j) { 40 | flags.Set(j, Napi::String::New(Env(), v[i].flags[j])); 41 | } 42 | 43 | arr.Set(i, rec); 44 | } 45 | 46 | obj.Set(k, arr); 47 | } 48 | 49 | Callback().Call({ obj }); 50 | } 51 | 52 | Napi::Value getArpTable(const Napi::CallbackInfo& info) { 53 | checkLength(info, 1); 54 | Napi::Function callback = info[0].As(); 55 | ArpWorker* w = new ArpWorker(callback); 56 | w->Queue(); 57 | return info.Env().Undefined(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /cxx/arp/Arp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | #include 5 | 6 | /* 7 | * Get ARP table for an interface 8 | */ 9 | 10 | namespace OverTheWire::Arp { 11 | struct ArpRecord { 12 | std::string ipAddr; 13 | std::string hwAddr; 14 | uint32_t hwType = 0x1; 15 | std::vector flags; 16 | }; 17 | 18 | using arp_table_t = std::map>; 19 | 20 | Napi::Object Init(Napi::Env env, Napi::Object exports); 21 | 22 | struct ArpWorker : public Napi::AsyncWorker { 23 | ArpWorker(Napi::Function&); 24 | ~ArpWorker(); 25 | void Execute() override; 26 | void OnOK() override; 27 | 28 | Napi::Function& callback; 29 | arp_table_t res; 30 | }; 31 | 32 | std::pair fromSys(); 33 | 34 | Napi::Value getArpTable(const Napi::CallbackInfo& info); 35 | } 36 | -------------------------------------------------------------------------------- /cxx/arp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(ARP_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Arp.cpp" 3 | "${CMAKE_CURRENT_SOURCE_DIR}/Platform.cpp" 4 | ) 5 | 6 | set(ARP_HDR 7 | "${CMAKE_CURRENT_SOURCE_DIR}/Arp.hpp" 8 | ) 9 | 10 | source_group("Source Files\\Arp" FILES ${ARP_SRC}) 11 | source_group("Header Files\\Arp" FILES ${ARP_HDR}) 12 | 13 | target_sources(${PROJECT_NAME} PRIVATE ${ARP_SRC} ${ARP_HDR}) 14 | -------------------------------------------------------------------------------- /cxx/arp/Platform.cpp: -------------------------------------------------------------------------------- 1 | #include "Arp.hpp" 2 | #if defined(__APPLE__) 3 | #include "macOS.cpp" 4 | #elif defined(_WIN32) 5 | #include "win32.cpp" 6 | #else 7 | namespace OverTheWire::Arp { 8 | std::pair fromSys() { 9 | return {"ARP table not implemented for this platform", {}}; 10 | } 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /cxx/arp/macOS.cpp: -------------------------------------------------------------------------------- 1 | //https://github.com/apple-oss-distributions/network_cmds/blob/network_cmds-669/arp.tproj/arp.c 2 | 3 | #include "macOS.hpp" 4 | 5 | namespace OverTheWire::Arp { 6 | 7 | static char* lladdr(struct sockaddr_dl *sdl) { 8 | static char buf[256]; 9 | char* cp; 10 | int n, bufsize = sizeof (buf), p = 0; 11 | 12 | bzero(buf, sizeof (buf)); 13 | cp = (char *)LLADDR(sdl); 14 | if ((n = sdl->sdl_alen) > 0) { 15 | while (--n >= 0) { 16 | p += snprintf(buf + p, bufsize - p, "%x%s", *cp++ & 0xff, n > 0 ? ":" : ""); 17 | } 18 | } 19 | return (buf); 20 | } 21 | 22 | std::string save(arp_table_t& table, struct sockaddr_dl *sdl, struct sockaddr_inarp *addr, struct rt_msghdr *rtm) { 23 | const char *host; 24 | struct hostent *hp; 25 | char ifname[IF_NAMESIZE]; 26 | if (if_indextoname(sdl->sdl_index, ifname) == NULL) { 27 | strcpy(ifname, "unknown"); 28 | } 29 | 30 | auto& vec = table[ifname]; 31 | 32 | ArpRecord rec; 33 | char str[INET6_ADDRSTRLEN]; 34 | uv_inet_ntop(addr->sin_family, &addr->sin_addr, str, INET6_ADDRSTRLEN); 35 | rec.ipAddr = str; 36 | rec.hwAddr = sdl->sdl_alen ? lladdr(sdl) : "(incomplete)"; 37 | rec.hwType = sdl->sdl_type; 38 | 39 | if ((rtm->rtm_flags & RTF_IFSCOPE)) { 40 | rec.flags.push_back("ifscope"); 41 | } 42 | 43 | if (rtm->rtm_rmx.rmx_expire == 0) { 44 | rec.flags.push_back("permanent"); 45 | } 46 | 47 | if (addr->sin_other & SIN_PROXY) { 48 | rec.flags.push_back("proxy"); 49 | } 50 | 51 | if (rtm->rtm_addrs & RTA_NETMASK) { 52 | addr = (struct sockaddr_inarp *)(SA_SIZE(sdl) + (char *)sdl); 53 | if (addr->sin_addr.s_addr == 0xffffffff) { 54 | rec.flags.push_back("published"); 55 | } 56 | if (addr->sin_len != 8) { 57 | rec.flags.push_back("weird"); 58 | } 59 | } 60 | 61 | vec.push_back(std::move(rec)); 62 | 63 | return ""; 64 | } 65 | 66 | std::pair fromSys() { 67 | arp_table_t res{}; 68 | 69 | int mib[6]; 70 | size_t needed; 71 | 72 | char* buf; 73 | 74 | struct rt_msghdr* rtm; 75 | struct sockaddr_inarp* sin2; 76 | struct sockaddr_dl* sdl; 77 | char ifname[IF_NAMESIZE]; 78 | int st; 79 | 80 | mib[0] = CTL_NET; 81 | mib[1] = PF_ROUTE; 82 | mib[2] = 0; 83 | //mib[3] = AF_INET; 84 | mib[3] = 0; 85 | mib[4] = NET_RT_FLAGS; 86 | mib[5] = RTF_LLINFO; 87 | 88 | if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) { 89 | return {"Route sysctl estimate error", res}; 90 | } 91 | if (needed == 0) { 92 | return {"", res}; 93 | } 94 | 95 | buf = NULL; 96 | 97 | for (;;) { 98 | char* newbuf = static_cast(realloc(buf, needed)); 99 | if (newbuf == NULL) { 100 | if (buf != NULL) { 101 | free(buf); 102 | } 103 | return {"Could not reallocate memory", res}; 104 | } 105 | buf = newbuf; 106 | st = sysctl(mib, 6, buf, &needed, NULL, 0); 107 | if (st == 0 || errno != ENOMEM) { 108 | break; 109 | } 110 | needed += needed / 8; 111 | } 112 | if (st == -1) { 113 | return {"actual retrieval of routing table", res}; 114 | } 115 | char* lim = buf + needed; 116 | for (char* next = buf; next < lim; next += rtm->rtm_msglen) { 117 | rtm = (struct rt_msghdr *)next; 118 | sin2 = (struct sockaddr_inarp *)(rtm + 1); 119 | sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2)); 120 | auto err = save(res, sdl, sin2, rtm); 121 | if (err.size() > 0) { 122 | return { err, res }; 123 | } 124 | } 125 | 126 | free(buf); 127 | 128 | return {"", res}; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /cxx/arp/macOS.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifndef SA_SIZE 34 | #define SA_SIZE(sa) \ 35 | ( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \ 36 | sizeof(uint32_t) : \ 37 | 1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(uint32_t) - 1) ) ) 38 | #endif 39 | -------------------------------------------------------------------------------- /cxx/arp/win32.cpp: -------------------------------------------------------------------------------- 1 | //https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getipnettable2 2 | 3 | #include "win32.hpp" 4 | 5 | namespace OverTheWire::Arp { 6 | 7 | std::pair fromSys() { 8 | arp_table_t res{}; 9 | 10 | PMIB_IPNET_TABLE2 pipTable = NULL; 11 | 12 | auto status = GetIpNetTable2(AF_UNSPEC, &pipTable); 13 | if (status != NO_ERROR) { 14 | return {"GetIpNetTable returned error" + std::to_string(status), res}; 15 | } 16 | 17 | for (int i = 0; (unsigned) i < pipTable->NumEntries; ++i) { 18 | char ifname[IF_NAMESIZE]; 19 | if (if_indextoname(pipTable->Table[i].InterfaceIndex, ifname) == NULL) { 20 | strcpy(ifname, "unknown"); 21 | } 22 | auto& vec = res[ifname]; 23 | 24 | ArpRecord rec; 25 | 26 | char str[INET6_ADDRSTRLEN]; 27 | uv_inet_ntop( 28 | pipTable->Table[i].Address.si_family, 29 | pipTable->Table[i].Address.si_family == AF_INET6 ? 30 | (const void*)(&pipTable->Table[i].Address.Ipv6) : 31 | (const void*)(&pipTable->Table[i].Address.Ipv4), 32 | str, INET6_ADDRSTRLEN); 33 | 34 | rec.ipAddr = str; 35 | std::stringstream ss; 36 | ss << std::hex; 37 | for (int j = 0; j < pipTable->Table[i].PhysicalAddressLength; j++) { 38 | if (j) { 39 | ss << ":"; 40 | } 41 | ss << (int)pipTable->Table[i].PhysicalAddress[j]; 42 | } 43 | 44 | rec.hwAddr = ss.str(); 45 | rec.hwType = pipTable->Table[i].InterfaceLuid.Info.IfType; 46 | 47 | switch (pipTable->Table[i].State) { 48 | case NlnsUnreachable: 49 | rec.flags.push_back("NlnsUnreachable"); 50 | break; 51 | case NlnsIncomplete: 52 | rec.flags.push_back("NlnsIncomplete"); 53 | break; 54 | case NlnsProbe: 55 | rec.flags.push_back("NlnsProbe"); 56 | break; 57 | case NlnsDelay: 58 | rec.flags.push_back("NlnsDelay"); 59 | break; 60 | case NlnsStale: 61 | rec.flags.push_back("NlnsStale"); 62 | break; 63 | case NlnsReachable: 64 | rec.flags.push_back("NlnsReachable"); 65 | break; 66 | case NlnsPermanent: 67 | rec.flags.push_back("NlnsPermanent"); 68 | break; 69 | default: 70 | rec.flags.push_back("Unknown"); 71 | break; 72 | } 73 | 74 | vec.push_back(std::move(rec)); 75 | } 76 | 77 | return {"", res}; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /cxx/arp/win32.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //#include 4 | //#include 5 | //#include 6 | //#include 7 | //#include 8 | #include 9 | -------------------------------------------------------------------------------- /cxx/bpf-filter/BpfFilter.cpp: -------------------------------------------------------------------------------- 1 | #include "BpfFilter.hpp" 2 | 3 | namespace OverTheWire::BpfFilter { 4 | 5 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 6 | BpfFilter::Init(env, exports); 7 | return exports; 8 | } 9 | 10 | Napi::Object BpfFilter::Init(Napi::Env env, Napi::Object exports) { 11 | Napi::Function func = DefineClass(env, "BpfFilter", { 12 | InstanceMethod<&BpfFilter::match>("match", static_cast(napi_writable | napi_configurable)), 13 | InstanceAccessor<&BpfFilter::getFilter, &BpfFilter::setFilter>("value"), 14 | InstanceAccessor<&BpfFilter::getLinkType, &BpfFilter::setLinkType>("linkType"), 15 | }); 16 | 17 | env.GetInstanceData()->SetClass(typeid(BpfFilter), func); 18 | exports.Set("BpfFilter", func); 19 | return exports; 20 | } 21 | 22 | BpfFilter::BpfFilter(const Napi::CallbackInfo& info) : Napi::ObjectWrap{info}, obj{new pcpp::BpfFilterWrapper} { 23 | if (info.Length() > 0) { 24 | filter = info[0].As().Utf8Value(); 25 | } 26 | if (info.Length() > 1) { 27 | linkType = static_cast(info[1].As().Uint32Value()); 28 | } 29 | 30 | if (filter.size() > 0) { 31 | if (!obj->setFilter(filter, linkType)) { 32 | Napi::Error::New(info.Env(), "Error setting filter").ThrowAsJavaScriptException(); 33 | } 34 | } 35 | } 36 | 37 | BpfFilter::~BpfFilter() {} 38 | 39 | Napi::Value BpfFilter::getFilter(const Napi::CallbackInfo& info) { 40 | return Napi::String::New(info.Env(), filter); 41 | } 42 | 43 | Napi::Value BpfFilter::getLinkType(const Napi::CallbackInfo& info) { 44 | return Napi::Number::New(info.Env(), linkType); 45 | } 46 | 47 | void BpfFilter::setFilter(const Napi::CallbackInfo& info, const Napi::Value& val) { 48 | filter = val.As().Utf8Value(); 49 | 50 | if (!obj->setFilter(filter, linkType)) { 51 | Napi::Error::New(info.Env(), "Error setting filter").ThrowAsJavaScriptException(); 52 | } 53 | } 54 | 55 | void BpfFilter::setLinkType(const Napi::CallbackInfo&, const Napi::Value& val) { 56 | linkType = static_cast(val.As().Uint32Value()); 57 | } 58 | 59 | Napi::Value BpfFilter::match(const Napi::CallbackInfo& info) { 60 | checkLength(info, 1); 61 | 62 | if (filter.size() == 0) { 63 | return Napi::Boolean::New(info.Env(), true); 64 | } 65 | 66 | js_buffer_t buf = info[0].As(); 67 | struct timespec ts; 68 | if (info.Length() > 1) { 69 | Napi::Array hrtime = info[1].As(); 70 | ts.tv_sec = hrtime.Get("0").As().Uint32Value(); 71 | ts.tv_nsec = hrtime.Get("1").As().Uint32Value(); 72 | } 73 | else { 74 | timespec_get(&ts, TIME_UTC); 75 | } 76 | 77 | auto _linkType = linkType; 78 | if (info.Length() > 3) { 79 | _linkType = static_cast(info[2].As().Uint32Value()); 80 | } 81 | 82 | return Napi::Boolean::New(info.Env(), obj->matchPacketWithFilter(buf.Data(), buf.Length(), ts, _linkType)); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /cxx/bpf-filter/BpfFilter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common.hpp" 5 | #include "stdlib.h" 6 | #include "PcapLiveDeviceList.h" 7 | #include "IpUtils.h" 8 | #include "SystemUtils.h" 9 | #include "pcap.h" 10 | 11 | /* Wrapper around PcapPlusPlus BpfFilterWrapper. 12 | * Wrapper around wrapper. 13 | */ 14 | 15 | namespace OverTheWire::BpfFilter { 16 | Napi::Object Init(Napi::Env env, Napi::Object exports); 17 | 18 | struct BpfFilter : public Napi::ObjectWrap { 19 | static Napi::Object Init(Napi::Env, Napi::Object); 20 | BpfFilter(const Napi::CallbackInfo& info); 21 | ~BpfFilter(); 22 | 23 | Napi::Value getFilter(const Napi::CallbackInfo&); 24 | void setFilter(const Napi::CallbackInfo&, const Napi::Value&); 25 | 26 | Napi::Value getLinkType(const Napi::CallbackInfo&); 27 | void setLinkType(const Napi::CallbackInfo&, const Napi::Value&); 28 | 29 | Napi::Value match(const Napi::CallbackInfo&); 30 | 31 | std::unique_ptr obj; 32 | std::string filter = ""; 33 | pcpp::LinkLayerType linkType = pcpp::LinkLayerType::LINKTYPE_ETHERNET; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /cxx/bpf-filter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(BPF_FILTER_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/BpfFilter.cpp" 3 | ) 4 | 5 | set(BPF_FILTER_HDR 6 | "${CMAKE_CURRENT_SOURCE_DIR}/BpfFilter.hpp" 7 | ) 8 | 9 | source_group("Source Files\\BpfFilter" FILES ${BPF_FILTER_SRC}) 10 | source_group("Header Files\\BpfFilter" FILES ${BPF_FILTER_HDR}) 11 | 12 | target_sources(${PROJECT_NAME} PRIVATE ${BPF_FILTER_SRC} ${BPF_FILTER_HDR}) 13 | -------------------------------------------------------------------------------- /cxx/checksums/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CHECKSUMS_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Checksums.cpp" 3 | ) 4 | 5 | set(CHECKSUMS_HDR 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Checksums.hpp" 7 | ) 8 | 9 | source_group("Source Files\\Checksums" FILES ${CHECKSUMS_SRC}) 10 | source_group("Header Files\\Checksums" FILES ${CHECKSUMS_HDR}) 11 | 12 | target_sources(${PROJECT_NAME} PRIVATE ${CHECKSUMS_SRC} ${CHECKSUMS_HDR}) 13 | -------------------------------------------------------------------------------- /cxx/checksums/Checksums.cpp: -------------------------------------------------------------------------------- 1 | #include "Checksums.hpp" 2 | 3 | namespace OverTheWire::Checksums { 4 | 5 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 6 | exports.Set("ip", Napi::Function::New(env, IPChecksum)); 7 | exports.Set("pseudo", Napi::Function::New(env, PseudoHeaderChecksum)); 8 | return exports; 9 | } 10 | 11 | pcpp::ScalarBuffer convertBuf(const Napi::Value& val) { 12 | Napi::Buffer buf = val.As>(); 13 | return { buf.Data(), buf.Length() * 2 }; 14 | } 15 | 16 | Napi::Value IPChecksum(const Napi::CallbackInfo& info) { 17 | checkLength(info, 1); 18 | 19 | std::vector> bufs; 20 | 21 | if (info[0].IsArray()) { 22 | Napi::Array ar = info[0].As(); 23 | for (size_t i{}; i < ar.Length(); ++i) { 24 | bufs.push_back(convertBuf(ar[i])); 25 | } 26 | } 27 | else { 28 | bufs.push_back(convertBuf(info[0])); 29 | } 30 | 31 | return Napi::Number::New(info.Env(), computeChecksum(bufs.data(), bufs.size())); 32 | } 33 | 34 | Napi::Value PseudoHeaderChecksum(const Napi::CallbackInfo& info) { 35 | checkLength(info, 1); 36 | Napi::Object arg = info[0].As(); 37 | uint8_t* data = arg.Get("data").As().Data(); 38 | size_t dataLen = arg.Get("data").As().Length(); 39 | pcpp::IPAddress::AddressType ipAddrType; 40 | std::string addrType = arg.Get("addrType").As().Utf8Value(); 41 | if (addrType == "IPv4") { 42 | ipAddrType = pcpp::IPAddress::IPv4AddressType; 43 | } 44 | else if (addrType == "IPv6") { 45 | ipAddrType = pcpp::IPAddress::IPv6AddressType; 46 | } 47 | else { 48 | Napi::Error::New(info.Env(), "Invalid address type").ThrowAsJavaScriptException(); 49 | return info.Env().Undefined(); 50 | } 51 | 52 | uint8_t protocolType = arg.Get("protocolType").As().Uint32Value() & 0xff; 53 | 54 | std::string src = arg.Get("src").As().Utf8Value(); 55 | std::string dst = arg.Get("dst").As().Utf8Value(); 56 | 57 | pcpp::IPAddress srcIP{src}; 58 | pcpp::IPAddress dstIP{dst}; 59 | 60 | return Napi::Number::New(info.Env(), computePseudoHdrChecksum(data, dataLen, ipAddrType, protocolType, srcIP, dstIP)); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /cxx/checksums/Checksums.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | #include "PacketUtils.h" 5 | 6 | /* 7 | * Checksum helpers 8 | */ 9 | 10 | namespace OverTheWire::Checksums { 11 | Napi::Object Init(Napi::Env env, Napi::Object exports); 12 | 13 | Napi::Value IPChecksum(const Napi::CallbackInfo&); 14 | Napi::Value PseudoHeaderChecksum(const Napi::CallbackInfo&); 15 | } 16 | -------------------------------------------------------------------------------- /cxx/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #if defined(__unix__) 13 | #include 14 | #endif 15 | 16 | #include "Sys.hpp" 17 | 18 | /* For me, utils or helpers are anti-patterns, 19 | * hence I names this file common.hpp. 20 | * But actually this file has some commonly used C++ headers 21 | * and JS <=> C++ specific functions. 22 | */ 23 | 24 | #ifdef DEBUG 25 | #define DEBUG_OUTPUT(x) std::cout << "[over-the-wire::cxx] " << (x) << std::endl; 26 | #else 27 | #define DEBUG_OUTPUT 28 | #endif 29 | 30 | using promise_t = std::shared_ptr; 31 | 32 | struct AddonData { 33 | std::map constructors; 34 | 35 | std::vector> refs; 36 | 37 | void refToInstance(Napi::Env env, Napi::Value v) { 38 | refs.push_back(Napi::Reference::New(v, 1)); 39 | }; 40 | 41 | Napi::FunctionReference& GetClass(const std::type_index& tid) { 42 | return constructors[tid]; 43 | } 44 | 45 | Napi::FunctionReference& SetClass(const std::type_index& tid, Napi::Function func) { 46 | constructors[tid] = std::forward(Napi::Persistent(func)); 47 | return constructors[tid]; 48 | } 49 | 50 | bool HasClass(const std::type_index& tid) { 51 | return constructors.count(tid) > 0; 52 | } 53 | }; 54 | 55 | inline std::string demangle(const char* name) { 56 | #if defined(__unix__) 57 | int status; 58 | char* demangled_name = abi::__cxa_demangle(name, 0, 0, &status); 59 | if (status == 0) { 60 | std::string realname = demangled_name; 61 | free(demangled_name); 62 | return realname; 63 | } else { 64 | return name; 65 | } 66 | #else 67 | return name; 68 | #endif 69 | }; 70 | 71 | template 72 | void deleter(Napi::Env env, T p) { 73 | #ifdef DEBUG 74 | std::cout << "[over-the-wire::cxx] Deleting " << demangle(typeid(T).name()); 75 | #endif 76 | if constexpr (skip) { 77 | #ifdef DEBUG 78 | std::cout << " (Skipping)" << std::endl; 79 | #endif 80 | } 81 | else { 82 | #ifdef DEBUG 83 | std::cout << std::endl; 84 | #endif 85 | delete p; 86 | } 87 | } 88 | 89 | inline void initAddon(Napi::Env env) { 90 | static thread_local bool was = false; 91 | if (!was) { 92 | env.SetInstanceData>(new AddonData()); 93 | was = true; 94 | } 95 | } 96 | 97 | inline void checkLength(const Napi::CallbackInfo& info, size_t n) { 98 | if (info.Length() < n) { 99 | Napi::Error::New(info.Env(), "Not enough arguments").ThrowAsJavaScriptException(); 100 | } 101 | } 102 | 103 | template 104 | void nop(T...) {} 105 | 106 | template 107 | struct JsParent {}; 108 | 109 | template 110 | void extendJsClass(Napi::Env env, Napi::Function& cls, const char* moduleName, const char* clsName) { 111 | Napi::Function require = env.Global() 112 | .Get("require").As(); 113 | Napi::Function jsCls = require.Call({Napi::String::New(env, moduleName)}) 114 | .ToObject() 115 | .Get(clsName) 116 | .As(); 117 | 118 | Napi::Function setProto = env.Global() 119 | .Get("Object") 120 | .ToObject() 121 | .Get("setPrototypeOf") 122 | .As(); 123 | 124 | setProto.Call({cls, jsCls}); 125 | setProto.Call({cls.Get("prototype"), jsCls.Get("prototype")}); 126 | 127 | env.GetInstanceData()->SetClass(typeid(ClsId), jsCls); 128 | } 129 | 130 | using js_buffer_t = Napi::Buffer; 131 | using c_buffer_t = std::pair; 132 | using cxx_buffer_t = std::pair, size_t>; 133 | 134 | c_buffer_t toCxx(const Napi::Value&&); 135 | c_buffer_t toCxx(const Napi::Value&); 136 | -------------------------------------------------------------------------------- /cxx/converters/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(Converters_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Converters.cpp" 3 | ) 4 | 5 | set(Converters_HDR 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Converters.hpp" 7 | ) 8 | 9 | source_group("Source Files\\Converters" FILES ${Converters_SRC}) 10 | source_group("Header Files\\Converters" FILES ${Converters_HDR}) 11 | 12 | target_sources(${PROJECT_NAME} PRIVATE ${Converters_SRC} ${Converters_HDR}) 13 | -------------------------------------------------------------------------------- /cxx/converters/Converters.cpp: -------------------------------------------------------------------------------- 1 | #include "Converters.hpp" 2 | 3 | namespace OverTheWire::Converters { 4 | 5 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 6 | exports.Set("inetPton", Napi::Function::New(env, "inetPton")); 7 | exports.Set("inetNtop", Napi::Function::New(env, "inetNtop")); 8 | 9 | exports.Set("htonl", Napi::Function::New(env, "htonl")); 10 | exports.Set("ntohl", Napi::Function::New(env, "ntohl")); 11 | exports.Set("htons", Napi::Function::New(env, "htons")); 12 | exports.Set("ntohs", Napi::Function::New(env, "ntohs")); 13 | 14 | return exports; 15 | } 16 | 17 | Napi::Value inetPton(const Napi::CallbackInfo& info) { 18 | checkLength(info, 2); 19 | Napi::Env env = info.Env(); 20 | int domain = info[0].As().Uint32Value(); 21 | std::string src = info[1].As().Utf8Value(); 22 | js_buffer_t res = js_buffer_t::New(env, domain == AF_INET6 ? sizeof(in6_addr) : sizeof(in_addr)); 23 | 24 | int s = uv_inet_pton(domain, src.c_str(), res.Data()); 25 | 26 | if (s != 0) { 27 | Napi::Error::New(env, getLibuvError(s)).ThrowAsJavaScriptException(); 28 | return env.Undefined(); 29 | } 30 | 31 | return res; 32 | } 33 | 34 | Napi::Value inetNtop(const Napi::CallbackInfo& info) { 35 | checkLength(info, 2); 36 | Napi::Env env = info.Env(); 37 | char str[INET6_ADDRSTRLEN]; 38 | int domain = info[0].As().Uint32Value(); 39 | int s; 40 | 41 | if (info[1].IsBuffer()) { 42 | js_buffer_t buf = info[1].As(); 43 | s = uv_inet_ntop(domain, buf.Data(), str, INET6_ADDRSTRLEN); 44 | } 45 | else if (info[1].IsNumber()) { 46 | uint32_t val = info[1].As().Uint32Value(); 47 | s = uv_inet_ntop(domain, &val, str, INET6_ADDRSTRLEN); 48 | } 49 | 50 | if (s != 0) { 51 | Napi::Error::New(env, getLibuvError(s)).ThrowAsJavaScriptException(); 52 | return env.Undefined(); 53 | } 54 | 55 | return Napi::String::New(env, str); 56 | } 57 | 58 | Napi::Value jsHtonl(const Napi::CallbackInfo& info) { 59 | checkLength(info, 1); 60 | return Napi::Number::New(info.Env(), htonl(info[0].As().Uint32Value())); 61 | } 62 | 63 | Napi::Value jsNtohl(const Napi::CallbackInfo& info) { 64 | checkLength(info, 1); 65 | return Napi::Number::New(info.Env(), ntohl(info[0].As().Uint32Value())); 66 | } 67 | 68 | Napi::Value jsHtons(const Napi::CallbackInfo& info) { 69 | checkLength(info, 1); 70 | return Napi::Number::New(info.Env(), htons(info[0].As().Uint32Value())); 71 | } 72 | 73 | Napi::Value jsNtohs(const Napi::CallbackInfo& info) { 74 | checkLength(info, 1); 75 | return Napi::Number::New(info.Env(), ntohs(info[0].As().Uint32Value())); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /cxx/converters/Converters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | #include "error/Error.hpp" 5 | #include 6 | 7 | /* 8 | * Get ARP table for an interface 9 | */ 10 | 11 | namespace OverTheWire::Converters { 12 | 13 | Napi::Object Init(Napi::Env, Napi::Object); 14 | 15 | Napi::Value inetPton(const Napi::CallbackInfo&); 16 | Napi::Value inetNtop(const Napi::CallbackInfo&); 17 | 18 | Napi::Value jsHtonl(const Napi::CallbackInfo&); 19 | Napi::Value jsNtohl(const Napi::CallbackInfo&); 20 | Napi::Value jsHtons(const Napi::CallbackInfo&); 21 | Napi::Value jsNtohs(const Napi::CallbackInfo&); 22 | } 23 | -------------------------------------------------------------------------------- /cxx/enums/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(ENUMS_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Enums.cpp" 3 | ) 4 | 5 | set(ENUMS_HDR 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Enums.hpp" 7 | ) 8 | 9 | source_group("Source Files\\Enums" FILES ${ENUMS_SRC}) 10 | source_group("Header Files\\Enums" FILES ${ENUMS_HDR}) 11 | 12 | target_sources(${PROJECT_NAME} PRIVATE ${ENUMS_SRC} ${ENUMS_HDR}) 13 | -------------------------------------------------------------------------------- /cxx/enums/Enums.cpp: -------------------------------------------------------------------------------- 1 | #include "Enums.hpp" 2 | 3 | namespace OverTheWire::Enums { 4 | 5 | #define ENUM_VALUE(x) exports.Set(#x, Napi::Number::New(env, x)) 6 | #define ENUM_VALUE_MANUAL(x, y) exports.Set(#x, Napi::Number::New(env, y)) 7 | 8 | Napi::Object InitLinkLayerType(Napi::Env env, Napi::Object exports) { 9 | using namespace pcpp; 10 | ENUM_VALUE(LINKTYPE_NULL); 11 | ENUM_VALUE(LINKTYPE_ETHERNET); 12 | ENUM_VALUE(LINKTYPE_AX25); 13 | ENUM_VALUE(LINKTYPE_IEEE802_5); 14 | ENUM_VALUE(LINKTYPE_ARCNET_BSD); 15 | ENUM_VALUE(LINKTYPE_SLIP); 16 | ENUM_VALUE(LINKTYPE_PPP); 17 | ENUM_VALUE(LINKTYPE_FDDI); 18 | ENUM_VALUE(LINKTYPE_DLT_RAW1); 19 | ENUM_VALUE(LINKTYPE_DLT_RAW2); 20 | ENUM_VALUE(LINKTYPE_PPP_HDLC); 21 | ENUM_VALUE(LINKTYPE_PPP_ETHER); 22 | ENUM_VALUE(LINKTYPE_ATM_RFC1483); 23 | ENUM_VALUE(LINKTYPE_RAW); 24 | ENUM_VALUE(LINKTYPE_C_HDLC); 25 | ENUM_VALUE(LINKTYPE_IEEE802_11); 26 | ENUM_VALUE(LINKTYPE_FRELAY); 27 | ENUM_VALUE(LINKTYPE_LOOP); 28 | ENUM_VALUE(LINKTYPE_LINUX_SLL); 29 | ENUM_VALUE(LINKTYPE_LTALK); 30 | ENUM_VALUE(LINKTYPE_PFLOG); 31 | ENUM_VALUE(LINKTYPE_IEEE802_11_PRISM); 32 | ENUM_VALUE(LINKTYPE_IP_OVER_FC); 33 | ENUM_VALUE(LINKTYPE_SUNATM); 34 | ENUM_VALUE(LINKTYPE_IEEE802_11_RADIOTAP); 35 | ENUM_VALUE(LINKTYPE_ARCNET_LINUX); 36 | ENUM_VALUE(LINKTYPE_APPLE_IP_OVER_IEEE1394); 37 | ENUM_VALUE(LINKTYPE_MTP2_WITH_PHDR); 38 | ENUM_VALUE(LINKTYPE_MTP2); 39 | ENUM_VALUE(LINKTYPE_MTP3); 40 | ENUM_VALUE(LINKTYPE_SCCP); 41 | ENUM_VALUE(LINKTYPE_DOCSIS); 42 | ENUM_VALUE(LINKTYPE_LINUX_IRDA); 43 | ENUM_VALUE(LINKTYPE_USER0); 44 | ENUM_VALUE(LINKTYPE_USER1); 45 | ENUM_VALUE(LINKTYPE_USER2); 46 | ENUM_VALUE(LINKTYPE_USER3); 47 | ENUM_VALUE(LINKTYPE_USER4); 48 | ENUM_VALUE(LINKTYPE_USER5); 49 | ENUM_VALUE(LINKTYPE_USER6); 50 | ENUM_VALUE(LINKTYPE_USER7); 51 | ENUM_VALUE(LINKTYPE_USER8); 52 | ENUM_VALUE(LINKTYPE_USER9); 53 | ENUM_VALUE(LINKTYPE_USER10); 54 | ENUM_VALUE(LINKTYPE_USER11); 55 | ENUM_VALUE(LINKTYPE_USER12); 56 | ENUM_VALUE(LINKTYPE_USER13); 57 | ENUM_VALUE(LINKTYPE_USER14); 58 | ENUM_VALUE(LINKTYPE_USER15); 59 | ENUM_VALUE(LINKTYPE_IEEE802_11_AVS); 60 | ENUM_VALUE(LINKTYPE_BACNET_MS_TP); 61 | ENUM_VALUE(LINKTYPE_PPP_PPPD); 62 | ENUM_VALUE(LINKTYPE_GPRS_LLC); 63 | ENUM_VALUE(LINKTYPE_GPF_T); 64 | ENUM_VALUE(LINKTYPE_GPF_F); 65 | ENUM_VALUE(LINKTYPE_LINUX_LAPD); 66 | ENUM_VALUE(LINKTYPE_BLUETOOTH_HCI_H4); 67 | ENUM_VALUE(LINKTYPE_USB_LINUX); 68 | ENUM_VALUE(LINKTYPE_PPI); 69 | ENUM_VALUE(LINKTYPE_IEEE802_15_4); 70 | ENUM_VALUE(LINKTYPE_SITA); 71 | ENUM_VALUE(LINKTYPE_ERF); 72 | ENUM_VALUE(LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR); 73 | ENUM_VALUE(LINKTYPE_AX25_KISS); 74 | ENUM_VALUE(LINKTYPE_LAPD); 75 | ENUM_VALUE(LINKTYPE_PPP_WITH_DIR); 76 | ENUM_VALUE(LINKTYPE_C_HDLC_WITH_DIR); 77 | ENUM_VALUE(LINKTYPE_FRELAY_WITH_DIR); 78 | ENUM_VALUE(LINKTYPE_IPMB_LINUX); 79 | ENUM_VALUE(LINKTYPE_IEEE802_15_4_NONASK_PHY); 80 | ENUM_VALUE(LINKTYPE_USB_LINUX_MMAPPED); 81 | ENUM_VALUE(LINKTYPE_FC_2); 82 | ENUM_VALUE(LINKTYPE_FC_2_WITH_FRAME_DELIMS); 83 | ENUM_VALUE(LINKTYPE_IPNET); 84 | ENUM_VALUE(LINKTYPE_CAN_SOCKETCAN); 85 | ENUM_VALUE(LINKTYPE_IPV4); 86 | ENUM_VALUE(LINKTYPE_IPV6); 87 | ENUM_VALUE(LINKTYPE_IEEE802_15_4_NOFCS); 88 | ENUM_VALUE(LINKTYPE_DBUS); 89 | ENUM_VALUE(LINKTYPE_DVB_CI); 90 | ENUM_VALUE(LINKTYPE_MUX27010); 91 | ENUM_VALUE(LINKTYPE_STANAG_5066_D_PDU); 92 | ENUM_VALUE(LINKTYPE_NFLOG); 93 | ENUM_VALUE(LINKTYPE_NETANALYZER); 94 | ENUM_VALUE(LINKTYPE_NETANALYZER_TRANSPARENT); 95 | ENUM_VALUE(LINKTYPE_IPOIB); 96 | ENUM_VALUE(LINKTYPE_MPEG_2_TS); 97 | ENUM_VALUE(LINKTYPE_NG40); 98 | ENUM_VALUE(LINKTYPE_NFC_LLCP); 99 | ENUM_VALUE(LINKTYPE_INFINIBAND); 100 | ENUM_VALUE(LINKTYPE_SCTP); 101 | ENUM_VALUE(LINKTYPE_USBPCAP); 102 | ENUM_VALUE(LINKTYPE_RTAC_SERIAL); 103 | ENUM_VALUE(LINKTYPE_BLUETOOTH_LE_LL); 104 | ENUM_VALUE(LINKTYPE_NETLINK); 105 | ENUM_VALUE(LINKTYPE_BLUETOOTH_LINUX_MONITOR); 106 | ENUM_VALUE(LINKTYPE_BLUETOOTH_BREDR_BB); 107 | ENUM_VALUE(LINKTYPE_BLUETOOTH_LE_LL_WITH_PHDR); 108 | ENUM_VALUE(LINKTYPE_PROFIBUS_DL); 109 | ENUM_VALUE(LINKTYPE_PKTAP); 110 | ENUM_VALUE(LINKTYPE_EPON); 111 | ENUM_VALUE(LINKTYPE_IPMI_HPM_2); 112 | ENUM_VALUE(LINKTYPE_ZWAVE_R1_R2); 113 | ENUM_VALUE(LINKTYPE_ZWAVE_R3); 114 | ENUM_VALUE(LINKTYPE_WATTSTOPPER_DLM); 115 | ENUM_VALUE(LINKTYPE_ISO_14443); 116 | ENUM_VALUE(LINKTYPE_LINUX_SLL2); 117 | return exports; 118 | } 119 | 120 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 121 | exports.Set("LinkLayerType", InitLinkLayerType(env, Napi::Object::New(env))); 122 | return exports; 123 | } 124 | 125 | #undef ENUM_VALUE 126 | #undef ENUM_VALUE_MANUAL 127 | 128 | } 129 | -------------------------------------------------------------------------------- /cxx/enums/Enums.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | #include "RawPacket.h" 5 | 6 | /* 7 | * Enums from PcapPlusPlus 8 | */ 9 | 10 | namespace OverTheWire::Enums { 11 | 12 | Napi::Object Init(Napi::Env env, Napi::Object exports); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /cxx/error/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(ERROR_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp" 3 | ) 4 | 5 | set(ERROR_HDR 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Error.hpp" 7 | ) 8 | 9 | source_group("Source Files\\Error" FILES ${ERROR_SRC}) 10 | source_group("Header Files\\Error" FILES ${ERROR_HDR}) 11 | 12 | target_sources(${PROJECT_NAME} PRIVATE ${ERROR_SRC} ${ERROR_HDR}) 13 | -------------------------------------------------------------------------------- /cxx/error/Error.cpp: -------------------------------------------------------------------------------- 1 | #include "Error.hpp" 2 | 3 | namespace OverTheWire { 4 | 5 | #ifdef _WIN32 6 | static thread_local char errbuf[1024]; 7 | #endif 8 | std::string getSystemError() { 9 | #ifdef _WIN32 10 | if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, WSAGetLastError(), 0, errbuf, 1024, NULL)) { 11 | return std::string(errbuf); 12 | } 13 | else { 14 | strcpy(errbuf, "Unknown error"); 15 | return std::string(errbuf); 16 | } 17 | #else 18 | return std::string(strerror(errno)); 19 | #endif 20 | } 21 | 22 | std::string getLibuvError(int code) { 23 | return std::string(uv_strerror(code)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /cxx/error/Error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "common.hpp" 6 | 7 | /* Painlessly retrive the last error. 8 | */ 9 | 10 | namespace OverTheWire { 11 | 12 | std::string getSystemError(); 13 | std::string getLibuvError(int); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /cxx/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(EXAMPLE_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Example.cpp" 3 | ) 4 | 5 | set(EXAMPLE_HDR 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Example.hpp" 7 | ) 8 | 9 | source_group("Source Files\\Example" FILES ${EXAMPLE_SRC}) 10 | source_group("Header Files\\Example" FILES ${EXAMPLE_HDR}) 11 | 12 | target_sources(${PROJECT_NAME} PRIVATE ${EXAMPLE_SRC} ${EXAMPLE_HDR}) 13 | -------------------------------------------------------------------------------- /cxx/example/Example.cpp: -------------------------------------------------------------------------------- 1 | #include "Example.hpp" 2 | 3 | namespace OverTheWire::Example { 4 | 5 | Napi::Value da(const Napi::CallbackInfo& info) { 6 | std::cout << "da" << std::endl; 7 | return info.Env().Undefined(); 8 | } 9 | 10 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 11 | exports.Set("da", Napi::Function::New(env, da)); 12 | 13 | return exports; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /cxx/example/Example.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common.hpp" 5 | 6 | /* First module in this project for testing purposes. 7 | * I think I'll keep it. 8 | */ 9 | 10 | namespace OverTheWire::Example { 11 | Napi::Object Init(Napi::Env env, Napi::Object exports); 12 | 13 | Napi::Value da(const Napi::CallbackInfo&); 14 | } 15 | -------------------------------------------------------------------------------- /cxx/main.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | #include "example/Example.hpp" 4 | #include "transports/pcap/Pcap.hpp" 5 | #include "transports/socket/Socket.hpp" 6 | #include "enums/Enums.hpp" 7 | #include "bpf-filter/BpfFilter.hpp" 8 | #include "checksums/Checksums.hpp" 9 | #include "converters/Converters.hpp" 10 | #include "arp/Arp.hpp" 11 | #include "routing/Routing.hpp" 12 | 13 | static Napi::Object Init(Napi::Env env, Napi::Object exports) { 14 | initAddon(env); 15 | OverTheWire::Example::Init(env, exports); 16 | OverTheWire::Transports::Pcap::Init(env, exports); 17 | OverTheWire::Enums::Init(env, exports); 18 | OverTheWire::BpfFilter::Init(env, exports); 19 | OverTheWire::Arp::Init(env, exports); 20 | OverTheWire::Routing::Init(env, exports); 21 | 22 | exports.Set("socket", OverTheWire::Transports::Socket::Init(env, Napi::Object::New(env))); 23 | exports.Set("converters", OverTheWire::Converters::Init(env, Napi::Object::New(env))); 24 | exports.Set("checksums", OverTheWire::Checksums::Init(env, Napi::Object::New(env))); 25 | 26 | return exports; 27 | } 28 | 29 | NODE_API_MODULE(OverTheWire, Init) 30 | -------------------------------------------------------------------------------- /cxx/routing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(ROUTING_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Routing.cpp" 3 | "${CMAKE_CURRENT_SOURCE_DIR}/Platform.cpp" 4 | ) 5 | 6 | set(ROUTING_HDR 7 | "${CMAKE_CURRENT_SOURCE_DIR}/Routing.hpp" 8 | ) 9 | 10 | source_group("Source Files\\Routing" FILES ${ROUTING_SRC}) 11 | source_group("Header Files\\Routing" FILES ${ROUTING_HDR}) 12 | 13 | target_sources(${PROJECT_NAME} PRIVATE ${ROUTING_SRC} ${ROUTING_HDR}) 14 | -------------------------------------------------------------------------------- /cxx/routing/Platform.cpp: -------------------------------------------------------------------------------- 1 | #include "Routing.hpp" 2 | #if defined(__APPLE__) 3 | #include "macOS.cpp" 4 | #elif defined(_WIN32) 5 | #include "win32.cpp" 6 | #else 7 | namespace OverTheWire::Routing { 8 | std::pair fromSys() { 9 | return {"Routing table not implemented for this platform", {}}; 10 | } 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /cxx/routing/Routing.cpp: -------------------------------------------------------------------------------- 1 | #include "Routing.hpp" 2 | 3 | namespace OverTheWire::Routing { 4 | 5 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 6 | exports.Set("getRoutingTable", Napi::Function::New(env, getRoutingTable)); 7 | return exports; 8 | } 9 | 10 | RoutingWorker::RoutingWorker(Napi::Function& callback) : AsyncWorker{callback}, callback{callback} {} 11 | 12 | RoutingWorker::~RoutingWorker() {} 13 | 14 | void RoutingWorker::Execute() { 15 | DEBUG_OUTPUT("RoutingWorker::Execute"); 16 | std::string err; 17 | std::tie(err, res) = fromSys(); 18 | if (err.size() > 0) { 19 | SetError(err); 20 | } 21 | } 22 | 23 | void RoutingWorker::OnOK() { 24 | Napi::HandleScope scope(Env()); 25 | 26 | Napi::Object obj = Napi::Object::New(Env()); 27 | 28 | for (auto& [k, v] : res) { 29 | Napi::Array arr = Napi::Array::New(Env(), v.size()); 30 | for (size_t i{}; i < v.size(); ++i) { 31 | Napi::Object rec = Napi::Object::New(Env()); 32 | 33 | rec.Set("destination", Napi::String::New(Env(), v[i].destination)); 34 | rec.Set("gateway", Napi::String::New(Env(), v[i].gateway)); 35 | rec.Set("mask", Napi::String::New(Env(), v[i].mask)); 36 | 37 | auto flags = Napi::Array::New(Env(), v[i].flags.size()); 38 | rec.Set("flags", flags); 39 | for (size_t j{}; j < v[i].flags.size(); ++j) { 40 | flags.Set(j, Napi::String::New(Env(), v[i].flags[j])); 41 | } 42 | 43 | arr.Set(i, rec); 44 | } 45 | 46 | obj.Set(k, arr); 47 | } 48 | 49 | Callback().Call({ obj }); 50 | } 51 | 52 | Napi::Value getRoutingTable(const Napi::CallbackInfo& info) { 53 | checkLength(info, 1); 54 | Napi::Function callback = info[0].As(); 55 | RoutingWorker* w = new RoutingWorker(callback); 56 | w->Queue(); 57 | return info.Env().Undefined(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /cxx/routing/Routing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | #include 5 | 6 | /* 7 | * Get ARP table for an interface 8 | */ 9 | 10 | namespace OverTheWire::Routing { 11 | struct RoutingRecord { 12 | std::string destination; 13 | std::string gateway; 14 | std::string mask; 15 | std::vector flags; 16 | }; 17 | 18 | using routing_table_t = std::map>; 19 | 20 | Napi::Object Init(Napi::Env env, Napi::Object exports); 21 | 22 | struct RoutingWorker : public Napi::AsyncWorker { 23 | RoutingWorker(Napi::Function&); 24 | ~RoutingWorker(); 25 | void Execute() override; 26 | void OnOK() override; 27 | 28 | Napi::Function& callback; 29 | routing_table_t res; 30 | }; 31 | 32 | std::pair fromSys(); 33 | 34 | Napi::Value getRoutingTable(const Napi::CallbackInfo& info); 35 | } 36 | -------------------------------------------------------------------------------- /cxx/routing/macOS.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define ROUNDUP(a) \ 29 | ((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t)) 30 | #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) 31 | 32 | struct bits { 33 | uint32_t b_mask; 34 | char b_val; 35 | } bits[] = { 36 | { RTF_UP, 'U' }, 37 | { RTF_GATEWAY, 'G' }, 38 | { RTF_HOST, 'H' }, 39 | { RTF_REJECT, 'R' }, 40 | { RTF_DYNAMIC, 'D' }, 41 | { RTF_MODIFIED, 'M' }, 42 | { RTF_MULTICAST,'m' }, 43 | { RTF_DONE, 'd' }, /* Completed -- for routing messages only */ 44 | { RTF_CLONING, 'C' }, 45 | { RTF_XRESOLVE, 'X' }, 46 | { RTF_LLINFO, 'L' }, 47 | { RTF_STATIC, 'S' }, 48 | { RTF_PROTO1, '1' }, 49 | { RTF_PROTO2, '2' }, 50 | { RTF_WASCLONED,'W' }, 51 | { RTF_PRCLONING,'c' }, 52 | { RTF_PROTO3, '3' }, 53 | { RTF_BLACKHOLE,'B' }, 54 | { RTF_BROADCAST,'b' }, 55 | { RTF_IFSCOPE, 'I' }, 56 | { RTF_IFREF, 'i' }, 57 | { RTF_PROXY, 'Y' }, 58 | { RTF_ROUTER, 'r' }, 59 | #ifdef RTF_GLOBAL 60 | { RTF_GLOBAL, 'g' }, 61 | #endif /* RTF_GLOBAL */ 62 | { 0 } 63 | }; 64 | 65 | typedef union { 66 | uint32_t dummy; /* Helps align structure. */ 67 | struct sockaddr u_sa; 68 | u_short u_data[128]; 69 | } sa_u; 70 | 71 | #define C(x) ((x) & 0xff) 72 | 73 | /* column widths; each followed by one space */ 74 | #define WID_DST(af) \ 75 | ((af) == AF_INET6 ? 39 : 18) 76 | #define WID_GW(af) \ 77 | ((af) == AF_INET6 ? 39 : 18) 78 | #define WID_RT_IFA(af) \ 79 | ((af) == AF_INET6 ? 39 : 18) 80 | #define WID_IF(af) 14 81 | -------------------------------------------------------------------------------- /cxx/routing/win32.cpp: -------------------------------------------------------------------------------- 1 | //https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getipnettable2 2 | 3 | #include "win32.hpp" 4 | 5 | namespace OverTheWire::Routing { 6 | 7 | std::pair fromSys() { 8 | routing_table_t res{}; 9 | 10 | DWORD retval; 11 | MIB_IPFORWARD_TABLE2 *routes = NULL; 12 | MIB_IPFORWARD_ROW2 *route; 13 | 14 | retval = GetIpForwardTable2(AF_INET, &routes); 15 | if (retval != ERROR_SUCCESS) { 16 | return {"GetIpForwardTable2 failed", res}; 17 | } 18 | 19 | for (int idx = 0; idx < routes->NumEntries; idx++) { 20 | route = routes->Table + idx; 21 | 22 | std::string iface; 23 | iface.resize(IF_MAX_STRING_SIZE + 1); 24 | 25 | DWORD dwSize = IF_MAX_STRING_SIZE + 1; 26 | DWORD dwRetVal = ConvertInterfaceLuidToNameW(&route->InterfaceLuid, (PWSTR)iface.data(), iface.size()); 27 | 28 | auto& vec = res[iface]; 29 | 30 | RoutingRecord rec; 31 | 32 | char str[INET6_ADDRSTRLEN]; 33 | auto& ipPrefix = route->DestinationPrefix; 34 | 35 | uv_inet_ntop( 36 | ipPrefix.Prefix.si_family, 37 | ipPrefix.Prefix.si_family == AF_INET6 ? 38 | (const void*)(&ipPrefix.Prefix.Ipv6) : 39 | (const void*)(&ipPrefix.Prefix.Ipv4), 40 | str, INET6_ADDRSTRLEN); 41 | 42 | rec.destination = str; 43 | 44 | auto& nextHop = route->NextHop; 45 | 46 | uv_inet_ntop( 47 | nextHop.si_family, 48 | nextHop.si_family == AF_INET6 ? 49 | (const void*)(&nextHop.Ipv6) : 50 | (const void*)(&nextHop.Ipv4), 51 | str, INET6_ADDRSTRLEN); 52 | 53 | rec.gateway = str; 54 | 55 | vec.push_back(rec); 56 | } 57 | 58 | return {"", res}; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /cxx/routing/win32.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Sys.hpp" 4 | #include 5 | #include 6 | -------------------------------------------------------------------------------- /cxx/toCxx.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | c_buffer_t toCxx(const Napi::Value& input) { 4 | auto res = std::make_pair((uint8_t*)nullptr, size_t{}); 5 | if (input.IsBuffer()) { 6 | js_buffer_t inputBuf = input.As(); 7 | res.second = inputBuf.Length(); 8 | res.first = new uint8_t[res.second + 1]; 9 | std::memcpy(res.first, inputBuf.Data(), res.second); 10 | } 11 | else if (input.IsTypedArray()) { 12 | Napi::TypedArray inputAr = input.As(); 13 | res.second = inputAr.ByteLength(); 14 | res.first = new uint8_t[res.second + 1]; 15 | std::memcpy(res.first, static_cast(inputAr.ArrayBuffer().Data()) + inputAr.ByteOffset(), res.second); 16 | } 17 | else { 18 | Napi::Error::New(input.Env(), "Invalid input type, expected either Buffer of TypedArray").ThrowAsJavaScriptException(); 19 | } 20 | DEBUG_OUTPUT((std::stringstream{} << "size is " << res.second).str()); 21 | return res; 22 | } 23 | 24 | c_buffer_t toCxx(const Napi::Value&& input) { 25 | return toCxx(input); 26 | } 27 | -------------------------------------------------------------------------------- /cxx/transports/pcap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PCAP_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Pcap.cpp" 3 | ) 4 | 5 | set(PCAP_HDR 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Pcap.hpp" 7 | ) 8 | 9 | source_group("Source Files\\Pcap" FILES ${PCAP_SRC}) 10 | source_group("Header Files\\Pcap" FILES ${PCAP_HDR}) 11 | 12 | target_sources(${PROJECT_NAME} PRIVATE ${PCAP_SRC} ${PCAP_HDR}) 13 | -------------------------------------------------------------------------------- /cxx/transports/pcap/Pcap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common.hpp" 5 | #include "stdlib.h" 6 | #include "PcapLiveDeviceList.h" 7 | #include "IpUtils.h" 8 | #include "SystemUtils.h" 9 | #include "pcap.h" 10 | 11 | /* PcapLiveDevice bindings that are specifically designed for 12 | * the Duplex stream wrapper. Btw, the only way to send L2 packets 13 | * on all platforms. Nice. Here we don't utilize the .pcap parsing 14 | * functions, because they are not stream-friendly for Node. 15 | * Streams are cool. 16 | */ 17 | 18 | namespace OverTheWire::Transports::Pcap { 19 | using device_t = pcpp::PcapLiveDevice; 20 | using device_ptr_t = std::shared_ptr; 21 | using Context = std::nullptr_t; 22 | using DataType = pcpp::RawPacket; 23 | void CallJs(Napi::Env, Napi::Function, Context*, DataType*); 24 | using TSFN = Napi::TypedThreadSafeFunction; 25 | using FinalizerDataType = void; 26 | using packets_t = std::vector; 27 | 28 | Napi::Object Init(Napi::Env env, Napi::Object exports); 29 | void onPacketArrivesRaw(pcpp::RawPacket*, pcpp::PcapLiveDevice*, void*); 30 | pcpp::RawPacket bufToPacket(js_buffer_t&&, timeval&); 31 | timeval getTime(); 32 | 33 | struct SendWorker : public Napi::AsyncWorker { 34 | SendWorker(device_ptr_t, Napi::Function&, packets_t&&); 35 | ~SendWorker(); 36 | void Execute() override; 37 | 38 | device_ptr_t dev; 39 | Napi::Function& callback; 40 | packets_t packets; 41 | }; 42 | 43 | struct PcapDevice : public Napi::ObjectWrap { 44 | static Napi::Object Init(Napi::Env, Napi::Object); 45 | PcapDevice(const Napi::CallbackInfo& info); 46 | ~PcapDevice(); 47 | Napi::Value _write(const Napi::CallbackInfo&); 48 | Napi::Value interfaceInfo(const Napi::CallbackInfo& info); 49 | Napi::Value stats(const Napi::CallbackInfo& info); 50 | Napi::Value setFilter(const Napi::CallbackInfo& info); 51 | Napi::Value setConfig(const Napi::CallbackInfo& info); 52 | Napi::Value open(const Napi::CallbackInfo& info); 53 | Napi::Value startCapture(const Napi::CallbackInfo& info); 54 | Napi::Value stopCapture(const Napi::CallbackInfo& info); 55 | Napi::Value _destroy(const Napi::CallbackInfo&); 56 | 57 | bool destroyed = false; 58 | void _destroy_impl(); 59 | 60 | pcpp::PcapLiveDevice::DeviceConfiguration config; 61 | device_ptr_t dev; 62 | bool hasPush = false; 63 | TSFN push; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /cxx/transports/socket/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOCKET_SRC 2 | "${CMAKE_CURRENT_SOURCE_DIR}/Socket.cpp" 3 | "${CMAKE_CURRENT_SOURCE_DIR}/Packets.cpp" 4 | "${CMAKE_CURRENT_SOURCE_DIR}/InputPackets.cpp" 5 | "${CMAKE_CURRENT_SOURCE_DIR}/SockAddr.cpp" 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Enums/Enums.cpp" 7 | ) 8 | 9 | set(SOCKET_HDR 10 | "${CMAKE_CURRENT_SOURCE_DIR}/Socket.hpp" 11 | "${CMAKE_CURRENT_SOURCE_DIR}/Packets.hpp" 12 | "${CMAKE_CURRENT_SOURCE_DIR}/InputPackets.hpp" 13 | "${CMAKE_CURRENT_SOURCE_DIR}/SockAddr.hpp" 14 | "${CMAKE_CURRENT_SOURCE_DIR}/SockAddr.hpp" 15 | "${CMAKE_CURRENT_SOURCE_DIR}/Enums/Enums.hpp" 16 | ) 17 | 18 | source_group("Source Files\\Socket" FILES ${SOCKET_SRC}) 19 | source_group("Header Files\\Socket" FILES ${SOCKET_HDR}) 20 | 21 | target_sources(${PROJECT_NAME} PRIVATE ${SOCKET_SRC} ${SOCKET_HDR}) 22 | -------------------------------------------------------------------------------- /cxx/transports/socket/Enums/Enums.cpp: -------------------------------------------------------------------------------- 1 | #include "Enums.hpp" 2 | 3 | namespace OverTheWire::Transports::Socket::Enums { 4 | 5 | #define ENUM_VALUE(x) exports.Set(#x, Napi::Number::New(env, x)) 6 | #define ENUM_VALUE_MANUAL(x, y) exports.Set(#x, Napi::Number::New(env, y)) 7 | 8 | #ifdef _WIN32 9 | #include "Windows.cpp" 10 | #elif defined(__linux__) || defined(__FreeBSD__) 11 | #include "Linux.cpp" 12 | #else 13 | #include "MacOS.cpp" 14 | #endif 15 | 16 | Napi::Object InitPorts(Napi::Env env, Napi::Object exports) { 17 | /* 18 | ENUM_VALUE_MANUAL(IPPORT_ECHO, 7); 19 | ENUM_VALUE_MANUAL(IPPORT_DISCARD, 9); 20 | ENUM_VALUE_MANUAL(IPPORT_SYSTAT, 11); 21 | ENUM_VALUE_MANUAL(IPPORT_DAYTIME, 13); 22 | ENUM_VALUE_MANUAL(IPPORT_NETSTAT, 15); 23 | ENUM_VALUE_MANUAL(IPPORT_FTP, 21); 24 | ENUM_VALUE_MANUAL(IPPORT_TELNET, 23); 25 | ENUM_VALUE_MANUAL(IPPORT_SMTP, 25); 26 | ENUM_VALUE_MANUAL(IPPORT_TIMESERVER, 37); 27 | ENUM_VALUE_MANUAL(IPPORT_NAMESERVER, 42); 28 | ENUM_VALUE_MANUAL(IPPORT_WHOIS, 43); 29 | ENUM_VALUE_MANUAL(IPPORT_MTP, 57); 30 | ENUM_VALUE_MANUAL(IPPORT_TFTP, 69); 31 | ENUM_VALUE_MANUAL(IPPORT_RJE, 77); 32 | ENUM_VALUE_MANUAL(IPPORT_FINGER, 79); 33 | ENUM_VALUE_MANUAL(IPPORT_TTYLINK, 87); 34 | ENUM_VALUE_MANUAL(IPPORT_SUPDUP, 95); 35 | ENUM_VALUE_MANUAL(IPPORT_EXECSERVER, 512); 36 | ENUM_VALUE_MANUAL(IPPORT_LOGINSERVER, 513); 37 | ENUM_VALUE_MANUAL(IPPORT_CMDSERVER, 514); 38 | ENUM_VALUE_MANUAL(IPPORT_EFSSERVER, 520); 39 | ENUM_VALUE_MANUAL(IPPORT_BIFFUDP, 512); 40 | ENUM_VALUE_MANUAL(IPPORT_WHOSERVER, 513); 41 | ENUM_VALUE_MANUAL(IPPORT_ROUTESERVER, 520); 42 | ENUM_VALUE_MANUAL(IPPORT_RESERVED, 1024); 43 | ENUM_VALUE_MANUAL(IPPORT_USERRESERVED, 5000); 44 | */ 45 | 46 | return exports; 47 | }; 48 | 49 | 50 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 51 | InitPorts(env, exports); 52 | InitSystemLocal(env, exports); 53 | return exports; 54 | } 55 | 56 | #undef ENUM_VALUE 57 | #undef ENUM_VALUE_MANUAL 58 | 59 | }; 60 | -------------------------------------------------------------------------------- /cxx/transports/socket/Enums/Enums.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hpp" 4 | 5 | /* This module is supposed to export some 6 | * system constants, just by putting them in the exports. 7 | * So we can do something new socket.Socket(socket.AF_INET, socket.SOCK_STREAM); 8 | * Sounds very simple, but again, differences between operating systems are hilarious. 9 | */ 10 | 11 | namespace OverTheWire::Transports::Socket::Enums { 12 | 13 | Napi::Object Init(Napi::Env env, Napi::Object exports); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /cxx/transports/socket/Enums/Linux.cpp: -------------------------------------------------------------------------------- 1 | Napi::Object InitSystemLocal(Napi::Env env, Napi::Object exports) { 2 | ENUM_VALUE(AF_UNIX); // Local communication unix(7) 3 | ENUM_VALUE(AF_LOCAL); // Synonym for AF_UNIX 4 | ENUM_VALUE(AF_INET); // IPv4 Internet protocols ip(7) 5 | ENUM_VALUE(AF_AX25); // Amateur radio AX.25 protocol ax25(4) 6 | ENUM_VALUE(AF_IPX); // IPX - Novell protocols 7 | ENUM_VALUE(AF_APPLETALK); // AppleTalk ddp(7) 8 | ENUM_VALUE(AF_X25); // ITU-T X.25 / ISO-8208 protocol x25(7) 9 | ENUM_VALUE(AF_INET6); // IPv6 Internet protocols ipv6(7) 10 | ENUM_VALUE(AF_DECnet); // DECet protocol sockets 11 | ENUM_VALUE(AF_KEY); // Key management protocol, originally 12 | // developed for usage with IPsec 13 | ENUM_VALUE(AF_NETLINK); // Kernel user interface device netlink(7) 14 | ENUM_VALUE(AF_PACKET); // Low-level packet interface packet(7) 15 | ENUM_VALUE(AF_RDS); // Reliable Datagram Sockets (RDS) protocol rds(7) 16 | 17 | ENUM_VALUE(AF_PPPOX); // Generic PPP transport layer, for setting 18 | // up L2 tunnels (L2TP and PPPoE) 19 | ENUM_VALUE(AF_LLC); // Logical link control (IEEE 802.2 LLC) 20 | // protocol 21 | ENUM_VALUE(AF_IB); // InfiniBand native addressing 22 | ENUM_VALUE(AF_MPLS); // Multiprotocol Label Switching 23 | ENUM_VALUE(AF_CAN); // Controller Area Network automotive bus 24 | // protocol 25 | ENUM_VALUE(AF_TIPC); // TIPC, "cluster domain sockets" protocol 26 | ENUM_VALUE(AF_BLUETOOTH); // Bluetooth low-level socket protocol 27 | ENUM_VALUE(AF_ALG); // Interface to kernel crypto API 28 | ENUM_VALUE(AF_VSOCK); // VSOCK (originally "VMWare VSockets") vsock(7) 29 | // protocol for hypervisor-guest 30 | // communication 31 | ENUM_VALUE(AF_KCM); // KCM (kernel connection multiplexer) 32 | // interface 33 | ENUM_VALUE(AF_XDP); // XDP (express data path) interface 34 | 35 | 36 | ENUM_VALUE(SOCK_STREAM); 37 | ENUM_VALUE(SOCK_DGRAM); 38 | ENUM_VALUE(SOCK_SEQPACKET); 39 | ENUM_VALUE(SOCK_RAW); 40 | ENUM_VALUE(SOCK_RDM); 41 | ENUM_VALUE(SOCK_PACKET); 42 | 43 | ENUM_VALUE(PF_UNIX); 44 | ENUM_VALUE(PF_LOCAL); 45 | ENUM_VALUE(PF_INET); 46 | ENUM_VALUE(PF_AX25); 47 | ENUM_VALUE(PF_IPX); 48 | ENUM_VALUE(PF_APPLETALK); 49 | ENUM_VALUE(PF_X25); 50 | ENUM_VALUE(PF_INET6); 51 | ENUM_VALUE(PF_DECnet); 52 | ENUM_VALUE(PF_KEY); 53 | 54 | ENUM_VALUE(PF_NETLINK); 55 | ENUM_VALUE(PF_PACKET); 56 | ENUM_VALUE(PF_RDS); 57 | 58 | ENUM_VALUE(PF_PPPOX); 59 | 60 | ENUM_VALUE(PF_LLC); 61 | 62 | ENUM_VALUE(PF_IB); 63 | ENUM_VALUE(PF_MPLS); 64 | ENUM_VALUE(PF_CAN); 65 | 66 | ENUM_VALUE(PF_TIPC); 67 | ENUM_VALUE(PF_BLUETOOTH); 68 | ENUM_VALUE(PF_ALG); 69 | ENUM_VALUE(PF_VSOCK); 70 | 71 | 72 | ENUM_VALUE(PF_KCM); 73 | 74 | ENUM_VALUE(PF_XDP); 75 | 76 | //setsockopt levels 77 | ENUM_VALUE(SOL_IP); 78 | ENUM_VALUE(SOL_IPV6); 79 | ENUM_VALUE(SOL_ICMPV6); 80 | ENUM_VALUE(SOL_RAW); 81 | ENUM_VALUE(SOL_DECNET); 82 | ENUM_VALUE(SOL_X25); 83 | ENUM_VALUE(SOL_PACKET); 84 | ENUM_VALUE(SOL_ATM); 85 | ENUM_VALUE(SOL_AAL); 86 | ENUM_VALUE(SOL_IRDA); 87 | ENUM_VALUE(SOL_NETBEUI); 88 | ENUM_VALUE(SOL_LLC); 89 | ENUM_VALUE(SOL_DCCP); 90 | ENUM_VALUE(SOL_NETLINK); 91 | ENUM_VALUE(SOL_TIPC); 92 | ENUM_VALUE(SOL_RXRPC); 93 | ENUM_VALUE(SOL_PPPOL2TP); 94 | ENUM_VALUE(SOL_BLUETOOTH); 95 | ENUM_VALUE(SOL_PNPIPE); 96 | ENUM_VALUE(SOL_RDS); 97 | ENUM_VALUE(SOL_IUCV); 98 | ENUM_VALUE(SOL_CAIF); 99 | ENUM_VALUE(SOL_ALG); 100 | ENUM_VALUE(SOL_NFC); 101 | ENUM_VALUE(SOL_KCM); 102 | ENUM_VALUE(SOL_TLS); 103 | ENUM_VALUE(SOL_XDP); 104 | 105 | ENUM_VALUE(IPPROTO_IP); 106 | ENUM_VALUE(IPPROTO_HOPOPTS); 107 | ENUM_VALUE(IPPROTO_ICMP); 108 | ENUM_VALUE(IPPROTO_IGMP); 109 | ENUM_VALUE(IPPROTO_IPIP); 110 | ENUM_VALUE(IPPROTO_TCP); 111 | ENUM_VALUE(IPPROTO_EGP); 112 | ENUM_VALUE(IPPROTO_PUP); 113 | ENUM_VALUE(IPPROTO_UDP); 114 | ENUM_VALUE(IPPROTO_IDP); 115 | ENUM_VALUE(IPPROTO_TP); 116 | ENUM_VALUE(IPPROTO_IPV6); 117 | ENUM_VALUE(IPPROTO_ROUTING); 118 | ENUM_VALUE(IPPROTO_FRAGMENT); 119 | ENUM_VALUE(IPPROTO_RSVP); 120 | ENUM_VALUE(IPPROTO_GRE); 121 | ENUM_VALUE(IPPROTO_ESP); 122 | ENUM_VALUE(IPPROTO_AH); 123 | ENUM_VALUE(IPPROTO_ICMPV6); 124 | ENUM_VALUE(IPPROTO_NONE); 125 | ENUM_VALUE(IPPROTO_DSTOPTS); 126 | ENUM_VALUE(IPPROTO_MTP); 127 | ENUM_VALUE(IPPROTO_ENCAP); 128 | ENUM_VALUE(IPPROTO_PIM); 129 | ENUM_VALUE(IPPROTO_COMP); 130 | ENUM_VALUE(IPPROTO_SCTP); 131 | ENUM_VALUE(IPPROTO_RAW); 132 | 133 | return exports; 134 | } 135 | -------------------------------------------------------------------------------- /cxx/transports/socket/Enums/MacOS.cpp: -------------------------------------------------------------------------------- 1 | Napi::Object InitSystemLocal(Napi::Env env, Napi::Object exports) { 2 | ENUM_VALUE(AF_UNIX); // Local communication unix(7) 3 | ENUM_VALUE(AF_LOCAL); // Synonym for AF_UNIX 4 | ENUM_VALUE(AF_INET); // IPv4 Internet protocols ip(7) 5 | ENUM_VALUE(AF_IPX); // IPX - Novell protocols 6 | ENUM_VALUE(AF_APPLETALK); // AppleTalk ddp(7) 7 | ENUM_VALUE(AF_INET6); // IPv6 Internet protocols ipv6(7) 8 | ENUM_VALUE(AF_DECnet); // DECet protocol sockets 9 | 10 | 11 | ENUM_VALUE(SOCK_STREAM); 12 | ENUM_VALUE(SOCK_DGRAM); 13 | ENUM_VALUE(SOCK_SEQPACKET); 14 | ENUM_VALUE(SOCK_RAW); 15 | ENUM_VALUE(SOCK_RDM); 16 | 17 | ENUM_VALUE(PF_UNIX); 18 | ENUM_VALUE(PF_LOCAL); 19 | ENUM_VALUE(PF_INET); 20 | ENUM_VALUE(PF_IPX); 21 | ENUM_VALUE(PF_APPLETALK); 22 | ENUM_VALUE(PF_INET6); 23 | ENUM_VALUE(PF_DECnet); 24 | ENUM_VALUE(PF_KEY); 25 | 26 | ENUM_VALUE(SO_DEBUG); 27 | ENUM_VALUE(SO_REUSEADDR); 28 | ENUM_VALUE(SO_REUSEPORT); 29 | ENUM_VALUE(SO_KEEPALIVE); 30 | ENUM_VALUE(SO_DONTROUTE); 31 | ENUM_VALUE(SO_LINGER); 32 | ENUM_VALUE(SO_BROADCAST); 33 | ENUM_VALUE(SO_OOBINLINE); 34 | ENUM_VALUE(SO_SNDBUF); 35 | ENUM_VALUE(SO_RCVBUF); 36 | ENUM_VALUE(SO_SNDLOWAT); 37 | ENUM_VALUE(SO_RCVLOWAT); 38 | ENUM_VALUE(SO_SNDTIMEO); 39 | ENUM_VALUE(SO_RCVTIMEO); 40 | ENUM_VALUE(SO_TYPE); 41 | ENUM_VALUE(SO_ERROR); 42 | ENUM_VALUE(SO_NOSIGPIPE); 43 | 44 | ENUM_VALUE(IPPROTO_IP); 45 | ENUM_VALUE(IPPROTO_HOPOPTS); 46 | ENUM_VALUE(IPPROTO_ICMP); 47 | ENUM_VALUE(IPPROTO_IGMP); 48 | ENUM_VALUE(IPPROTO_IPIP); 49 | ENUM_VALUE(IPPROTO_TCP); 50 | ENUM_VALUE(IPPROTO_EGP); 51 | ENUM_VALUE(IPPROTO_PUP); 52 | ENUM_VALUE(IPPROTO_UDP); 53 | ENUM_VALUE(IPPROTO_IDP); 54 | ENUM_VALUE(IPPROTO_TP); 55 | ENUM_VALUE(IPPROTO_IPV6); 56 | ENUM_VALUE(IPPROTO_ROUTING); 57 | ENUM_VALUE(IPPROTO_FRAGMENT); 58 | ENUM_VALUE(IPPROTO_RSVP); 59 | ENUM_VALUE(IPPROTO_GRE); 60 | ENUM_VALUE(IPPROTO_ESP); 61 | ENUM_VALUE(IPPROTO_AH); 62 | ENUM_VALUE(IPPROTO_ICMPV6); 63 | ENUM_VALUE(IPPROTO_NONE); 64 | ENUM_VALUE(IPPROTO_DSTOPTS); 65 | ENUM_VALUE(IPPROTO_MTP); 66 | ENUM_VALUE(IPPROTO_ENCAP); 67 | ENUM_VALUE(IPPROTO_PIM); 68 | ENUM_VALUE(IPPROTO_SCTP); 69 | ENUM_VALUE(IPPROTO_RAW); 70 | 71 | return exports; 72 | return exports; 73 | } 74 | -------------------------------------------------------------------------------- /cxx/transports/socket/Enums/Windows.cpp: -------------------------------------------------------------------------------- 1 | Napi::Object InitSystemLocal(Napi::Env env, Napi::Object exports) { 2 | // ENUM_VALUE(AF_UNSPEC); 3 | // ENUM_VALUE(AF_INET); 4 | // ENUM_VALUE(AF_IPX); 5 | // ENUM_VALUE(AF_APPLETALK); 6 | // ENUM_VALUE(AF_NETBIOS); 7 | // ENUM_VALUE(AF_INET6); 8 | // ENUM_VALUE(AF_IRDA); 9 | // ENUM_VALUE(AF_BTH); 10 | // 11 | // ENUM_VALUE(SOCK_STREAM); 12 | // ENUM_VALUE(SOCK_DGRAM); 13 | // ENUM_VALUE(SOCK_RAW); 14 | // ENUM_VALUE(SOCK_RDM); 15 | // ENUM_VALUE(SOCK_SEQPACKET); 16 | // 17 | // ENUM_VALUE(IPPROTO_ICMP); 18 | // ENUM_VALUE(IPPROTO_IGMP); 19 | // //ENUM_VALUE(BTHPROTO_RFCOMM); 20 | // ENUM_VALUE(IPPROTO_TCP); 21 | // ENUM_VALUE(IPPROTO_UDP); 22 | // ENUM_VALUE(IPPROTO_ICMPV6); 23 | // //ENUM_VALUE(IPPROTO_RM); 24 | // ENUM_VALUE(IPPROTO_IP); 25 | // ENUM_VALUE(IPPROTO_GGP); 26 | // ENUM_VALUE(IPPROTO_IPV4); 27 | // ENUM_VALUE(IPPROTO_ST); 28 | // ENUM_VALUE(IPPROTO_CBT); 29 | // ENUM_VALUE(IPPROTO_EGP); 30 | // ENUM_VALUE(IPPROTO_IGP); 31 | // ENUM_VALUE(IPPROTO_PUP); 32 | // ENUM_VALUE(IPPROTO_IDP); 33 | // ENUM_VALUE(IPPROTO_RDP); 34 | // ENUM_VALUE(IPPROTO_ND); 35 | // ENUM_VALUE(IPPROTO_ICLFXBM); 36 | // ENUM_VALUE(IPPROTO_PIM); 37 | // ENUM_VALUE(IPPROTO_PGM); 38 | // ENUM_VALUE(IPPROTO_L2TP); 39 | // ENUM_VALUE(IPPROTO_SCTP); 40 | // ENUM_VALUE(IPPROTO_RAW); 41 | // 42 | // ENUM_VALUE(SO_BROADCAST); 43 | // ENUM_VALUE(SO_CONDITIONAL_ACCEPT); 44 | // ENUM_VALUE(SO_EXCLUSIVEADDRUSE); 45 | // ENUM_VALUE(SO_KEEPALIVE); 46 | // ENUM_VALUE(SO_RCVBUF); 47 | // ENUM_VALUE(SO_REUSEADDR); 48 | // 49 | return exports; 50 | } 51 | -------------------------------------------------------------------------------- /cxx/transports/socket/InputPackets.cpp: -------------------------------------------------------------------------------- 1 | #include "InputPackets.hpp" 2 | 3 | namespace OverTheWire::Transports::Socket { 4 | 5 | read_res_t&& emptyRes() { 6 | auto res = std::make_pair( 7 | std::make_pair, size_t>(nullptr, 0), 8 | std::make_pair, size_t>(nullptr, 0) 9 | ); 10 | return std::move(res); 11 | } 12 | 13 | InputPacketsIterator::InputPacketsIterator(InputPacketReader& parent, size_t idx, bool isNull) 14 | : parent{parent}, idx{idx}, isNull{isNull} {} 15 | 16 | InputPacketsIterator::InputPacketsIterator(const InputPacketsIterator& other) 17 | : parent{other.parent}, idx{other.idx}, isNull{other.isNull}, value{std::make_pair(std::string{""}, emptyRes())} {} 18 | 19 | void InputPacketsIterator::read() { 20 | if (was) return; 21 | was = true; 22 | 23 | #ifdef _WIN32 24 | //TODO 25 | WSAOVERLAPPED ioOverlapped = { 0 }; 26 | DWORD bytes = 0; 27 | DWORD flags = 0; 28 | int result; 29 | WSABUF buf; 30 | buf.buf = (char*)new uint8_t[parent.bufSize]; 31 | buf.len = parent.bufSize; 32 | 33 | if (parent.queryAddr) { 34 | sockaddr_ptr_t fromPtr {(sockaddr*)(new sockaddr_storage)}; 35 | int from_len; 36 | memset(fromPtr.get(), 0, sizeof sockaddr_storage); 37 | from_len = sizeof sockaddr_storage; 38 | result = WSARecvFrom(parent.fd, 39 | (WSABUF*)&buf, 40 | 1, 41 | &bytes, 42 | &flags, 43 | fromPtr.get(), 44 | &from_len, 45 | NULL, 46 | NULL); 47 | value.pData.pAddr = std::make_pair(std::move(fromPtr), from_len); 48 | } 49 | else { 50 | result = WSARecv(parent.fd, 51 | (WSABUF*)&buf, 52 | 1, 53 | &bytes, 54 | &flags, 55 | &ioOverlapped, 56 | NULL); 57 | } 58 | if (result == -1) { 59 | value.pErr = getSystemError(); 60 | isNull = true; 61 | delete buf.buf; 62 | } 63 | value.pData.pBuf = std::make_pair(decltype(value.second.first.first){(uint8_t*)buf.buf}, result); 64 | //TODO recvmmsg is way too hard to implenet here, so I save it for later 65 | #else 66 | msghdr h{}; 67 | ssize_t nread = 0; 68 | std::unique_ptr peer = decltype(peer){(sockaddr*)new struct sockaddr_storage}; 69 | std::unique_ptr buf = decltype(buf){new uint8_t[parent.bufSize]}; 70 | std::unique_ptr iovec = decltype(iovec){new struct iovec}; 71 | 72 | iovec->iov_base = (void*)buf.get(); 73 | iovec->iov_len = parent.bufSize; 74 | 75 | memset(&h, 0, sizeof(h)); 76 | memset(peer.get(), 0, sizeof(*peer.get())); 77 | h.msg_name = peer.get(); 78 | h.msg_namelen = sizeof(sockaddr_storage); 79 | h.msg_iov = iovec.get(); 80 | h.msg_iovlen = 1; 81 | 82 | do { 83 | nread = recvmsg(parent.fd, &h, 0); 84 | } while (nread == -1 && errno == EINTR); 85 | 86 | if (nread == -1) { 87 | value.pErr = getSystemError(); 88 | isNull = true; 89 | } 90 | else { 91 | value.pData.pBuf = std::make_pair(std::move(buf), nread); 92 | if (parent.queryAddr) { 93 | value.pData.pAddr = std::make_pair(std::move(peer), sizeof(sockaddr_storage)); 94 | } 95 | } 96 | #endif 97 | } 98 | 99 | void InputPacketsIterator::incr() { 100 | if (isNull) return; 101 | if (++idx == parent.maxRead) { 102 | isNull = true; 103 | idx = -1; 104 | } 105 | } 106 | 107 | InputPacketsIterator::reference InputPacketsIterator::operator*() { 108 | read(); 109 | return value; 110 | } 111 | 112 | InputPacketsIterator::pointer InputPacketsIterator::operator->() { 113 | read(); 114 | return &value; 115 | } 116 | 117 | InputPacketsIterator& InputPacketsIterator::operator++() { 118 | read(); 119 | incr(); 120 | return *this; 121 | } 122 | 123 | InputPacketsIterator InputPacketsIterator::operator++(int) { 124 | auto tmp = *this; 125 | read(); 126 | incr(); 127 | return tmp; 128 | } 129 | 130 | bool operator==(const InputPacketsIterator& a, const InputPacketsIterator& b) { 131 | if (a.isNull && b.isNull) return true; 132 | return a.parent.fd == b.parent.fd && a.idx == b.idx; 133 | }; 134 | 135 | bool operator!=(const InputPacketsIterator& a, const InputPacketsIterator& b) { 136 | if (a.isNull && b.isNull) return false; 137 | return a.parent.fd != b.parent.fd || a.idx != b.idx; 138 | }; 139 | 140 | 141 | InputPacketReader::InputPacketReader(SOCKET fd, size_t bufSize = defaultBufferSize, bool queryAddr = true) : fd{fd}, queryAddr{queryAddr}, bufSize{bufSize} {} 142 | 143 | InputPacketsIterator InputPacketReader::begin() { 144 | return InputPacketsIterator(*this, 0, false); 145 | } 146 | 147 | InputPacketsIterator InputPacketReader::end() { 148 | return InputPacketsIterator(*this, -1, true); 149 | } 150 | 151 | }; 152 | -------------------------------------------------------------------------------- /cxx/transports/socket/InputPackets.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "common.hpp" 6 | #include "Sys.hpp" 7 | #include "error/Error.hpp" 8 | #include "SockAddr.hpp" 9 | 10 | /* 11 | * This is the class that tries to abstract away 12 | * the process of reading from socket with C++ iterators. 13 | */ 14 | 15 | namespace OverTheWire::Transports::Socket { 16 | 17 | #define pErr first 18 | #define pData second 19 | #define pBuf first 20 | #define pAddr second 21 | 22 | const size_t defaultBufferSize = 65'535; 23 | 24 | struct InputPacketReader; 25 | 26 | using read_res_t = std::pair; 27 | read_res_t&& emptyRes(); 28 | 29 | struct InputPacketsIterator { 30 | using iterator_category = std::input_iterator_tag; 31 | using difference_type = size_t; 32 | using value_type = std::pair; 33 | using pointer = value_type*; 34 | using reference = value_type&; 35 | 36 | InputPacketsIterator(InputPacketReader&, size_t idx, bool isNull); 37 | InputPacketsIterator(const InputPacketsIterator&); 38 | 39 | reference operator*(); 40 | pointer operator->(); 41 | 42 | InputPacketsIterator& operator++(); 43 | InputPacketsIterator operator++(int); 44 | 45 | friend bool operator==(const InputPacketsIterator& a, const InputPacketsIterator& b); 46 | friend bool operator!=(const InputPacketsIterator& a, const InputPacketsIterator& b); 47 | 48 | void read(); 49 | void incr(); 50 | 51 | value_type value; 52 | bool was = false; 53 | InputPacketReader& parent; 54 | size_t idx; 55 | bool isNull; 56 | }; 57 | 58 | struct InputPacketReader { 59 | InputPacketReader(SOCKET, size_t, bool); 60 | InputPacketsIterator begin(); 61 | InputPacketsIterator end(); 62 | 63 | SOCKET fd; 64 | size_t maxRead = 32; 65 | size_t bufSize; 66 | bool queryAddr = true; 67 | }; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /cxx/transports/socket/Packets.cpp: -------------------------------------------------------------------------------- 1 | #include "Packets.hpp" 2 | 3 | namespace OverTheWire::Transports::Socket { 4 | 5 | Packets::Packets(int& flags, bool& connected) : flags{flags}, connected{connected} {} 6 | 7 | #ifndef _WIN32 8 | std::pair createMsghdr(uint8_t* buf, size_t size, sockaddr* addr, size_t addrSize, iovec* msgIov, bool connected) { 9 | auto res = std::make_pair(true, msghdr{}); 10 | if (addr->sa_family == AF_UNSPEC || connected) { 11 | res.second.msg_name = NULL; 12 | res.second.msg_namelen = 0; 13 | } 14 | else { 15 | res.second.msg_name = addr; 16 | res.second.msg_namelen = addrSize; 17 | } 18 | msgIov->iov_base = buf; 19 | msgIov->iov_len = size; 20 | res.second.msg_iov = msgIov; 21 | res.second.msg_iovlen = 1; 22 | 23 | return res; 24 | } 25 | #endif 26 | 27 | bool Packets::add(uint8_t* buf, size_t size, SockAddr* target) { 28 | addr_t addr; 29 | sockaddr* addrRaw = nullptr; 30 | if (!connected || addrs.size() == 0) { 31 | std::string err; 32 | std::tie(err, addr) = target->addr(); 33 | if (err.size() > 0 && !connected) { 34 | return false; 35 | } 36 | addrRaw = addr.first.get(); 37 | addrs.push_back(std::move(addr)); 38 | } 39 | #ifdef _WIN32 40 | WSABUF pkt; 41 | pkt.len = size; 42 | pkt.buf = (char*)buf; 43 | packets.push_back(pkt); 44 | #else 45 | iovecs.emplace_back(); 46 | iovec* iovec = &iovecs.back(); 47 | bool ok; 48 | msghdr msg; 49 | std::tie(ok, msg) = createMsghdr(buf, size, addrRaw, addr.second, iovec, connected); 50 | if (!ok) return false; 51 | #if defined(__linux__) || defined(__FreeBSD__) 52 | mmsghdr p; 53 | p.msg_hdr = std::move(msg); 54 | packets.emplace_back(std::move(p)); 55 | #else 56 | packets.emplace_back(std::move(msg)); 57 | #endif 58 | #endif 59 | return true; 60 | } 61 | 62 | SendStatus Packets::send(SOCKET fd) { 63 | #ifdef _WIN32 64 | int result; 65 | DWORD bytes; 66 | WSAOVERLAPPED ioOverlapped = { 0 }; 67 | if (connected) { 68 | assert(packets.size() == addrs.size()); 69 | } 70 | while (!packets.empty()) { 71 | auto* pkt = &packets.front(); 72 | 73 | if (connected) { 74 | result = WSASend(fd, 75 | pkt, 76 | 1, 77 | &bytes, 78 | 0, 79 | &ioOverlapped, 80 | NULL); 81 | 82 | } 83 | else { 84 | auto& addr = addrs.front(); 85 | result = WSASendTo(fd, 86 | pkt, 87 | 1, 88 | &bytes, 89 | 0, 90 | addr.first.get(), 91 | addr.second, 92 | &ioOverlapped, 93 | NULL); 94 | } 95 | 96 | if (result == 0) { 97 | packets.pop_front(); 98 | if (!connected) { 99 | addrs.pop_front(); 100 | } 101 | } 102 | 103 | if (GetLastError() == ERROR_IO_PENDING) { 104 | return SendStatus::again; 105 | } 106 | 107 | return SendStatus::fail; 108 | } 109 | 110 | return SendStatus::ok; 111 | 112 | #elif defined(__linux__) || defined(__FreeBSD__) 113 | int npkts; 114 | do { 115 | npkts = sendmmsg(fd, packets.data(), packets.size(), flags); 116 | } while (npkts == -1 && errno == EINTR); 117 | 118 | if (npkts < 1) { 119 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) { 120 | return SendStatus::again; 121 | } 122 | return SendStatus::fail; 123 | } 124 | addrs.clear(); 125 | packets.clear(); 126 | iovecs.clear(); 127 | return SendStatus::ok; 128 | #else 129 | assert(packets.size() == iovecs.size()); 130 | if (!connected) { 131 | assert(packets.size() == addrs.size()); 132 | } 133 | int npkts; 134 | while (!packets.empty()) { 135 | auto& pkt = packets.front(); 136 | int size; 137 | do { 138 | size = sendmsg(fd, &pkt, flags); 139 | } while (npkts == -1 && errno == EINTR); 140 | DEBUG_OUTPUT(size); 141 | 142 | if (size < 1) { 143 | DEBUG_OUTPUT(getSystemError()); 144 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) { 145 | return SendStatus::again; 146 | } 147 | return SendStatus::fail; 148 | } 149 | 150 | packets.pop_front(); 151 | iovecs.pop_front(); 152 | addrs.pop_front(); 153 | } 154 | return SendStatus::ok; 155 | #endif 156 | } 157 | 158 | size_t Packets::size() { 159 | return packets.size(); 160 | } 161 | 162 | }; 163 | -------------------------------------------------------------------------------- /cxx/transports/socket/Packets.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "common.hpp" 7 | #include "Sys.hpp" 8 | #include "error/Error.hpp" 9 | #include "SockAddr.hpp" 10 | 11 | /* 12 | * The Packets class is intended to abstract away 13 | * the differences between OS's in terms of packet storage format 14 | * and sending methods, but introduces more problems than it solves. 15 | * On Linux we can send multiple packets to multiple destinations with 16 | * a single call (Linux superiority as always), on Windows we can send multiple packets to a single 17 | * destination, while on MacOS we can send only one packet at a time @_@ 18 | */ 19 | 20 | namespace OverTheWire::Transports::Socket { 21 | 22 | enum class SendStatus { again, ok, fail }; 23 | 24 | struct Packets { 25 | Packets(int& flags, bool& connected); 26 | bool add(uint8_t*, size_t, SockAddr*); 27 | size_t size(); 28 | SendStatus send(SOCKET); 29 | 30 | int& flags; 31 | bool& connected; 32 | #ifdef _WIN32 33 | std::deque addrs; 34 | std::deque packets; 35 | #elif defined(__linux__) || defined(__FreeBSD__) 36 | std::vector addrs; 37 | std::vector iovecs; 38 | std::vector packets; 39 | #else 40 | std::deque addrs; 41 | std::deque iovecs; 42 | std::deque packets; 43 | #endif 44 | }; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /cxx/transports/socket/SockAddr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "common.hpp" 6 | #include "error/Error.hpp" 7 | 8 | /* 9 | * Container for sockaddr struct. Some of the useful info below. 10 | * from https://docs.libuv.org/en/v1.x/misc.html#c.uv_ip4_addr 11 | * int uv_ip4_addr(const char *ip, int port, struct sockaddr_in *addr) 12 | * Convert a string containing an IPv4 addresses to a binary structure. 13 | * int uv_ip6_addr(const char *ip, int port, struct sockaddr_in6 *addr) 14 | * Convert a string containing an IPv6 addresses to a binary structure. 15 | * int uv_ip_name(const struct sockaddr *src, char *dst, size_t size) 16 | * Convert a binary structure containing an IPv4 address or an IPv6 address to a string. 17 | * int uv_inet_ntop(int af, const void *src, char *dst, size_t size) 18 | * int uv_inet_pton(int af, const char *src, void *dst) 19 | * Cross-platform IPv6-capable implementation of inet_ntop(3) and inet_pton(3). On success they return 0. In case of error the target dst pointer is unmodified. 20 | */ 21 | 22 | namespace OverTheWire::Transports::Socket { 23 | 24 | using sockaddr_ptr_t = std::unique_ptr; 25 | using addr_t = std::pair; 26 | 27 | int ip_name(const struct sockaddr*, char*, size_t); 28 | 29 | struct SockAddr : public Napi::ObjectWrap { 30 | static Napi::Object Init(Napi::Env, Napi::Object); 31 | static Napi::Value fromRaw(Napi::Env, addr_t&&); 32 | static Napi::FunctionReference*& ctor() { 33 | static Napi::FunctionReference* _ctor; 34 | return _ctor; 35 | }; 36 | SockAddr(const Napi::CallbackInfo& info); 37 | ~SockAddr(); 38 | bool genName(Napi::Env, bool); 39 | std::pair addr(); 40 | 41 | Napi::Value toString(const Napi::CallbackInfo&); 42 | Napi::Value toBuffer(const Napi::CallbackInfo&); 43 | Napi::Value toHuman(const Napi::CallbackInfo&); 44 | 45 | Napi::Value getPort(const Napi::CallbackInfo&); 46 | void setPort(const Napi::CallbackInfo&, const Napi::Value&); 47 | 48 | Napi::Value getIp(const Napi::CallbackInfo&); 49 | void setIp(const Napi::CallbackInfo&, const Napi::Value&); 50 | 51 | Napi::Value getDomain(const Napi::CallbackInfo&); 52 | void setDomain(const Napi::CallbackInfo&, const Napi::Value&); 53 | 54 | std::string ip; 55 | int port = 0; 56 | int domain = -1; 57 | 58 | char name[INET6_ADDRSTRLEN]; 59 | }; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /cxx/transports/socket/Socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "common.hpp" 7 | #include "Packets.hpp" 8 | #include "InputPackets.hpp" 9 | #include "error/Error.hpp" 10 | #include "SockAddr.hpp" 11 | #include "Enums/Enums.hpp" 12 | 13 | /* This class tries to tie together the OS-specific API, N-API and libuv. 14 | * This is quite hard. 15 | */ 16 | 17 | namespace OverTheWire::Transports::Socket { 18 | 19 | Napi::Object Init(Napi::Env env, Napi::Object exports); 20 | 21 | struct Socket : public Napi::ObjectWrap { 22 | static Napi::Object Init(Napi::Env, Napi::Object); 23 | Socket(const Napi::CallbackInfo& info); 24 | ~Socket(); 25 | Napi::Value write(const Napi::CallbackInfo&); 26 | void createSocket(Napi::Env); 27 | void initSocket(Napi::Env); 28 | void pollStart(); 29 | void handleIOEvent(int, int); 30 | void setFlag(int, bool); 31 | void close(); 32 | bool getFlag(int); 33 | 34 | bool processReq(Napi::Env&, const Napi::Value&&, const Napi::Object&&); 35 | 36 | Napi::Value resume(const Napi::CallbackInfo&); 37 | Napi::Value pause(const Napi::CallbackInfo&); 38 | Napi::Value close(const Napi::CallbackInfo&); 39 | Napi::Value bind(const Napi::CallbackInfo&); 40 | Napi::Value connect(const Napi::CallbackInfo&); 41 | Napi::Value setsockopt(const Napi::CallbackInfo&); 42 | Napi::Value getsockopt(const Napi::CallbackInfo&); 43 | Napi::Value ioctl(const Napi::CallbackInfo&); 44 | Napi::Value toHuman(const Napi::CallbackInfo&); 45 | 46 | Napi::Value getBufferSize(const Napi::CallbackInfo&); 47 | void setBufferSize(const Napi::CallbackInfo&, const Napi::Value&); 48 | 49 | void refForRead(); 50 | void refForWrite(); 51 | void unrefForRead(); 52 | void unrefForWrite(); 53 | 54 | bool connected = false; 55 | std::string boundTo = ""; 56 | std::string connectedTo = ""; 57 | int flags = 0; 58 | int sendFlags = 0; 59 | int domain; 60 | int type; 61 | int protocol; 62 | int pollFlags = 0; 63 | size_t bufferSize = defaultBufferSize; 64 | SOCKET pollfd = 0; 65 | std::unique_ptr pollWatcher; 66 | size_t writeRefsCount = 0; 67 | size_t readRefsCount = 0; 68 | Packets packets; 69 | }; 70 | 71 | static void IoEvent(uv_poll_t*, int, int); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": ["lib", "lib/layers"], 4 | "exclude": ["lib/checks"], 5 | "includePattern": ".js$", 6 | "excludePattern": "(node_modules/|docs)" 7 | }, 8 | "opts": { 9 | "destination": "./docs", 10 | "template": "node_modules/clean-jsdoc-theme", 11 | "readme": "./README.md" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | checks 2 | -------------------------------------------------------------------------------- /lib/af.js: -------------------------------------------------------------------------------- 1 | const { socket } = require('#lib/bindings'); 2 | const { isIPv4, isIPv6 } = require('net'); 3 | 4 | function defaultFamily(addr) { 5 | if (isIPv4(addr)) { 6 | return socket.AF_INET; 7 | } 8 | else if (isIPv6(addr)) { 9 | return socket.AF_INET6; 10 | } 11 | 12 | throw new Error(`Uknown addr type ${addr}`); 13 | } 14 | 15 | function updateDomain(obj, addr) { 16 | obj.domain = defaultFamily(addr); 17 | } 18 | 19 | module.exports = { defaultFamily, updateDomain }; 20 | -------------------------------------------------------------------------------- /lib/arp.js: -------------------------------------------------------------------------------- 1 | const os = require('node:os'); 2 | const fs = require('node:fs'); 3 | const readline = require('node:readline'); 4 | 5 | const platform = os.platform(); 6 | 7 | 8 | /** 9 | * Retrieves the ARP (Address Resolution Protocol) table for all interfaces. 10 | * 11 | * @async 12 | * @function getArpTable 13 | * @returns {Promise} An object where the keys are interface names and the values are arrays of ARP records. 14 | * Each ARP record is represented by an object with `ipAddr` (IP address) and `hwAddr` (hardware address) properties. 15 | * 16 | * @example 17 | * const arpTable = await getArpTable(); 18 | * // arpTable = { 19 | * // eth0: [ 20 | * // { ipAddr: '192.168.1.1', hwAddr: '00:1A:2B:3C:4D:5E' }, 21 | * // { ipAddr: '192.168.1.2', hwAddr: '00:1A:2B:3C:4D:5F' } 22 | * // ], 23 | * // wlan0: [ 24 | * // { ipAddr: '192.168.1.3', hwAddr: '00:1A:2B:3C:4D:60' } 25 | * // ] 26 | * // } 27 | */ 28 | 29 | if (platform === 'linux') { 30 | async function getArpTable() { 31 | const rl = readline.createInterface({ 32 | input: fs.createReadStream('/proc/net/arp'), 33 | crlfDelay: Infinity, 34 | }); 35 | 36 | const res = {}; 37 | let fst = true; 38 | for await (const line of rl) { 39 | if (fst) { 40 | fst = false; 41 | continue; 42 | } 43 | const [ipAddr, hwType, flags, hwAddr, _, device] = line.split(/\s/).filter(e => e.length > 0); 44 | if (!res[device]) { 45 | res[device] = []; 46 | } 47 | res[device].push({ ipAddr, hwType: Number(hwType), flags: Number(flags), hwAddr }); 48 | } 49 | return res; 50 | } 51 | 52 | module.exports = { getArpTable }; 53 | } 54 | else { 55 | const { getArpTable: getArpTableCxx } = require('./bindings'); 56 | 57 | module.exports = { 58 | async getArpTable() { 59 | return new Promise((resolve, reject) => { 60 | getArpTableCxx(res => { 61 | if (res instanceof Error) { 62 | reject(res); 63 | } 64 | resolve(res); 65 | }); 66 | }); 67 | }, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/bindings.js: -------------------------------------------------------------------------------- 1 | global.require = require; 2 | module.exports = require('bindings')('over-the-wire'); 3 | -------------------------------------------------------------------------------- /lib/bpfFilter.js: -------------------------------------------------------------------------------- 1 | const { BpfFilter: BpfFilterCxx } = require('#lib/bindings'); 2 | const { Packet } = require('#lib/packet'); 3 | 4 | class BpfFilter extends BpfFilterCxx { 5 | constructor(...args) { 6 | super(...args); 7 | } 8 | 9 | match(pkt, ...args) { 10 | if (pkt instanceof Packet) { 11 | return super.match(pkt.buffer, ...args); 12 | } 13 | return super.match(pkt, ...args); 14 | } 15 | } 16 | 17 | module.exports = { BpfFilter }; 18 | -------------------------------------------------------------------------------- /lib/buffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | * @param buf {Buffer} 4 | * @param at {number} 5 | * @param size {number} 6 | * @return {Buffer} 7 | */ 8 | const extendAt = (buf, at, size) => { 9 | return Buffer.concat([buf.subarray(0, at), Buffer.alloc(size), buf.subarray(at)]); 10 | }; 11 | 12 | /** 13 | * @private 14 | * @param buf {Buffer} 15 | * @param at {number} 16 | * @param size {number} 17 | * @return {Buffer} 18 | */ 19 | const shrinkAt = (buf, at, size) => { 20 | return Buffer.concat([buf.subarray(0, at), buf.subarray(at + size)]); 21 | }; 22 | 23 | function fromNumber(num){ 24 | const bufAr = []; 25 | 26 | do { 27 | bufAr.push(num & 0xff); 28 | } while((num >>= 8) > 0); 29 | 30 | return Buffer.from(bufAr.reverse()) 31 | } 32 | 33 | module.exports = { extendAt, shrinkAt, fromNumber }; 34 | -------------------------------------------------------------------------------- /lib/converters.js: -------------------------------------------------------------------------------- 1 | const { socket } = require('#lib/bindings'); 2 | const { converters } = require('#lib/bindings'); 3 | const { defaultFamily } = require('#lib/af'); 4 | 5 | const { inetNtop, inetPton } = converters; 6 | 7 | converters.inetNtop = (...args) => { 8 | if (args.length == 1) { 9 | const [addr] = args; 10 | try { 11 | return inetNtop(socket.AF_INET, addr); 12 | } catch(err) { 13 | return inetNtop(socket.AF_INET6, addr); 14 | } 15 | } 16 | return inetNtop(...args); 17 | }; 18 | 19 | converters.inetPton = (...args) => { 20 | if (args.length == 1) { 21 | const [addr] = args; 22 | return inetPton(defaultFamily(addr), addr); 23 | } 24 | return inetPton(...args); 25 | } 26 | 27 | module.exports = converters; 28 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | const { LinkLayerType } = require('./enums'); 2 | 3 | module.exports = { 4 | snaplen: 65535, 5 | linktype: LinkLayerType.LINKTYPE_ETHERNET, 6 | }; 7 | -------------------------------------------------------------------------------- /lib/enums.js: -------------------------------------------------------------------------------- 1 | const { LinkLayerType } = require('#lib/bindings'); 2 | 3 | /* 4 | * @typedef LinkLayerType 5 | * @property {number} LINKTYPE_NULL 6 | * @property {number} LINKTYPE_ETHERNET 7 | * @property {number} LINKTYPE_AX25 8 | * @property {number} LINKTYPE_IEEE802_5 9 | * @property {number} LINKTYPE_ARCNET_BSD 10 | * @property {number} LINKTYPE_SLIP 11 | * @property {number} LINKTYPE_PPP 12 | * @property {number} LINKTYPE_FDDI 13 | * @property {number} LINKTYPE_DLT_RAW1 14 | * @property {number} LINKTYPE_DLT_RAW2 15 | * @property {number} LINKTYPE_PPP_HDLC 16 | * @property {number} LINKTYPE_PPP_ETHER 17 | * @property {number} LINKTYPE_ATM_RFC1483 18 | * @property {number} LINKTYPE_RAW 19 | * @property {number} LINKTYPE_C_HDLC 20 | * @property {number} LINKTYPE_IEEE802_11 21 | * @property {number} LINKTYPE_FRELAY 22 | * @property {number} LINKTYPE_LOOP 23 | * @property {number} LINKTYPE_LINUX_SLL 24 | * @property {number} LINKTYPE_LTALK 25 | * @property {number} LINKTYPE_PFLOG 26 | * @property {number} LINKTYPE_IEEE802_11_PRISM 27 | * @property {number} LINKTYPE_IP_OVER_FC 28 | * @property {number} LINKTYPE_SUNATM 29 | * @property {number} LINKTYPE_IEEE802_11_RADIOTAP 30 | * @property {number} LINKTYPE_ARCNET_LINUX 31 | * @property {number} LINKTYPE_APPLE_IP_OVER_IEEE1394 32 | * @property {number} LINKTYPE_MTP2_WITH_PHDR 33 | * @property {number} LINKTYPE_MTP2 34 | * @property {number} LINKTYPE_MTP3 35 | * @property {number} LINKTYPE_SCCP 36 | * @property {number} LINKTYPE_DOCSIS 37 | * @property {number} LINKTYPE_LINUX_IRDA 38 | * @property {number} LINKTYPE_USER0 39 | * @property {number} LINKTYPE_USER1 40 | * @property {number} LINKTYPE_USER2 41 | * @property {number} LINKTYPE_USER3 42 | * @property {number} LINKTYPE_USER4 43 | * @property {number} LINKTYPE_USER5 44 | * @property {number} LINKTYPE_USER6 45 | * @property {number} LINKTYPE_USER7 46 | * @property {number} LINKTYPE_USER8 47 | * @property {number} LINKTYPE_USER9 48 | * @property {number} LINKTYPE_USER10 49 | * @property {number} LINKTYPE_USER11 50 | * @property {number} LINKTYPE_USER12 51 | * @property {number} LINKTYPE_USER13 52 | * @property {number} LINKTYPE_USER14 53 | * @property {number} LINKTYPE_USER15 54 | * @property {number} LINKTYPE_IEEE802_11_AVS 55 | * @property {number} LINKTYPE_BACNET_MS_TP 56 | * @property {number} LINKTYPE_PPP_PPPD 57 | * @property {number} LINKTYPE_GPRS_LLC 58 | * @property {number} LINKTYPE_GPF_T 59 | * @property {number} LINKTYPE_GPF_F 60 | * @property {number} LINKTYPE_LINUX_LAPD 61 | * @property {number} LINKTYPE_BLUETOOTH_HCI_H4 62 | * @property {number} LINKTYPE_USB_LINUX 63 | * @property {number} LINKTYPE_PPI 64 | * @property {number} LINKTYPE_IEEE802_15_4 65 | * @property {number} LINKTYPE_SITA 66 | * @property {number} LINKTYPE_ERF 67 | * @property {number} LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR 68 | * @property {number} LINKTYPE_AX25_KISS 69 | * @property {number} LINKTYPE_LAPD 70 | * @property {number} LINKTYPE_PPP_WITH_DIR 71 | * @property {number} LINKTYPE_C_HDLC_WITH_DIR 72 | * @property {number} LINKTYPE_FRELAY_WITH_DIR 73 | * @property {number} LINKTYPE_IPMB_LINUX 74 | * @property {number} LINKTYPE_IEEE802_15_4_NONASK_PHY 75 | * @property {number} LINKTYPE_USB_LINUX_MMAPPED 76 | * @property {number} LINKTYPE_FC_2 77 | * @property {number} LINKTYPE_FC_2_WITH_FRAME_DELIMS 78 | * @property {number} LINKTYPE_IPNET 79 | * @property {number} LINKTYPE_CAN_SOCKETCAN 80 | * @property {number} LINKTYPE_IPV4 81 | * @property {number} LINKTYPE_IPV6 82 | * @property {number} LINKTYPE_IEEE802_15_4_NOFCS 83 | * @property {number} LINKTYPE_DBUS 84 | * @property {number} LINKTYPE_DVB_CI 85 | * @property {number} LINKTYPE_MUX27010 86 | * @property {number} LINKTYPE_STANAG_5066_D_PDU 87 | * @property {number} LINKTYPE_NFLOG 88 | * @property {number} LINKTYPE_NETANALYZER 89 | * @property {number} LINKTYPE_NETANALYZER_TRANSPARENT 90 | * @property {number} LINKTYPE_IPOIB 91 | * @property {number} LINKTYPE_MPEG_2_TS 92 | * @property {number} LINKTYPE_NG40 93 | * @property {number} LINKTYPE_NFC_LLCP 94 | * @property {number} LINKTYPE_INFINIBAND 95 | * @property {number} LINKTYPE_SCTP 96 | * @property {number} LINKTYPE_USBPCAP 97 | * @property {number} LINKTYPE_RTAC_SERIAL 98 | * @property {number} LINKTYPE_BLUETOOTH_LE_LL 99 | * @property {number} LINKTYPE_NETLINK 100 | * @property {number} LINKTYPE_BLUETOOTH_LINUX_MONITOR 101 | * @property {number} LINKTYPE_BLUETOOTH_BREDR_BB 102 | * @property {number} LINKTYPE_BLUETOOTH_LE_LL_WITH_PHDR 103 | * @property {number} LINKTYPE_PROFIBUS_DL 104 | * @property {number} LINKTYPE_PKTAP 105 | * @property {number} LINKTYPE_EPON 106 | * @property {number} LINKTYPE_IPMI_HPM_2 107 | * @property {number} LINKTYPE_ZWAVE_R1_R2 108 | * @property {number} LINKTYPE_ZWAVE_R3 109 | * @property {number} LINKTYPE_WATTSTOPPER_DLM 110 | * @property {number} LINKTYPE_ISO_14443 111 | * @property {number} LINKTYPE_LINUX_SLL2 112 | */ 113 | 114 | /* 115 | * @type {LinkLayerType} 116 | */ 117 | LinkLayerType; 118 | 119 | module.exports = { LinkLayerType }; 120 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const { LiveDevice } = require('./liveDevice'); 2 | const socket = require('./socket'); 3 | const { BpfFilter } = require('./bpfFilter'); 4 | const { LinkLayerType } = require('./enums'); 5 | const { createReadStream, createWriteStream, constants } = require('./pcapFile'); 6 | const { Packet } = require('./packet'); 7 | const { getArpTable } = require('./arp'); 8 | const { getRoutingTable } = require('./routing'); 9 | const converters = require('./converters'); 10 | 11 | module.exports = { 12 | Pcap: { 13 | LiveDevice, 14 | createReadStream, 15 | createWriteStream, 16 | constants, 17 | }, 18 | socket, 19 | LinkLayerType, 20 | BpfFilter, 21 | Packet, 22 | system: { 23 | ...converters, 24 | getArpTable, 25 | getRoutingTable, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /lib/layers/ARP.js: -------------------------------------------------------------------------------- 1 | const { compile, alignOffset } = require('struct-compile'); 2 | const { LinkLayerType } = require("#lib/enums"); 3 | const mixins = require("#lib/layers/mixins"); 4 | const { OsiModelLayers} = require("#lib/layers/osi"); 5 | const { macToString, macFromString } = require("#lib/layers/mac"); 6 | const { inetPton, inetNtop } = require('#lib/converters'); 7 | 8 | const OPCODE = { 9 | 1: 'who-has', 10 | 2: 'is-at' 11 | }; 12 | 13 | const OPCODE_LOOKUP = Object.entries(OPCODE).reduce((acc, [numCode, stringCode]) => { 14 | acc[stringCode] = numCode; 15 | return acc; 16 | }, {}); 17 | 18 | const PCPP_ETHERTYPE_IP = 0x0800; 19 | const PCPP_ETHERTYPE_IPX = 0x8137; 20 | const PCPP_ETHERTYPE_IPV6 = 0x86dd; 21 | 22 | const { ARPHeader } = compile(` 23 | //@NE 24 | struct ARPHeader { 25 | // Hardware type (HTYPE) 26 | uint16_t hardwareType; 27 | // Protocol type (PTYPE) 28 | uint16_t protocolType; 29 | // Hardware address length (HLEN) 30 | uint8_t hardwareLength; 31 | // Protocol length (PLEN) 32 | uint8_t protocolLength; 33 | // Specifies the operation that the sender is performing: 1 (::ARP_REQUEST) for request, 2 (::ARP_REPLY) for reply 34 | uint16_t opcode; 35 | // Sender hardware address (SHA) 36 | uint8_t hardwareSrc[6]; 37 | // @LE Sender protocol address (SPA) 38 | uint32_t protocolSrc; 39 | // Target hardware address (THA) 40 | uint8_t hardwareDst[6]; 41 | // @LE Target protocol address (TPA) 42 | uint32_t protocolDst; 43 | } __attribute__(packed); 44 | `); 45 | 46 | /** 47 | * ARP protocol layer 48 | * @class 49 | * @implements {Layer} 50 | * @property {number} hardwareType - Hardware type (HTYPE) 51 | * @property {number} protocolType - Protocol type (PTYPE) 52 | * @property {number} hardwareLength - Hardware address length (HLEN) 53 | * @property {number} protocolLength - Protocol length (PLEN) 54 | * @property {'who-has' | 'is-at'} opcode - Specifies the operation that the sender is performing: 1 (::ARP_REQUEST) for request, 2 (::ARP_REPLY) for reply 55 | * @property {string} hardwareSrc - Sender hardware address (SHA) 56 | * @property {string} protocolSrc - Sender protocol address (SPA) 57 | * @property {string} hardwareDst - Target hardware address (THA) 58 | * @property {string} protocolDst - Target protocol address (TPA) 59 | */ 60 | class ARP extends ARPHeader { 61 | name = 'ARP'; 62 | 63 | /** 64 | * @param {Buffer|Object} data - Input buffer or object with protocol fields. 65 | * @param {LayerOptions} opts - Options for the layer. 66 | */ 67 | constructor(data = {}, opts = {}) { 68 | super(data); 69 | mixins.ctor(this, data, opts); 70 | } 71 | 72 | static toAlloc = () => ARPHeader.config.length; 73 | 74 | static osi = OsiModelLayers.DataLink; 75 | osi = OsiModelLayers.DataLink; 76 | 77 | /** 78 | * The source mac address in human-readable format. 79 | * @type {string} 80 | */ 81 | get hardwareSrc() { 82 | return macToString(super.hardwareSrc); 83 | } 84 | 85 | set hardwareSrc(val) { 86 | super.hardwareSrc = macFromString(val); 87 | } 88 | 89 | /** 90 | * The destination mac address in human-readable format. 91 | * @type {string} 92 | */ 93 | get hardwareDst() { 94 | return macToString(super.hardwareDst); 95 | } 96 | 97 | set hardwareDst(val) { 98 | super.hardwareDst = macFromString(val); 99 | } 100 | 101 | /** 102 | * The source internet address in human-readable format. 103 | * @type {string} 104 | */ 105 | get protocolSrc() { 106 | return inetNtop(super.protocolSrc); 107 | } 108 | 109 | set protocolSrc(val) { 110 | super.protocolSrc = inetPton(val); 111 | } 112 | 113 | /** 114 | * The destination internet address in human-readable format. 115 | * @type {string} 116 | */ 117 | get protocolDst() { 118 | return inetNtop(super.protocolDst); 119 | } 120 | 121 | set protocolDst(val) { 122 | super.protocolDst = inetPton(val); 123 | } 124 | 125 | /** 126 | * @return {'who-has' | 'is-at'} 127 | */ 128 | get opcode() { 129 | return OPCODE[super.opcode]; 130 | } 131 | 132 | set opcode(val) { 133 | super.opcode = OPCODE_LOOKUP[val]; 134 | } 135 | 136 | toObject() { 137 | return { 138 | ...super.toObject(), 139 | hardwareSrc: this.hardwareSrc, 140 | hardwareDst: this.hardwareDst, 141 | protocolSrc: this.protocolSrc, 142 | protocolDst: this.protocolDst, 143 | opcode: this.opcode, 144 | }; 145 | } 146 | 147 | defaults(obj, layers) { 148 | if (!this.hardwareType) { 149 | this.hardwareType = 1; 150 | } 151 | if (!this.hardwareLength) { 152 | this.hardwareLength = 6; 153 | } 154 | if (!this.protocolType) { 155 | this.protocolType = PCPP_ETHERTYPE_IP; 156 | } 157 | if (!this.protocolLength) { 158 | this.protocolLength = 4; 159 | } 160 | if (this.opcode == 0x0001) { 161 | this.hardwareDst = '00:00:00:00:00:00'; 162 | } 163 | } 164 | 165 | nextProto(layers) { 166 | return layers.Payload(this.buffer.subarray(this.length), { ...this.opts, prev: this }); 167 | } 168 | }; 169 | 170 | module.exports = { ARP }; 171 | -------------------------------------------------------------------------------- /lib/layers/Ethernet.js: -------------------------------------------------------------------------------- 1 | const { LinkLayerType } = require('../enums'); 2 | const { compile } = require('struct-compile'); 3 | const { macToString, macFromString } = require('./mac'); 4 | const { OsiModelLayers } = require('./osi'); 5 | const { ETHERTYPE } = require('./enums'); 6 | const child = require('./child'); 7 | const mixins = require('./mixins'); 8 | 9 | const { EthernetHeader } = compile(` 10 | //@NE 11 | struct EthernetHeader { 12 | //destination MAC 13 | uint8_t dst[6]; 14 | 15 | //source MAC 16 | uint8_t src[6]; 17 | 18 | //underlying protocol 19 | uint16_t type; 20 | } __attribute__(packed); 21 | `); 22 | 23 | const childProto = { 24 | [ETHERTYPE.IP]: 'IPv4', 25 | [ETHERTYPE.IPV6]: 'IPv6', 26 | [ETHERTYPE.ARP]: 'ARP', 27 | [ETHERTYPE.VLAN]: 'Vlan', 28 | [ETHERTYPE.IEEE_802_1AD]: 'Vlan', 29 | [ETHERTYPE.PPPOES]: 'PPPoESession', 30 | [ETHERTYPE.PPPOED]: 'PPPoEDiscovery', 31 | [ETHERTYPE.MPLS]: 'Mpls', 32 | [ETHERTYPE.WAKE_ON_LAN]: 'WakeOnLanLayer', 33 | }; 34 | 35 | const lookupChild = child.lookupChild(childProto); 36 | const lookupKey = child.lookupKey(childProto); 37 | 38 | /** 39 | * Ethernet protocol layer 40 | * @class 41 | * @implements {Layer} 42 | * @property {string} src - Source MAC address. 43 | * @property {string} dst - Destination MAC address. 44 | * @property {number} type - Protocol type. 45 | */ 46 | class Ethernet extends EthernetHeader { 47 | static linktype = LinkLayerType.LINKTYPE_ETHERNET; 48 | name = 'Ethernet'; 49 | 50 | /** 51 | * @param {Buffer|Object} data - Input buffer or object with protocol fields. 52 | * @param {Object} opts - Options for the layer. 53 | */ 54 | constructor(data = {}, opts = {}) { 55 | super(data); 56 | mixins.ctor(this, data, opts); 57 | } 58 | 59 | static toAlloc = () => EthernetHeader.prototype.config.length; 60 | 61 | static osi = OsiModelLayers.DataLink; 62 | osi = OsiModelLayers.DataLink; 63 | 64 | /** 65 | * The source mac address in human-readable format. 66 | * @type {string} 67 | */ 68 | get src() { 69 | return macToString(super.src); 70 | } 71 | 72 | set src(val) { 73 | super.src = macFromString(val); 74 | } 75 | 76 | /** 77 | * The destination mac address in human-readable format. 78 | * @type {string} 79 | */ 80 | get dst() { 81 | return macToString(super.dst); 82 | } 83 | 84 | set dst(val) { 85 | super.dst = macFromString(val); 86 | } 87 | 88 | toObject() { 89 | return { 90 | ...super.toObject(), 91 | src: this.src, 92 | dst: this.dst, 93 | }; 94 | } 95 | 96 | defaults(obj, layers) { 97 | if (!obj.type) { 98 | if (this.next) { 99 | this.type = lookupKey(layers, this.next); 100 | } 101 | else { 102 | this.type = ETHERTYPE.IP; 103 | } 104 | } 105 | } 106 | 107 | nextProto(layers) { 108 | return lookupChild(layers, this.type, this); 109 | } 110 | }; 111 | 112 | module.exports = { Ethernet }; 113 | -------------------------------------------------------------------------------- /lib/layers/Payload.js: -------------------------------------------------------------------------------- 1 | const { OsiModelLayers } = require('./osi'); 2 | 3 | /** 4 | * Raw payload 5 | * @class 6 | * @implements {Layer} 7 | */ 8 | class Payload { 9 | name = 'Payload'; 10 | 11 | constructor(data = {}, { prev = null, allocated = null } = {}) { 12 | this.prev = prev; 13 | this.next = null; 14 | 15 | if (allocated !== null) { 16 | this.length = allocated; 17 | } 18 | 19 | if (Buffer.isBuffer(data)) { 20 | this.data = data; 21 | if (!(this.length >= 0)) { 22 | this.lenght = this.data.length; 23 | } 24 | } 25 | } 26 | 27 | static toAlloc = (data) => data?.data?.length ?? 0; 28 | static osi = OsiModelLayers.Application; 29 | osi = OsiModelLayers.Application; 30 | 31 | toObject() { 32 | return { 33 | data: this.data, 34 | }; 35 | } 36 | 37 | get buffer() { 38 | return this.data; 39 | } 40 | 41 | defaults(obj = {}) {} 42 | 43 | merge(obj) { 44 | if (obj.data) { 45 | if (obj.data.length > this.length) { 46 | this.buffer = this.extendAt(this.length, Math.abs(obj.data.length - this.length)); 47 | } 48 | else if (obj.data.length < this.length) { 49 | this.buffer = this.shrinkAt(this.length, Math.abs(obj.data.length - this.length)); 50 | } 51 | obj.data.copy(this.data); 52 | this.length = this.data.length; 53 | } 54 | } 55 | 56 | nextProto(layers) { 57 | return null; 58 | } 59 | }; 60 | 61 | module.exports = { Payload }; 62 | -------------------------------------------------------------------------------- /lib/layers/TCP.js: -------------------------------------------------------------------------------- 1 | const { compile } = require('struct-compile'); 2 | const { OsiModelLayers } = require('./osi'); 3 | const { IPProtocolTypes } = require('./enums'); 4 | const { checksums } = require('#lib/bindings'); 5 | const child = require('./child'); 6 | const mixins = require('./mixins'); 7 | const { omit } = require('#lib/pick'); 8 | const { addField } = require('#lib/struct'); 9 | 10 | const { TCPHeader } = compile(` 11 | //@NE 12 | struct TCPHeader { 13 | // Source TCP port 14 | uint16_t src; 15 | // Destination TCP port 16 | uint16_t dst; 17 | // Sequence number 18 | uint32_t seq; 19 | // Acknowledgment number 20 | uint32_t ack; 21 | 22 | // @LE 23 | uint16_t 24 | reservedFlag:4, 25 | dataOffset:4, 26 | 27 | finFlag:1, 28 | synFlag:1, 29 | rstFlag:1, 30 | pshFlag:1, 31 | ackFlag:1, 32 | urgFlag:1, 33 | eceFlag:1, 34 | cwrFlag:1; 35 | 36 | // The size of the receive window, which specifies the number of window size units (by default, bytes) 37 | uint16_t windowSize; 38 | // The 16-bit checksum field is used for error-checking of the header and data 39 | uint16_t checksum; 40 | // If the URG flag is set, then this 16-bit field is an offset from the sequence number indicating the last urgent data byte 41 | uint16_t urgentPointer; 42 | } __attribute__((packed)); 43 | `); 44 | 45 | const { length: baseLength } = TCPHeader.prototype.config; 46 | 47 | const flagNames = ['reserved', 'cwr', 'ece', 'urg', 'ack', 'psh', 'rst', 'syn', 'fin']; 48 | const flagKeys = flagNames.map(e => e + 'Flag'); 49 | 50 | /** 51 | * @typedef {Object} TCPFlags 52 | * @property {number} reserved - Reserved flag (0 or 1). 53 | * @property {number} cwr - Congestion Window Reduced (0 or 1). 54 | * @property {number} ece - ECN-Echo (0 или 1). 55 | * @property {number} urg - Urgent pointer field significant (0 или 1). 56 | * @property {number} ack - Acknowledgment field significant (0 или 1). 57 | * @property {number} psh - Push Function (0 или 1). 58 | * @property {number} rst - Reset the connection (0 или 1). 59 | * @property {number} syn - Synchronize sequence numbers (0 или 1). 60 | * @property {number} fin - No more data from sender (0 или 1). 61 | */ 62 | 63 | /** 64 | * TCP protocol layer 65 | * @class 66 | * @property {number} src - Source TCP port. 67 | * @property {number} dst - Destination TCP port. 68 | * @property {number} seq - Sequence number. 69 | * @property {number} ack - Acknowledgment number. 70 | * @property {number} windowSize - The size of the receive window, which specifies the number of window size units (by default, bytes). 71 | * @property {number} checksum - The 16-bit checksum field is used for error-checking of the header and data. 72 | * @property {number} urgentPointer - If the URG flag is set, then this 16-bit field is an offset from the sequence number indicating the last urgent data byte. 73 | * @property {TCPFlags} flags - TCP flags. 74 | * @implements {Layer} 75 | */ 76 | class TCP extends TCPHeader { 77 | name = 'TCP'; 78 | 79 | constructor(data = {}, opts = {}) { 80 | super(data); 81 | mixins.ctor(this, data, opts); 82 | 83 | this.length = opts.allocated ?? this.dataOffset * 4; 84 | 85 | /** 86 | * TLV options; 87 | * @type {Iterable.} 88 | */ 89 | this.options; 90 | } 91 | 92 | static toAlloc = (data) => baseLength + TCP.prototype.optionsLength(data.options); 93 | static osi = OsiModelLayers.Transport; 94 | osi = OsiModelLayers.Transport; 95 | 96 | _prepareFlags() { 97 | const res = {}; 98 | for (const flag of flagNames) { 99 | res[flag] = this[flag + 'Flag']; 100 | } 101 | return res; 102 | } 103 | 104 | /** 105 | * Get the flags object. Changes to this object will update the associated buffer. 106 | * @type {TCPFlags} 107 | */ 108 | get flags() { 109 | const self = this; 110 | return new Proxy(Object.seal(this._prepareFlags()), { 111 | get(target, prop, receiver) { 112 | return target[prop]; 113 | }, 114 | set(target, prop, value) { 115 | target[prop] = value; 116 | const internalKey = prop + 'Flag'; 117 | if (self[internalKey] !== undefined) { 118 | self[internalKey] = value; 119 | } 120 | }, 121 | }); 122 | } 123 | 124 | set flags(obj) { 125 | for (const flag of flagNames) { 126 | if (obj[flag] !== undefined) { 127 | this[flag + 'Flag'] = obj[flag]; 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Retrieves all fields of the TCP layer. 134 | * @example 135 | * tcp.toObject(); 136 | * // { 137 | * // src: 52622, 138 | * // dst: 24043, 139 | * // seq: 3994458414, 140 | * // ack: 3198720281, 141 | * // dataOffset: 8, 142 | * // windowSize: 2048, 143 | * // checksum: 3346, 144 | * // urgentPointer: 0, 145 | * // flags: { 146 | * // reserved: 0, 147 | * // cwr: 0, 148 | * // ece: 0, 149 | * // urg: 0, 150 | * // ack: 1, 151 | * // psh: 0, 152 | * // rst: 0, 153 | * // syn: 0, 154 | * // fin: 0 155 | * // }, 156 | * // options: [ 157 | * // { type: 1 }, 158 | * // { type: 1 }, 159 | * // { type: 8, recLength: 10, value: Buffer.from([0x52, 0xd3, 0xc6, 0x50, 0xdd, 0x04, 0xcd, 0xd6]) } 160 | * // ], 161 | * // } 162 | * @returns {Object} The TCP layer fields as an object. 163 | */ 164 | toObject() { 165 | return { 166 | ...omit(super.toObject(), ...flagKeys), 167 | flags: this.flags, 168 | options: [...this.options], 169 | }; 170 | } 171 | 172 | /** 173 | * Calculates and updates the checksum for the TCP layer. 174 | * This method mutates the object by setting the `checksum` property 175 | * based on the current state of the `buffer` and `prev` field. 176 | */ 177 | calculateChecksum() { 178 | this.checksum = checksums.pseudo({ 179 | data: this.buffer, 180 | addrType: this.prev?.name ?? 'IPv4', 181 | src: this.prev?.src, 182 | dst: this.prev?.dst, 183 | protocolType: IPProtocolTypes.TCP, 184 | }); 185 | } 186 | 187 | defaults(obj = {}, layers) { 188 | if (!obj.windowSize) { 189 | this.windowSize = 2048; 190 | } 191 | if (!obj.urgentPointer) { 192 | this.urgentPointer = 0; 193 | } 194 | if (!obj.dataOffset) { 195 | this.dataOffset = this.length / 4; 196 | } 197 | } 198 | 199 | checksums(obj) { 200 | if (!obj.checksum) { 201 | this.calculateChecksum(); 202 | } 203 | } 204 | 205 | nextProto(layers) { 206 | return new layers.Payload(this._buf.subarray(this.length)); 207 | } 208 | }; 209 | 210 | addField(TCP.prototype, 'flags'); 211 | 212 | mixins.withOptions(TCP.prototype, { baseLength, skipTypes: [0x1, 0x0], lengthIsTotal: true }); 213 | 214 | module.exports = { TCP }; 215 | -------------------------------------------------------------------------------- /lib/layers/TLV.js: -------------------------------------------------------------------------------- 1 | const { compile, alignOffset } = require('struct-compile'); 2 | 3 | /** 4 | * @typedef {Object} TLVOption 5 | * @property {number} type - The TLV option type. 6 | * @property {number} length - The length of the TLV option value. 7 | * @property {Buffer} value - The TLV option value. 8 | */ 9 | 10 | const { TLVRecord_8, TLVPadding_8 } = compile(` 11 | struct TLVRecord_8 { 12 | // Record type 13 | uint8_t type; 14 | // Record length in bytes 15 | uint8_t recLength; 16 | // Record value (variable size) 17 | uint8_t value[]; 18 | }; 19 | 20 | struct TLVPadding_8 { 21 | // Record type 22 | uint8_t type; 23 | }; 24 | `); 25 | 26 | class TLV_8 extends TLVRecord_8 { 27 | constructor(data = {}, opts = {}) { 28 | super(data); 29 | if (!Buffer.isBuffer(data) && typeof data == 'object' && data?.value) { 30 | this.value = data.value; 31 | this._buf = Buffer.concat([this._buf, data.value]); 32 | } 33 | else { 34 | this.value = this._buf.subarray(this.length, this.recLength + (opts.lengthIsTotal ? 0 : this.length)); 35 | } 36 | 37 | if (opts.lengthIsTotal) { 38 | this.length = this.recLength; 39 | } 40 | else { 41 | this.length += this.recLength; 42 | } 43 | } 44 | }; 45 | 46 | class TLVIterator { 47 | constructor(Cls, buf, { skipTypes = [], lengthIsTotal = false } = {}) { 48 | this.Cls = Cls; 49 | this.buf = buf; 50 | this.pos = 0; 51 | this.skipTypes = skipTypes; 52 | this.lengthIsTotal = lengthIsTotal; 53 | } 54 | 55 | next() { 56 | try { 57 | if (this.pos < this.buf.length) { 58 | const res = new this.Cls(this.buf.subarray(this.pos), { lengthIsTotal: this.lengthIsTotal }); 59 | if (this.skipTypes.includes(res.type)) { 60 | this.pos += 1; 61 | return { done: false, value: { type: res.type } }; 62 | } 63 | else { 64 | this.pos += res.length; 65 | return { done: false, value: res.toObject() }; 66 | } 67 | } 68 | } catch(err) {} 69 | return { done: true }; 70 | } 71 | 72 | [Symbol.iterator]() { 73 | return this; 74 | } 75 | } 76 | 77 | function TLVSerialize(Cls, PaddingCls, opts, { skipTypes = [], lengthIsTotal = false, align = null } = {}) { 78 | let res = Buffer 79 | .concat(opts 80 | .map(e => skipTypes.includes(e.type) ? 81 | new PaddingCls(e).buffer : 82 | new Cls(e, { lengthIsTotal }).buffer 83 | ) 84 | ); 85 | 86 | if (align !== null && res.length % align > 0) { 87 | res = Buffer.concat([res, Buffer.alloc(alignOffset(res.length, align) - res.length)]); 88 | } 89 | 90 | return res; 91 | } 92 | 93 | function TLVLength(Cls, PaddingCls, opts, { skipTypes = [], lengthIsTotal = false, align = null } = {}) { 94 | let res = 0; 95 | 96 | for (const opt of opts) { 97 | if (skipTypes.includes(e.type)) { 98 | res += PaddingCls.prototype.config.length; 99 | } 100 | else { 101 | if (lengthIsTotal) { 102 | res += opt.recLength; 103 | } 104 | else { 105 | res += opt.recLength + Cls.prototype.config.length; 106 | } 107 | } 108 | } 109 | 110 | if (align !== null && res.length % align > 0) { 111 | res = alignOffset(res, align); 112 | } 113 | 114 | return res; 115 | } 116 | 117 | module.exports = { TLV_8, TLVPadding_8, TLVIterator, TLVSerialize, TLVLength }; 118 | -------------------------------------------------------------------------------- /lib/layers/UDP.js: -------------------------------------------------------------------------------- 1 | const { compile } = require('struct-compile'); 2 | const { OsiModelLayers } = require('./osi'); 3 | const { IPProtocolTypes } = require('./enums'); 4 | const { checksums } = require('#lib/bindings'); 5 | const child = require('./child'); 6 | const mixins = require('./mixins'); 7 | 8 | const { UDPHeader } = compile(` 9 | //@NE 10 | struct UDPHeader { 11 | /** Source port */ 12 | uint16_t src; 13 | /** Destination port */ 14 | uint16_t dst; 15 | /** Length of header and payload in bytes */ 16 | uint16_t totalLength; 17 | /** Error-checking of the header and data */ 18 | uint16_t checksum; 19 | }; 20 | `); 21 | 22 | const { length: baseLength } = UDPHeader.prototype.config; 23 | 24 | /** 25 | * UDP protocol layer 26 | * @class 27 | * @property {number} src - Source UDP port. 28 | * @property {number} dst - Destination UDP port. 29 | * @property {number} totalLength - This field specifies the length in bytes of the UDP datagram (the header fields and Data field) in octets. 30 | * @property {number} checksum - The 16-bit checksum field is used for error-checking of the header and data. 31 | * @implements {Layer} 32 | */ 33 | class UDP extends UDPHeader { 34 | name = 'UDP'; 35 | 36 | constructor(data = {}, opts = {}) { 37 | super(data); 38 | mixins.ctor(this, data, opts); 39 | 40 | this.length = opts.allocated ?? this.totalLength * 4; 41 | 42 | /** 43 | * TLV options; 44 | * @type {Iterable.} 45 | */ 46 | this.options; 47 | } 48 | 49 | static toAlloc = (data) => baseLength; 50 | 51 | static osi = OsiModelLayers.Transport; 52 | osi = OsiModelLayers.Transport; 53 | 54 | /** 55 | * Retrieves all fields of the UDP layer. 56 | * @example 57 | * udp.toObject(); 58 | * // { 59 | * // src: 52622, 60 | * // dst: 24043, 61 | * // seq: 3994458414, 62 | * // totalLength: 2048, 63 | * // checksum: 3346, 64 | * // } 65 | * @returns {Object} The UDP layer fields as an object. 66 | */ 67 | toObject() { 68 | return super.toObject(); 69 | } 70 | 71 | /** 72 | * Calculates and updates the checksum for the UDP layer. 73 | * This method mutates the object by setting the `checksum` property 74 | * based on the current state of the `buffer` and `prev` field. 75 | */ 76 | calculateChecksum() { 77 | this.checksum = checksums.pseudo({ 78 | data: this.buffer, 79 | addrType: this.prev?.name ?? 'IPv4', 80 | src: this.prev?.src, 81 | dst: this.prev?.dst, 82 | protocolType: IPProtocolTypes.UDP, 83 | }); 84 | } 85 | 86 | defaults(obj = {}, layers) { 87 | if (!obj.totalLength) { 88 | this.totalLength = this.length / 4; 89 | } 90 | } 91 | 92 | checksums(obj) { 93 | if (!obj.checksum) { 94 | this.calculateChecksum(); 95 | } 96 | } 97 | 98 | nextProto(layers) { 99 | return new layers.Payload(this._buf.subarray(this.length)); 100 | } 101 | }; 102 | 103 | module.exports = { UDP }; 104 | -------------------------------------------------------------------------------- /lib/layers/child.js: -------------------------------------------------------------------------------- 1 | const lookupChild = (dict) => (layers, val, self) => new (layers[dict[val] ?? 'Payload'] ?? layers.Payload)(self.buffer.subarray(self.length), { ...self.opts, prev: self }); 2 | 3 | const lookupKey = (dict) => { 4 | const reverted = Object.entries(dict).reduce((res, [k, v]) => res.set(v, Number.isNaN(Number(k)) ? k : Number(k)), new Map); 5 | return (layers, obj) => { 6 | return reverted.get(obj?.name) ?? null; 7 | }; 8 | } 9 | 10 | module.exports = { lookupChild, lookupKey }; 11 | -------------------------------------------------------------------------------- /lib/layers/enums.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum for Ethernet child protocols. 3 | * @readonly 4 | * @enum {number} 5 | */ 6 | module.exports.ETHERTYPE = { 7 | /** Internet protocol */ 8 | IP: 0x0800, 9 | /** Address resolution */ 10 | ARP: 0x0806, 11 | /** Transparent Ethernet Bridging */ 12 | ETHBRIDGE: 0x6558, 13 | /** Reverse ARP */ 14 | REVARP: 0x8035, 15 | /** AppleTalk protocol */ 16 | AT: 0x809B, 17 | /** AppleTalk ARP */ 18 | AARP: 0x80F3, 19 | /** IEEE 802.1Q VLAN tagging */ 20 | VLAN: 0x8100, 21 | /** IPX */ 22 | IPX: 0x8137, 23 | /** IP protocol version 6 */ 24 | IPV6: 0x86dd, 25 | /** used to test interfaces */ 26 | LOOPBACK: 0x9000, 27 | /** PPPoE discovery */ 28 | PPPOED: 0x8863, 29 | /** PPPoE session */ 30 | PPPOES: 0x8864, 31 | /** MPLS */ 32 | MPLS: 0x8847, 33 | /** Point-to-point protocol (PPP) */ 34 | PPP: 0x880B, 35 | /** RDMA over Converged Ethernet (RoCEv1) */ 36 | ROCEV1: 0x8915, 37 | /** IEEE 802.1ad Provider Bridge, Q-in-Q */ 38 | IEEE_802_1AD: 0x88A8, 39 | /** Wake on LAN */ 40 | WAKE_ON_LAN: 0x0842, 41 | }; 42 | 43 | /** 44 | * Enum for IPv4 child protocols. 45 | * @readonly 46 | * @enum {number} 47 | */ 48 | module.exports.IPProtocolTypes = { 49 | /** Dummy protocol for TCP */ 50 | IP: 0, 51 | /** IPv6 Hop-by-Hop options */ 52 | HOPOPTS: 0, 53 | /** Internet Control Message Protocol */ 54 | ICMP: 1, 55 | /** Internet Gateway Management Protocol */ 56 | IGMP: 2, 57 | /** IPIP tunnels (older KA9Q tunnels use 94) */ 58 | IPIP: 4, 59 | /** Transmission Control Protocol */ 60 | TCP: 6, 61 | /** Exterior Gateway Protocol */ 62 | EGP: 8, 63 | /** PUP protocol */ 64 | PUP: 12, 65 | /** User Datagram Protocol */ 66 | UDP: 17, 67 | /** XNS IDP protocol */ 68 | IDP: 22, 69 | /** IPv6 header */ 70 | IPV6: 41, 71 | /** IPv6 Routing header */ 72 | ROUTING: 43, 73 | /** IPv6 fragmentation header */ 74 | FRAGMENT: 44, 75 | /** GRE protocol */ 76 | GRE: 47, 77 | /** encapsulating security payload */ 78 | ESP: 50, 79 | /** authentication header */ 80 | AH: 51, 81 | /** ICMPv6 */ 82 | ICMPV6: 58, 83 | /** IPv6 no next header */ 84 | NONE: 59, 85 | /** IPv6 Destination options */ 86 | DSTOPTS: 60, 87 | /** VRRP protocol */ 88 | VRRP: 112, 89 | /** Raw IP packets */ 90 | RAW: 255, 91 | /** Maximum value */ 92 | MAX: 256, 93 | }; 94 | 95 | 96 | /** 97 | * Enum for IPv4 options. 98 | * @readonly 99 | * @enum {number} 100 | */ 101 | module.exports.IPv4OptionTypes = 102 | { 103 | /** End of Options List */ 104 | EndOfOptionsList: 0, 105 | /** No Operation */ 106 | NOP: 1, 107 | /** Record Route */ 108 | RecordRoute: 7, 109 | /** MTU Probe */ 110 | MTUProbe: 11, 111 | /** MTU Reply */ 112 | MTUReply: 12, 113 | /** Quick-Start */ 114 | QuickStart: 25, 115 | /** Timestamp */ 116 | Timestamp: 68, 117 | /** Traceroute */ 118 | Traceroute: 82, 119 | /** Security */ 120 | Security: 130, 121 | /** Loose Source Route */ 122 | LooseSourceRoute: 131, 123 | /** Extended Security */ 124 | ExtendedSecurity: 133, 125 | /** Commercial Security */ 126 | CommercialSecurity: 134, 127 | /** Stream ID */ 128 | StreamID: 136, 129 | /** Strict Source Route */ 130 | StrictSourceRoute: 137, 131 | /** Extended Internet Protocol */ 132 | ExtendedInternetProtocol: 145, 133 | /** Address Extension */ 134 | AddressExtension: 147, 135 | /** Router Alert */ 136 | RouterAlert: 148, 137 | /** Selective Directed Broadcast */ 138 | SelectiveDirectedBroadcast: 149, 139 | /** Dynamic Packet State */ 140 | DynamicPacketState: 151, 141 | /** Upstream Multicast Pkt. */ 142 | UpstreamMulticastPkt: 152, 143 | /** Unknown IPv4 option */ 144 | Unknown: 153, 145 | }; 146 | -------------------------------------------------------------------------------- /lib/layers/exampleLayer.js: -------------------------------------------------------------------------------- 1 | const { OsiModelLayers } = require('./osi'); 2 | const mixins = require('#lib/layers/mixins'); 3 | 4 | /** 5 | * Interface for classes that protocol layers. 6 | * 7 | * @interface 8 | */ 9 | class Layer { 10 | /** 11 | * @param {Buffer|Object} data - Input buffer or object with protocol fields. 12 | * @param {Object} opts - Options for the layer. 13 | */ 14 | constructor(data = {}, opts = {}) { 15 | /** 16 | * Underlying buffer synced with the properties. 17 | * @type {Buffer} 18 | */ 19 | this.buffer; 20 | 21 | //best to use existing mixins 22 | mixins.ctor(this, data, opts); 23 | /** 24 | * Number of bytes occupied by the layer. 25 | * @type {number} 26 | */ 27 | this.length = opts.allocated ?? 0; 28 | /** 29 | * @private 30 | * Should be the same as the class name. 31 | * @type {string} 32 | */ 33 | this.name = 'Layer'; 34 | /** 35 | * Respective protocol OSI layer. 36 | * @type {OsiModelLayers} 37 | */ 38 | this.osi = OsiModelLayers.Unknown; 39 | 40 | /** 41 | * @private 42 | * Previous layer. 43 | * @type {Layer} 44 | */ 45 | this.prev; 46 | 47 | /** 48 | * @private 49 | * Next layer. 50 | * @type {Layer} 51 | */ 52 | this.next; 53 | } 54 | 55 | /** 56 | * @private 57 | */ 58 | static toAlloc = () => 0; 59 | 60 | /** 61 | * @private 62 | * Sets default properties for the layer based on user input and existing layers. 63 | * @param {Object} obj - User-defined properties. 64 | * @param {Object} layers - All available protocol layers. 65 | */ 66 | defaults(obj, layers) { 67 | // Implementation 68 | } 69 | 70 | /** 71 | * @private 72 | * Receives an object with input properties 73 | * @param {Object} obj - User-defined properties. 74 | */ 75 | merge(obj) { 76 | // Implementation 77 | } 78 | 79 | /** 80 | * @private 81 | * Determines the next protocol based on the current layer and existing layers. 82 | * @param {Object} layers - All available protocol layers. 83 | * @returns {Layer} The next layer's protocol. 84 | */ 85 | nextProto(layers) { 86 | // Implementation 87 | return new Layer(this.buffer.subarray(this.length)); // Placeholder return 88 | } 89 | 90 | /** 91 | * Returns an object with all protcol fields without owning the underlying buffer. 92 | * Useful to read all the data and use it without having to deal with binary representation. 93 | * @returns {Object} - All protocol fields 94 | */ 95 | toObject() { 96 | return {}; 97 | } 98 | } 99 | 100 | module.exports = { Layer }; 101 | -------------------------------------------------------------------------------- /lib/layers/index.js: -------------------------------------------------------------------------------- 1 | const { Ethernet } = require('./Ethernet'); 2 | const { IPv4 } = require('./IPv4'); 3 | const { TCP } = require('./TCP'); 4 | const { ARP } = require('./ARP'); 5 | const { Payload } = require('./Payload'); 6 | 7 | const layers = { 8 | IPv4, 9 | Ethernet, 10 | Payload, 11 | TCP, 12 | ARP, 13 | }; 14 | 15 | const linktype = {}; 16 | 17 | for (const cls of Object.values(layers)) { 18 | if (cls.linktype) { 19 | linktype[cls.linktype] = cls; 20 | } 21 | } 22 | 23 | module.exports = { 24 | layers, 25 | linktype, 26 | }; 27 | -------------------------------------------------------------------------------- /lib/layers/mac.js: -------------------------------------------------------------------------------- 1 | const macToString = buf => buf.toJSON().data.map(e => e.toString(16).padStart(2, '0')).join(':'); 2 | const macFromString = str => Buffer.from(str.replaceAll(':', ''), 'hex'); 3 | 4 | module.exports = { macToString, macFromString }; 5 | -------------------------------------------------------------------------------- /lib/layers/mixins.js: -------------------------------------------------------------------------------- 1 | const buffer = require('../buffer'); 2 | const { TLV_8, TLVPadding_8, TLVIterator, TLVSerialize, TLVLength } = require('./TLV'); 3 | 4 | const ctor = (self, data, opts) => { 5 | const { prev = null, extendAt, shrinkAt } = opts; 6 | self.opts = opts; 7 | self.prev = prev; 8 | self.next = null; 9 | self.extendAt = extendAt ?? ((...args) => buffer.extendAt(self.buffer, ...args)); 10 | self.shrinkAt = shrinkAt ?? ((...args) => buffer.shrinkAt(self.buffer, ...args)); 11 | if (prev) { 12 | prev.next = self; 13 | } 14 | 15 | if (self._hasOptions) { 16 | if (!Buffer.isBuffer(data)) { 17 | if (data.options) { 18 | self.options = data.options; 19 | } 20 | } 21 | } 22 | } 23 | 24 | const complementOptions = options => { 25 | const res = []; 26 | 27 | for (const opt of options) { 28 | if (opt.recLength) { 29 | res.push({ ...opt }); 30 | } 31 | else { 32 | res.push({ ...opt, recLength: opt?.value?.length ?? 0 }); 33 | } 34 | } 35 | 36 | return res; 37 | } 38 | 39 | const withOptions = (proto, { baseLength, skipTypes = [], lengthIsTotal = false }) => { 40 | Object.defineProperty(proto, 'options', { 41 | get() { 42 | const { length } = this; 43 | return new TLVIterator(TLV_8, this._buf.subarray(baseLength, length), { skipTypes, lengthIsTotal }); 44 | }, 45 | set(opts) { 46 | const { length } = this; 47 | const serialized = TLVSerialize(TLV_8, TLVPadding_8, complementOptions(opts), { skipTypes, lengthIsTotal, align: 4 }); 48 | const diff = serialized.length - (length - baseLength); 49 | this.length += diff; 50 | if (diff > 0) { 51 | this.buffer = this.extendAt(length, diff); 52 | } 53 | else if (diff < 0) { 54 | this.buffer = this.shrinkAt(length, diff); 55 | } 56 | serialized.copy(this._buf, baseLength); 57 | this.headerLength = (baseLength + serialized.length) / 4; 58 | }, 59 | }); 60 | 61 | proto.optionsLength = function(opts) { 62 | if (!opts) return 0; 63 | return TLVLength(TLV_8, TLVPadding_8, complementOptions(opts), { skipTypes, lengthIsTotal, align: 4 }); 64 | }; 65 | 66 | proto._hasOptions = true; 67 | }; 68 | 69 | module.exports = { ctor, withOptions }; 70 | -------------------------------------------------------------------------------- /lib/layers/osi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum for OSI model layers. 3 | * @readonly 4 | * @enum {number} 5 | */ 6 | const OsiModelLayers = { 7 | /** Physical layer (layer 1) */ 8 | Physical: 1, 9 | /** Data link layer (layer 2) */ 10 | DataLink: 2, 11 | /** Network layer (layer 3) */ 12 | Network: 3, 13 | /** Transport layer (layer 4) */ 14 | Transport: 4, 15 | /** Session layer (layer 5) */ 16 | Sesion: 5, 17 | /** Presentation layer (layer 6) */ 18 | Presentation: 6, 19 | /** Application layer (layer 7) */ 20 | Application: 7, 21 | /** Unknown / null layer */ 22 | Unknown: 8, 23 | }; 24 | 25 | module.exports = { OsiModelLayers }; 26 | -------------------------------------------------------------------------------- /lib/layersList.js: -------------------------------------------------------------------------------- 1 | class LayersList { 2 | constructor(opts = {}) { 3 | this._layers = opts._layers ?? {}; 4 | this._layersHead = opts._layersHead ?? null; 5 | this._layersTail = opts._layersTail ?? null; 6 | this._layersCount = opts._layersCount ?? 0; 7 | } 8 | 9 | _addLayer(newLayer) { 10 | if (!this._layersHead) { 11 | this._layersTail = this._layersHead = newLayer; 12 | } 13 | else if (this._layersTail) { 14 | this._layersTail.next = newLayer; 15 | } 16 | 17 | this._layersTail = newLayer; 18 | 19 | const { name } = newLayer; 20 | 21 | if (!this._layers[name]) { 22 | this._layers[name] = newLayer; 23 | } 24 | else if (Array.isArray(this._layers[name])) { 25 | this._layers[name].push(newLayer); 26 | } 27 | else { 28 | this._layers[name] = [this._layers[name], newLayer]; 29 | } 30 | 31 | this.layersCount++; 32 | 33 | return newLayer; 34 | } 35 | 36 | _createLayer(Layer, buffer, opts = {}) { 37 | const prev = this._layersTail; 38 | 39 | const newLayer = new Layer(buffer, { 40 | prev, 41 | ...(this._defaultOpts ?? {}), 42 | ...opts, 43 | }); 44 | 45 | return this._addLayer(newLayer); 46 | } 47 | 48 | _genLayers(fn) { 49 | let i = 0; 50 | 51 | if (this._layersTail === null) { 52 | this._layersTail = this._layersHead = fn(null, i++); 53 | 54 | if (this._layersTail === null) { 55 | return; 56 | } 57 | else { 58 | this._layersCount++; 59 | } 60 | } 61 | 62 | do { 63 | const newLayer = fn(this._layersTail, i++); 64 | this._layersTail.next = newLayer; 65 | this._layersTail = newLayer; 66 | 67 | if (!this._layersTail !== null) { 68 | this._layersCount++; 69 | } 70 | } while(this._layersTail !== null); 71 | } 72 | 73 | _eachLayer(fn) { 74 | let cur = this._layersHead; 75 | let i = 0; 76 | while (cur !== null) { 77 | fn(cur, i++); 78 | cur = cur.next; 79 | } 80 | } 81 | 82 | _eachLayerReverse(fn) { 83 | let cur = this._layersTail; 84 | let i = 0; 85 | while (cur !== null) { 86 | fn(cur, i++); 87 | cur = cur.prev; 88 | } 89 | } 90 | } 91 | 92 | module.exports = { LayersList }; 93 | -------------------------------------------------------------------------------- /lib/liveDevice.js: -------------------------------------------------------------------------------- 1 | const { Duplex } = require('stream'); 2 | const { PcapDevice: LiveDeviceCxx } = require('#lib/bindings'); 3 | const { pick } = require('#lib/pick'); 4 | const { Packet } = require('#lib/packet'); 5 | 6 | const optionsKeys = [ 7 | 'capture', 8 | 'parse', 9 | 'mode', 10 | 'direction', 11 | 'packetBufferTimeoutMs', 12 | 'packetBufferSize', 13 | 'snapshotLength', 14 | 'nflogGroup', 15 | ]; 16 | 17 | const manualOptionsKeys = ['filter', 'iface']; 18 | 19 | const getOptions = obj => pick(obj, ...optionsKeys, ...manualOptionsKeys); 20 | 21 | /** 22 | * @typedef {Object} LiveDeviceOptions 23 | * @property {string} [mode] - The mode of the device, either "promiscuous" or "normal". 24 | * @property {string} [direction] - The direction of packet capture, either "inout", "in", or "out". 25 | * @property {number} [packetBufferTimeoutMs] - The packet buffer timeout in milliseconds. 26 | * @property {number} [packetBufferSize] - The size of the packet buffer. 27 | * @property {number} [snapshotLength] - The snapshot length for packet capture. 28 | * @property {number} [nflogGroup] - The NFLOG group. 29 | * @property {string} [iface] - The network interface name. 30 | * @property {string} [filter] - The filter string for packet capture. 31 | */ 32 | 33 | 34 | /** 35 | * @typedef {Object} DeviceStats 36 | * @property {number} packetsDrop - The number of packets dropped. 37 | * @property {number} packetsDropByInterface - The number of packets dropped by the interface. 38 | * @property {number} packetsRecv - The number of packets received. 39 | */ 40 | 41 | /** 42 | * @typedef {Object} InterfaceInfo 43 | * @property {string} name - The name of the network interface. 44 | * @property {string} description - The description of the network interface. 45 | * @property {string} mac - The MAC address of the network interface. 46 | * @property {string} gateway - The gateway address of the network interface. 47 | * @property {number} mtu - The Maximum Transmission Unit size. 48 | * @property {string} linktype - The link type of the network interface. 49 | * @property {string[]} dnsServers - The DNS servers associated with the network interface. 50 | * @property {string[]} addresses - The IP addresses associated with the network interface. 51 | */ 52 | 53 | /** 54 | * Duplex stream for capturing and injecting packets on a specific device. 55 | * @extends Duplex 56 | */ 57 | class LiveDevice extends Duplex { 58 | /** 59 | * Creates an instance of LiveDevice. 60 | * @param {LiveDeviceOptions} options - The options for the LiveDevice instance. 61 | */ 62 | constructor(options = {}) { 63 | super({ objectMode: true }); 64 | 65 | this.options = getOptions(options); 66 | this.isOpen = false; 67 | this.capturing = false; 68 | this.optionsChanged = false; 69 | 70 | optionsKeys.forEach(opt => { 71 | Object.defineProperty(this, opt, { 72 | get() { 73 | return this.options[opt]; 74 | }, 75 | set(val) { 76 | this.optionsChanged = true; 77 | return this.options[opt] = val; 78 | }, 79 | }); 80 | }); 81 | 82 | this.options.push = (buffer) => { 83 | if (!this._ifaceCached) { 84 | this._ifaceCached = this.iface; 85 | } 86 | 87 | const res = this.push(new Packet({ buffer, iface: this._iface })); 88 | 89 | if (!res) { 90 | this.pcapInternal.stopCapture(); 91 | this.capturing = false; 92 | } 93 | }; 94 | 95 | this.pcapInternal = new LiveDeviceCxx(this.options); 96 | } 97 | 98 | _construct(callback) { 99 | if (this.optionsChanged) { 100 | this.pcapInternal.setConfig(this.options); 101 | } 102 | 103 | if (!this.pcapInternal) { 104 | return callback(); 105 | } 106 | 107 | try { 108 | this.pcapInternal.open(); 109 | } catch (err) { 110 | return callback(err); 111 | } 112 | 113 | this.isOpen = true; 114 | if (this.options.filter) { 115 | this.pcapInternal.setFilter(this.options.filter); 116 | } 117 | callback(); 118 | } 119 | 120 | _read(size) { 121 | if (this.options.capture === false) { 122 | this.push(null); 123 | } else if (!this.capturing) { 124 | this.pcapInternal.startCapture(); 125 | this.capturing = true; 126 | } 127 | } 128 | 129 | _write(chunk, encoding, callback) { 130 | if (chunk instanceof Packet) { 131 | return this.pcapInternal._write(chunk.buffer, callback); 132 | } 133 | if (Buffer.isBuffer(chunk) || ArrayBuffer.isView(chunk)) { 134 | if (chunk.length > 0) { 135 | return this.pcapInternal._write(chunk, callback); 136 | } else { 137 | callback(); 138 | } 139 | } else { 140 | callback(new Error('Invalid argument - expected Packet | Buffer | TypedArray')); 141 | } 142 | } 143 | 144 | _writev(chunks, callback) { 145 | return this.pcapInternal._write(chunks.map(e => e.chunk instanceof Packet ? e.chunk.buffer : e.chunk), callback); 146 | } 147 | 148 | _destroy(err, callback) { 149 | if (this.pcapInternal) { 150 | this.pcapInternal._destroy(); 151 | } 152 | callback(err); 153 | } 154 | 155 | _final(callback) { 156 | this.pcapInternal._destroy(); 157 | delete this.pcapInternal; 158 | } 159 | 160 | /** 161 | * The statistics of the device. 162 | * @throws {Error} If the device is not open. 163 | * @type {DeviceStats} 164 | */ 165 | get stats() { 166 | if (!this.isOpen) { 167 | throw new Error('Device is not open'); 168 | } 169 | return this.pcapInternal.stats; 170 | } 171 | 172 | /** 173 | * The filter for the device. 174 | * @type {string} 175 | */ 176 | set filter(filter) { 177 | this.options.filter = filter; 178 | if (this.isOpen) { 179 | this.pcapInternal.setFilter(filter); 180 | } 181 | 182 | return filter; 183 | } 184 | 185 | get filter() { 186 | return this.options.filter; 187 | } 188 | 189 | set iface(iface) { 190 | this.options.iface = iface; 191 | } 192 | 193 | /** 194 | * The interface information for the device. 195 | * @type {InterfaceInfo} 196 | */ 197 | get iface() { 198 | return this.pcapInternal.interfaceInfo; 199 | } 200 | } 201 | 202 | module.exports = { LiveDevice }; 203 | -------------------------------------------------------------------------------- /lib/packet.js: -------------------------------------------------------------------------------- 1 | const { LinkLayerType } = require('#lib/enums'); 2 | const defaults = require('#lib/defaults'); 3 | const { layers, linktype } = require('#lib/layers/index'); 4 | const { shrinkAt, extendAt } = require('#lib/buffer'); 5 | const { TimeStamp } = require('#lib/timestamp'); 6 | const { LayersList } = require('#lib/layersList'); 7 | 8 | class Packet extends LayersList { 9 | constructor(data, opts = {}) { 10 | super({}); 11 | this._toBuild = []; 12 | 13 | this.shrinkAt = this.shrinkAt.bind(this._buffer); 14 | this.extendAt = this.shrinkAt.bind(this._buffer); 15 | 16 | this._defaultOpts = { 17 | shrinkAt: this.shrinkAt, 18 | extendAt: this.shrinkAt, 19 | }; 20 | 21 | if (data instanceof Packet) { 22 | this._buffer = Buffer.from(data.buffer); 23 | this._origLength = data._origLength; 24 | this._layersCount = data._layersCount; 25 | 26 | this.iface = { ...data.iface }; 27 | this.linktype = data.linktype; 28 | 29 | if (opts.copy) { 30 | this.timestamp = TimeStamp.now(); 31 | } 32 | else { 33 | this.timestamp = data.timestamp.clone(); 34 | } 35 | 36 | this._toBuild = [...data._toBuild]; 37 | 38 | data._eachLayer(l => { 39 | this._createLayer(layers[l.name], Buffer.from(l.buffer)); 40 | }); 41 | } 42 | else if (typeof data == 'object') { 43 | const { buffer = null, iface = { ...defaults }, timestamp = TimeStamp.now(), origLength = null } = data; 44 | 45 | this._buffer = buffer; 46 | this._origLength = origLength; 47 | this._layersCount = 0; 48 | 49 | this.iface = iface; 50 | this.linktype = this.iface?.linktype ?? LinkLayerType.LINKTYPE_ETHERNET; 51 | 52 | this.timestamp = timestamp; 53 | 54 | this._needsParse = this._buffer?.length > 0; 55 | } 56 | 57 | if (data?.comment) { 58 | this.comment = data.comment; 59 | } 60 | } 61 | 62 | equals(pkt) { 63 | if (!pkt instanceof Packet) { 64 | return false; 65 | } 66 | 67 | if (this.iface?.linktype !== pkt.iface?.linktype) { 68 | return false; 69 | } 70 | 71 | if (this.iface?.name !== pkt.iface?.name) { 72 | return false; 73 | } 74 | 75 | if (this.iface?.mtu !== pkt.iface?.mtu) { 76 | return false; 77 | } 78 | 79 | if (this.comment !== pkt.comment) { 80 | return false; 81 | } 82 | 83 | if (this.timestamp.compare(pkt.timestamp) !== 0) { 84 | return false; 85 | } 86 | 87 | if (Buffer.compare(this.buffer, pkt.buffer) !== 0) { 88 | return false; 89 | } 90 | 91 | return true; 92 | } 93 | 94 | get _needsBuild() { 95 | return this._toBuild.length > 0; 96 | } 97 | 98 | [Symbol.for('nodejs.util.inspect.custom')]() { 99 | return `'; 103 | } 104 | 105 | _build() { 106 | if (!this._needsBuild) { 107 | return; 108 | } 109 | 110 | let toAlloc = 0; 111 | const allocArray = []; 112 | 113 | for (const { Layer, data } of this._toBuild) { 114 | const allocUnit = Layer.toAlloc(data); 115 | toAlloc += allocUnit; 116 | allocArray.push(allocUnit); 117 | } 118 | 119 | let newBuffer = Buffer.alloc(toAlloc); 120 | let curBuffer; 121 | 122 | if (Buffer.isBuffer(this._buffer)) { 123 | this._buffer = Buffer.concat([this._buffer, newBuffer]); 124 | curBuffer = this._buffer; 125 | 126 | this._eachLayer(l => { 127 | l.buffer = curBuffer; 128 | curBuffer = curBuffer.subarray(l.length); 129 | }); 130 | } 131 | else { 132 | curBuffer = newBuffer; 133 | this._buffer = curBuffer; 134 | } 135 | 136 | const initCount = this._layersCount; 137 | 138 | this._genLayers((prev, i) => { 139 | const obj = this._toBuild[i]; 140 | 141 | if (!obj) { 142 | return null; 143 | } 144 | 145 | const { Layer, data } = obj; 146 | 147 | const res = this._createLayer(Layer, curBuffer, { allocated: allocArray[i] }); 148 | 149 | res.merge(data); 150 | 151 | curBuffer = curBuffer.subarray(res.length); 152 | 153 | return res; 154 | }); 155 | 156 | this._eachLayer((l, i) => { 157 | if (i < initCount) return; 158 | l.defaults(this._toBuild[i - initCount]); 159 | }); 160 | 161 | this._eachLayer((l, i) => { 162 | if (i < initCount) return; 163 | if (typeof l.checksums == 'function') { 164 | l.checksums(this._toBuild[i - initCount]); 165 | } 166 | }); 167 | 168 | this._toBuild = []; 169 | this._origLength = null; 170 | } 171 | 172 | get origLength() { 173 | return this._origLength ?? this.length; 174 | } 175 | 176 | get length() { 177 | return this.buffer.length; 178 | } 179 | 180 | _parse() { 181 | let buf = this._buffer; 182 | let prev = null; 183 | let Layer = linktype[this.linktype] ?? layers.Payload; 184 | 185 | let newLayer = new Layer(this._buffer, { 186 | shrinkAt: this.shrinkAt, 187 | extendAt: this.extendAt, 188 | prev, 189 | }); 190 | 191 | this._layersHead = newLayer; 192 | 193 | while (newLayer !== null && buf.length > 0) { 194 | this._layersCount++; 195 | this._layers[newLayer.name] = newLayer; 196 | buf = buf.subarray(newLayer.length); 197 | prev = newLayer; 198 | 199 | newLayer = newLayer.nextProto(layers); 200 | } 201 | 202 | this._layersTail = newLayer; 203 | 204 | this._needsParse = false; 205 | } 206 | 207 | get layers() { 208 | if (this._needsParse) { 209 | this._parse(); 210 | } 211 | if (this._needsBuild) { 212 | this._build(); 213 | } 214 | return this._layers; 215 | } 216 | 217 | toObject() { 218 | this.layers; 219 | 220 | const layers = {}; 221 | 222 | this._eachLayer(l => { 223 | layers[l.name] = l.toObject(); 224 | }); 225 | 226 | return { 227 | iface: { ...this.iface }, 228 | layers, 229 | }; 230 | } 231 | 232 | shrinkAt(...args) { 233 | throw new Error('Buffer shrinking not yet implemented, try creating new packet'); 234 | //return shrinkAt(this.buffer, ...args); 235 | } 236 | 237 | extendAt(...args) { 238 | throw new Error('Buffer extending not yet implemented, try creating new packet'); 239 | //return extendAt(this.buffer, ...args); 240 | } 241 | 242 | get buffer() { 243 | if (this._needsBuild) { 244 | this._build(); 245 | } 246 | return this._buffer; 247 | } 248 | 249 | clone() { 250 | return new Packet(this); 251 | } 252 | 253 | copy() { 254 | return new Packet(this, { copy: true }); 255 | } 256 | } 257 | 258 | for (const [name, Layer] of Object.entries(layers)) { 259 | Packet.prototype[name] = function(data) { 260 | this._toBuild.push({ Layer, data }); 261 | this.needsBuild = true; 262 | return this; 263 | } 264 | } 265 | 266 | module.exports = { Packet }; 267 | -------------------------------------------------------------------------------- /lib/pcapFile/index.js: -------------------------------------------------------------------------------- 1 | const { PcapInputStream, PcapOutputStream } = require('./pcap'); 2 | const { PcapNGInputStream, PcapNGOutputStream, constants } = require('./pcapng/index.js'); 3 | 4 | const createReadStream = ({ format = 'pcap', ...options } = {}) => { 5 | if (format == 'pcap') { 6 | return new PcapInputStream(options); 7 | } 8 | else if (format == 'pcapng') { 9 | return new PcapNGInputStream(options); 10 | } 11 | else { 12 | throw new Error(`Unknown format ${format}`); 13 | } 14 | } 15 | 16 | const createWriteStream = ({ format = 'pcap', ...options } = {}) => { 17 | if (format == 'pcap') { 18 | return new PcapOutputStream(options); 19 | } 20 | if (format == 'pcapng') { 21 | return new PcapNGOutputStream(options); 22 | } 23 | else { 24 | throw new Error(`Unknown format ${format}`); 25 | } 26 | } 27 | 28 | module.exports = { createReadStream, createWriteStream, constants }; 29 | -------------------------------------------------------------------------------- /lib/pcapFile/pcap.js: -------------------------------------------------------------------------------- 1 | const { Transform } = require('stream'); 2 | 3 | const { 4 | PcapFileHeader, 5 | PacketHeader, 6 | PcapFileHeaderBE, 7 | PacketHeaderBE, 8 | BYTE_ORDER_MAGIC, 9 | BYTE_ORDER_MAGIC_SWAPPED, 10 | BYTE_ORDER_MAGIC_NANO, 11 | BYTE_ORDER_MAGIC_SWAPPED_NANO, 12 | } = require('./structs').pcap; 13 | 14 | const { BlockReader, BufferReader } = require('./reader'); 15 | 16 | const { TimeStamp } = require('#lib/timestamp'); 17 | 18 | const defaults = require('#lib/defaults'); 19 | 20 | const { Packet } = require('#lib/packet'); 21 | 22 | class PcapReader extends BlockReader { 23 | constructor(...args) { 24 | super(...args); 25 | 26 | this.hdrReader = PcapFileHeader.createSingleReader({ toObject: false }); 27 | this.hdr = null; 28 | 29 | this.pktHdrReader = PacketHeader.createSingleReader({ toObject: true }); 30 | this.pendingPkt = null; 31 | } 32 | 33 | nextStage() { 34 | if (this.stage == 0) { 35 | this.reader = this.hdrReader; 36 | this.stage = 1; 37 | } 38 | else if (this.stage == 1) { 39 | this.hdr = this.hdrReader.result; 40 | this.pktIface = { 41 | linktype: this.hdr.linktype, 42 | }; 43 | 44 | if (this.hdr.magic == BYTE_ORDER_MAGIC_SWAPPED || this.hdr.magic == BYTE_ORDER_MAGIC_SWAPPED_NANO) { 45 | this.pktHdrReader = PacketHeaderBE.createSingleReader({ toObject: true }); 46 | this.hdr = new PcapFileHeaderBE(this.hdr.buffer).toObject(); 47 | } 48 | else if (this.hdr.magic == BYTE_ORDER_MAGIC || this.hdr.magic == BYTE_ORDER_MAGIC_NANO) { 49 | this.hdr = this.hdr.toObject(); 50 | } 51 | else { 52 | throw new Error(`Unknown magic number: ${this.hdr.magic.toString(16)}`); 53 | } 54 | this.inputStream.emit('header', this.hdr); 55 | 56 | this.timeUnit = this.hdr.magic == BYTE_ORDER_MAGIC_NANO ? 'ns' : 'ms'; 57 | 58 | this.reader = this.pktHdrReader; 59 | this.stage = 2; 60 | } 61 | else if (this.stage == 2) { 62 | this.pendingPkt = this.pktHdrReader.result; 63 | this.pendingPkt.buf = []; 64 | this.pendingPkt.currentSize = 0; 65 | this.pktHdrReader.reset(); 66 | 67 | this.reader = new BufferReader(this.pendingPkt.caplen); 68 | this.stage = 3; 69 | } 70 | else if (this.stage == 3) { 71 | this.inputStream.push(new Packet({ 72 | buffer: this.reader.result, 73 | iface: this.pktIface, 74 | timestamp: new TimeStamp({ 75 | s: this.pendingPkt.tv_sec, 76 | ...(this.timeUnit == 'ns' ? { ns: this.pendingPkt.tv_usec } : { ms: this.pendingPkt.tv_usec }) 77 | }), 78 | })); 79 | 80 | this.pendingPkt = null; 81 | 82 | this.reader = this.pktHdrReader; 83 | this.stage = 2; 84 | } 85 | } 86 | } 87 | 88 | class PcapInputStream extends Transform { 89 | constructor(opts) { 90 | super({ ...opts, readableObjectMode: true }); 91 | this.blockReader = new PcapReader(this); 92 | } 93 | 94 | _transform(chunk, encoding, callback) { 95 | try { 96 | this.blockReader.write(chunk); 97 | } catch(err) { 98 | return callback(err); 99 | } 100 | return callback(); 101 | } 102 | }; 103 | 104 | class PcapOutputStream extends Transform { 105 | constructor({ timeUnit = 'ms', iface = null, snaplen = null, ...opts } = {}) { 106 | super({ ...opts, writableObjectMode: true }); 107 | 108 | this.timeUnit = timeUnit; 109 | 110 | this.snaplen = snaplen ?? iface?.mtu ?? defaults.snaplen; 111 | this.linktype = iface?.linktype ?? defaults.linktype; 112 | this.hdrDone = false; 113 | } 114 | 115 | _transform(chunk, encoding, callback) { 116 | if (!this.hdrDone) { 117 | let snaplen, linktype; 118 | 119 | if (chunk instanceof Packet && chunk.iface) { 120 | snaplen = chunk.iface.mtu; 121 | linktype = chunk.iface.linktype; 122 | } 123 | 124 | snaplen ||= this.snaplen; 125 | linktype ||= this.linktype; 126 | 127 | const hdr = new PcapFileHeader({ 128 | magic: this.timeUnit == 'ms' ? BYTE_ORDER_MAGIC : BYTE_ORDER_MAGIC_NANO, 129 | version_major: 2, 130 | version_minor: 4, 131 | thiszone: 0, 132 | sigfigs: 0, 133 | snaplen, 134 | linktype, 135 | }); 136 | 137 | this.push(hdr.buffer); 138 | this.hdrDone = true; 139 | } 140 | 141 | let ts, buffer; 142 | 143 | if (Buffer.isBuffer(chunk)) { 144 | ts = TimeStamp.now(this.timeUnit); 145 | buffer = chunk; 146 | } 147 | else if (chunk instanceof Packet) { 148 | if (chunk.iface?.linktype !== this.linktype) { 149 | return callback(new Error(`Packet\'s linktype (${chunk.iface?.linktype}) does not match stream\'s (${this.linktype})`)); 150 | } 151 | ts = chunk.timestamp; 152 | buffer = chunk.buffer; 153 | } 154 | else { 155 | return callback(new Error(`Invalid argument: ${chunk}`)); 156 | } 157 | 158 | let tv_usec, tv_sec; 159 | 160 | if (this.timeUnit == 'ns') { 161 | const packed = ts.packedIn({ s: true, ns: true }); 162 | tv_sec = packed.s; 163 | tv_usec = packed.ns; 164 | } 165 | else { 166 | const packed = ts.packedIn({ s: true, ms: true }); 167 | tv_sec = packed.s; 168 | tv_usec = packed.ms; 169 | } 170 | 171 | const pktHdr = new PacketHeader({ 172 | tv_sec, 173 | tv_usec, 174 | caplen: buffer.length, 175 | len: buffer.length, 176 | }); 177 | 178 | this.push(pktHdr.buffer); 179 | this.push(buffer); 180 | 181 | callback(); 182 | } 183 | }; 184 | 185 | module.exports = { PcapInputStream, PcapOutputStream }; 186 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/const.js: -------------------------------------------------------------------------------- 1 | const { structs } = require('#lib/pcapFile/structs').pcapng; 2 | 3 | const blockTrailerLength = structs[0].BlockTrailer.prototype.config.length; 4 | const blockHeaderLength = structs[0].BlockHeader.prototype.config.length; 5 | const additionalLength = blockTrailerLength + blockHeaderLength; 6 | 7 | module.exports = { 8 | blockTrailerLength, 9 | blockHeaderLength, 10 | additionalLength, 11 | }; 12 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/empty.js: -------------------------------------------------------------------------------- 1 | const { BlockReader, BufferReader } = require('#lib/pcapFile/reader'); 2 | const { blockTrailerLength, blockHeaderLength, additionalLength } = require('../const'); 3 | 4 | class EmptyReader extends BlockReader { 5 | type = null; 6 | 7 | constructor(...args) { 8 | super(...args); 9 | this.readLength = 0; 10 | this.toRead = this.blockHdr.total_length - additionalLength; 11 | } 12 | 13 | nextStage() { 14 | if (this.stage == 0) { 15 | this.reader = new BufferReader(this.toRead); 16 | this.stage = 1; 17 | } 18 | else { 19 | this.reader = null; 20 | } 21 | } 22 | 23 | get result() { 24 | return {}; 25 | } 26 | } 27 | 28 | module.exports = { EmptyReader }; 29 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/enhancedPacket.js: -------------------------------------------------------------------------------- 1 | const { alignOffset } = require('struct-compile'); 2 | 3 | const { Packet } = require('#lib/packet'); 4 | const { BlockReader, BufferReader } = require('#lib/pcapFile/reader'); 5 | const { pick } = require('#lib/pick'); 6 | 7 | const { OptionReader } = require('./option'); 8 | 9 | const { blockTrailerLength, blockHeaderLength, additionalLength } = require('../const'); 10 | 11 | const { constants } = require('#lib/pcapFile/structs').pcapng; 12 | 13 | const Tsresol = require('../tsresol'); 14 | 15 | class EnhancedPacketBlockReader extends BlockReader { 16 | push = true; 17 | 18 | constructor(...args) { 19 | super(...args); 20 | this.blockReader = this.inputStream._structs.EnhancedPacketBlock.createSingleReader({ toObject: false }); 21 | this.optionReader = new OptionReader(this.inputStream); 22 | this.options = []; 23 | this.readLength = 0; 24 | this.blockLength = this.blockHdr.total_length - additionalLength; 25 | this.pktToRead = 0; 26 | } 27 | 28 | nextStage() { 29 | if (this.stage == 0) { 30 | this.reader = this.blockReader; 31 | this.stage = 1; 32 | } 33 | else if (this.stage == 1) { 34 | this.block = this.blockReader.result; 35 | this.readLength += this.block.length; 36 | this.pktToRead = alignOffset(this.block.caplen, 4); 37 | 38 | this.reader = new BufferReader(this.pktToRead); 39 | this.stage = 2; 40 | } 41 | else if (this.stage == 2) { 42 | this.buffer = this.reader.result; 43 | this.readLength += this.pktToRead; 44 | if (this.readLength < this.blockLength) { 45 | this.reader = this.optionReader; 46 | this.stage = 3; 47 | } 48 | else { 49 | this.reader = null; 50 | } 51 | } 52 | else if (this.stage == 3) { 53 | this.options.push(this.optionReader.result); 54 | this.readLength += this.optionReader.readLength; 55 | this.optionReader.reset(); 56 | if (this.readLength < this.blockLength) { 57 | this.reader = this.optionReader; 58 | this.stage = 3; 59 | } 60 | else { 61 | this.reader = null; 62 | } 63 | } 64 | } 65 | 66 | get result() { 67 | const block = this.block.toObject(); 68 | const iface = this.inputStream.interfaces[block.interface_id] ?? {}; 69 | const commentOpt = this.options.find(e => e.option_code == constants.OPT_COMMENT); 70 | 71 | return new Packet({ 72 | iface: pick(iface, 'linktype', 'name'), 73 | buffer: this.buffer.subarray(0, this.block.caplen), 74 | timestamp: Tsresol.parse(iface.tsresol, block), 75 | ...(commentOpt && { comment: commentOpt.buffer.toString().split('\0')[0] }), 76 | ...(this.block.caplen != this.block.len && { origLength: this.block.len }), 77 | }); 78 | } 79 | } 80 | 81 | module.exports = { EnhancedPacketBlockReader }; 82 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/index.js: -------------------------------------------------------------------------------- 1 | const { PcapNGReader } = require('./pcapng'); 2 | 3 | module.exports = { PcapNGReader }; 4 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/interfaceDescription.js: -------------------------------------------------------------------------------- 1 | const { BlockReader } = require('#lib/pcapFile/reader'); 2 | 3 | const { OptionReader } = require('./option'); 4 | const { blockTrailerLength, blockHeaderLength, additionalLength } = require('../const'); 5 | 6 | class InterfaceDescriptionBlockReader extends BlockReader { 7 | type = 'interface-description'; 8 | 9 | constructor(...args) { 10 | super(...args); 11 | this.blockReader = this.inputStream._structs.InterfaceDescriptionBlock.createSingleReader({ toObject: false }); 12 | this.optionReader = new OptionReader(this.inputStream); 13 | this.options = []; 14 | this.readLength = 0; 15 | } 16 | 17 | nextStage() { 18 | if (this.stage == 0) { 19 | this.reader = this.blockReader; 20 | this.stage = 1; 21 | } 22 | else if (this.stage == 1) { 23 | this.block = this.blockReader.result; 24 | this.blockLength = this.blockHdr.total_length - additionalLength; 25 | this.readLength += this.block.length; 26 | if (this.readLength < this.blockLength) { 27 | this.reader = this.optionReader; 28 | this.stage = 2; 29 | } 30 | else { 31 | this.reader = null; 32 | } 33 | } 34 | else if (this.stage == 2) { 35 | this.options.push(this.optionReader.result); 36 | this.readLength += this.optionReader.readLength; 37 | this.optionReader.reset(); 38 | 39 | if (this.readLength < this.blockLength) { 40 | this.reader = this.optionReader; 41 | this.stage = 2; 42 | } 43 | else { 44 | this.reader = null; 45 | } 46 | } 47 | } 48 | 49 | get result() { 50 | return { 51 | ...this.block.toObject(), 52 | options: this.options, 53 | }; 54 | } 55 | } 56 | 57 | module.exports = { InterfaceDescriptionBlockReader }; 58 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/option.js: -------------------------------------------------------------------------------- 1 | const { alignOffset } = require('struct-compile'); 2 | 3 | const { BlockReader, BufferReader } = require('#lib/pcapFile/reader'); 4 | 5 | class OptionReader extends BlockReader { 6 | constructor(...args) { 7 | super(...args); 8 | this.optionHdrReader = this.inputStream._structs.OptionHeader.createSingleReader({ toObject: true }); 9 | this.reset(); 10 | } 11 | 12 | reset() { 13 | this.optionHdrReader.reset(); 14 | this.readLength = 0; 15 | this.optionHdr = null; 16 | this.resetBlockReader(); 17 | } 18 | 19 | nextStage() { 20 | if (this.stage == 0) { 21 | this.reader = this.optionHdrReader; 22 | this.stage = 1; 23 | } 24 | else if (this.stage == 1) { 25 | this.optionHdr = this.optionHdrReader.result; 26 | this.optionLength = alignOffset(this.optionHdr.option_length, 4); 27 | 28 | this.reader = new BufferReader(this.optionLength); 29 | this.stage = 2; 30 | } 31 | else if (this.stage == 2) { 32 | this.optionBuf = this.reader.result; 33 | this.readLength = this.inputStream._structs.OptionHeader.prototype.config.length + this.optionLength; 34 | 35 | this.reader = null; 36 | } 37 | } 38 | 39 | get result() { 40 | return { 41 | ...this.optionHdr, 42 | buffer: this.optionBuf.toString(), 43 | }; 44 | } 45 | }; 46 | 47 | module.exports = { OptionReader }; 48 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/packetBlock.js: -------------------------------------------------------------------------------- 1 | const { BlockReader } = require('#lib/pcapFile/reader'); 2 | 3 | class PacketBlockReader extends BlockReader { 4 | constructor(...args) { 5 | super(...args); 6 | } 7 | } 8 | 9 | module.exports = { PacketBlockReader }; 10 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/pcapng.js: -------------------------------------------------------------------------------- 1 | const { BlockReader } = require('#lib/pcapFile/reader'); 2 | 3 | const { 4 | BT_SHB, 5 | BT_IDB, 6 | BT_EPB, 7 | BT_SPB, 8 | } = require('#lib/pcapFile/structs').pcapng; 9 | 10 | const { SectionBlockReader } = require('./sectionBlock'); 11 | const { InterfaceDescriptionBlockReader } = require('./interfaceDescription'); 12 | const { EnhancedPacketBlockReader } = require('./enhancedPacket'); 13 | const { SimplePacketBlockReader } = require('./simplePacket'); 14 | const { EmptyReader } = require('./empty'); 15 | 16 | function getBlockReader(inputStream, blockHdr) { 17 | const { block_type } = blockHdr; 18 | switch(block_type) { 19 | case BT_SHB: 20 | return new SectionBlockReader(inputStream, blockHdr); 21 | case BT_IDB: 22 | return new InterfaceDescriptionBlockReader(inputStream, blockHdr); 23 | case BT_EPB: 24 | return new EnhancedPacketBlockReader(inputStream, blockHdr); 25 | case BT_SPB: 26 | return new SimplePacketBlockReader(inputStream, blockHdr); 27 | /*case BT_PB: 28 | return new PacketBlockReader(inputStream, blockHdr);*/ 29 | default: 30 | return new EmptyReader(inputStream, blockHdr); 31 | } 32 | }; 33 | 34 | class PcapNGReader extends BlockReader { 35 | root = true; 36 | constructor(...args) { 37 | super(...args); 38 | } 39 | 40 | nextStage() { 41 | if (this.stage == 0) { 42 | this.inputStream.hdrReader.reset(); 43 | this.reader = this.inputStream.hdrReader; 44 | this.stage = 1; 45 | } 46 | else if (this.stage == 1) { 47 | this.hdr = this.reader.result; 48 | this.reader.reset(); 49 | 50 | this.reader = getBlockReader(this.inputStream, this.hdr); 51 | this.stage = 2; 52 | } 53 | else if (this.stage == 2) { 54 | this.block = this.reader.result; 55 | if (this.reader.type) { 56 | this.inputStream.emit(this.reader.type, this.block); 57 | } 58 | else if (this.reader.push) { 59 | this.inputStream.push(this.block); 60 | } 61 | 62 | this.reader = this.inputStream.trailerReader; 63 | this.stage = 3; 64 | } 65 | else if (this.stage == 3) { 66 | this.trailer = this.reader.result; 67 | if (this.trailer.total_length != this.hdr.total_length) { 68 | throw new Error(`Inavlid block trailer with value ${this.trailer.total_length}`); 69 | } 70 | this.inputStream.trailerReader.reset(); 71 | this.hdr = null; 72 | this.block = null; 73 | this.trailer = null; 74 | 75 | this.reader = this.inputStream.hdrReader; 76 | this.stage = 1; 77 | } 78 | } 79 | } 80 | 81 | module.exports = { PcapNGReader }; 82 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/sectionBlock.js: -------------------------------------------------------------------------------- 1 | const { BlockReader } = require('#lib/pcapFile/reader'); 2 | 3 | const { OptionReader } = require('./option'); 4 | const { blockTrailerLength, blockHeaderLength, additionalLength } = require('../const'); 5 | 6 | const { 7 | PCAP_NG_VERSION_MAJOR, 8 | PCAP_NG_VERSION_MINOR, 9 | BYTE_ORDER_MAGIC, 10 | BYTE_ORDER_MAGIC_SWAP, 11 | } = require('#lib/pcapFile/structs').pcapng; 12 | 13 | class SectionBlockReader extends BlockReader { 14 | type = 'section-header'; 15 | 16 | constructor(...args) { 17 | super(...args); 18 | this.blockReader = this.inputStream._structs.SectionHeaderBlock.createSingleReader({ toObject: false }); 19 | this.optionReader = new OptionReader(this.inputStream); 20 | this.options = []; 21 | this.readLength = 0; 22 | } 23 | 24 | nextStage() { 25 | if (this.stage == 0) { 26 | this.reader = this.blockReader; 27 | this.stage = 1; 28 | } 29 | else if (this.stage == 1) { 30 | this.block = this.blockReader.result; 31 | if (this.block.byte_order_magic == BYTE_ORDER_MAGIC_SWAP) { 32 | this.inputStream._toggleEndianness(); 33 | this.inputStream._defaultReaders(); 34 | this.block = new this.inpusStream._structs.SectionHeaderBlock(this.block.buffer); 35 | this.blockHdr = new this.inpusStream._structs.BlockHeader(this.blockHdr.buffer); 36 | } 37 | if (this.block.byte_order_magic != BYTE_ORDER_MAGIC) { 38 | throw new Error(`Invalid byte_order_magic: ${this.block.byte_order_magic}`); 39 | } 40 | this.blockLength = this.blockHdr.total_length - additionalLength; 41 | this.readLength += this.block.length; 42 | 43 | if (this.readLength < this.blockLength) { 44 | this.reader = this.optionReader; 45 | this.stage = 2; 46 | } 47 | else { 48 | this.reader = null; 49 | } 50 | } 51 | else if (this.stage == 2) { 52 | this.options.push(this.optionReader.result); 53 | this.readLength += this.optionReader.readLength; 54 | this.optionReader.reset(); 55 | 56 | if (this.readLength < this.blockLength) { 57 | this.reader = this.optionReader; 58 | this.stage = 2; 59 | } 60 | else { 61 | this.reader = null; 62 | } 63 | } 64 | } 65 | 66 | get result() { 67 | return { 68 | ...this.block.toObject(), 69 | options: this.options, 70 | }; 71 | } 72 | } 73 | 74 | module.exports = { SectionBlockReader }; 75 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/readers/simplePacket.js: -------------------------------------------------------------------------------- 1 | const { alignOffset } = require('struct-compile'); 2 | 3 | const { BlockReader, BufferReader } = require('#lib/pcapFile/reader'); 4 | 5 | const { blockTrailerLength, blockHeaderLength, additionalLength } = require('../const'); 6 | 7 | class SimplePacketBlockReader extends BlockReader { 8 | push = true; 9 | 10 | constructor(...args) { 11 | super(...args); 12 | this.blockReader = this.inputStream._structs.SimplePacketBlock.createSingleReader({ toObject: false }); 13 | this.blockLength = this.blockHdr.total_length - additionalLength; 14 | this.pktToRead = 0; 15 | } 16 | 17 | nextStage() { 18 | if (this.stage == 0) { 19 | this.reader = this.blockReader; 20 | this.stage = 1; 21 | } 22 | else if (this.stage == 1) { 23 | this.block = this.blockReader.result; 24 | this.readLength += this.block.length; 25 | this.pktToRead = alignOffset(this.block.caplen, 4); 26 | 27 | this.reader = new BufferReader(this.pktToRead); 28 | this.stage = 2; 29 | } 30 | else if (this.stage == 2) { 31 | this.pkt = this.reader.result; 32 | this.readLength += this.pktToRead; 33 | this.reader = null; 34 | } 35 | } 36 | 37 | get result() { 38 | return new Packet({ 39 | buffer: this.pkt.subarray(0, this.block.caplen), 40 | }); 41 | } 42 | } 43 | 44 | module.exports = { SimplePacketBlockReader }; 45 | -------------------------------------------------------------------------------- /lib/pcapFile/pcapng/tsresol.js: -------------------------------------------------------------------------------- 1 | const { TimeStamp } = require('#lib/timestamp'); 2 | 3 | const _defaultResol = 1e6; 4 | 5 | function getResol(tsresol) { 6 | let resol = null; 7 | 8 | if (tsresol) { 9 | const resolValue = Buffer.from(tsresol).readInt8(); 10 | const flag = (1 << 31); 11 | if (resolValue & flag) { 12 | resol = Math.round(1 / Math.pow(2, -1 * (resolValue & (~flag)))); 13 | } 14 | else { 15 | resol = Math.round(1 / Math.pow(10, -1 * (resolValue & (~flag)))); 16 | } 17 | } 18 | 19 | return resol; 20 | } 21 | 22 | 23 | function serialize(tsresol, ts, defaultResol = _defaultResol) { 24 | const resol = getResol(tsresol ?? ts.tsresol) ?? defaultResol; 25 | 26 | const value = ts.ns * BigInt(resol) / BigInt(1e9); 27 | timestamp_high = Number((value & BigInt(0xffffffff00000000)) >> 32n); 28 | timestamp_low = Number(value & BigInt(0x00000000ffffffff)); 29 | 30 | return { timestamp_high, timestamp_low }; 31 | } 32 | 33 | function parse(tsresol, { timestamp_high, timestamp_low }, defaultResol = _defaultResol) { 34 | const resol = getResol(tsresol) ?? defaultResol; 35 | 36 | const ns = ((BigInt(timestamp_high) << 32n) + BigInt(timestamp_low)) * BigInt(1e9) / BigInt(resol); 37 | 38 | const res = new TimeStamp({ 39 | s: Number(ns / BigInt(1e9)), 40 | ns: Number(ns % BigInt(1e9)), 41 | }); 42 | 43 | //ugly move to keep the same tsresol 44 | res.tsresol = tsresol ?? Buffer.from([0x06, 0x00, 0x00, 0x00]); 45 | 46 | return res; 47 | } 48 | 49 | module.exports = { serialize, parse }; 50 | -------------------------------------------------------------------------------- /lib/pcapFile/reader.js: -------------------------------------------------------------------------------- 1 | class BlockReader { 2 | constructor(inputStream, blockHdr = null) { 3 | this.inputStream = inputStream; 4 | this.blockHdr = blockHdr; 5 | this.resetBlockReader(); 6 | } 7 | 8 | resetBlockReader() { 9 | this.finished = false; 10 | this.remaining = null; 11 | this.stage = -1; 12 | } 13 | 14 | write(chunk) { 15 | if (this.finished == true) { 16 | throw new Error('Already finished'); 17 | } 18 | if (this.stage == -1) { 19 | this.stage = 0; 20 | this.nextStage(); 21 | } 22 | let buf = chunk; 23 | while (buf?.length > 0) { 24 | if (this.reader === null) { 25 | this.finished = true; 26 | this.remaining = buf; 27 | break; 28 | } 29 | 30 | this.reader.write(buf); 31 | 32 | if (this.reader.finished) { 33 | buf = this.reader.remaining; 34 | this.nextStage(); 35 | } 36 | else { 37 | buf = null; 38 | } 39 | } 40 | } 41 | } 42 | 43 | class BufferReader { 44 | constructor(length) { 45 | this.length = length; 46 | this.bufs = []; 47 | this.readLength = 0; 48 | } 49 | 50 | write(chunk) { 51 | if (this.finished == true) { 52 | throw new Error('Already finished'); 53 | } 54 | this.bufs.push(chunk); 55 | this.readLength += chunk.length; 56 | if (this.readLength >= this.length) { 57 | const output = Buffer.concat(this.bufs); 58 | this.result = output.subarray(0, this.length); 59 | this.remaining = output.subarray(this.length); 60 | this.finished = true; 61 | } 62 | } 63 | } 64 | 65 | module.exports = { BlockReader, BufferReader }; 66 | -------------------------------------------------------------------------------- /lib/pcapFile/structs.js: -------------------------------------------------------------------------------- 1 | const { compile } = require('struct-compile'); 2 | const { toBE } = require('#lib/struct'); 3 | 4 | function initPcap() { 5 | /* 6 | * Byte-order magic value. 7 | */ 8 | const BYTE_ORDER_MAGIC = 0xA1B2C3D4; 9 | const BYTE_ORDER_MAGIC_NANO = 0xA1B23C4D; 10 | const BYTE_ORDER_MAGIC_SWAPPED = 0xD4C3B2A1; 11 | const BYTE_ORDER_MAGIC_SWAPPED_NANO = 0x4D3CB2A1; 12 | 13 | const { PcapFileHeader, PacketHeader } = compile(` 14 | //@LE 15 | struct PcapFileHeader { 16 | uint32_t magic; 17 | uint16_t version_major; 18 | uint16_t version_minor; 19 | int32_t thiszone; 20 | uint32_t sigfigs; 21 | uint32_t snaplen; 22 | uint32_t linktype; 23 | }; 24 | 25 | //@LE 26 | struct PacketHeader { 27 | uint32_t tv_sec; 28 | uint32_t tv_usec; 29 | uint32_t caplen; 30 | uint32_t len; 31 | }; 32 | `); 33 | 34 | const PcapFileHeaderBE = toBE(PcapFileHeader); 35 | const PacketHeaderBE = toBE(PacketHeader); 36 | 37 | return { 38 | PcapFileHeader, 39 | PacketHeader, 40 | PcapFileHeaderBE, 41 | PacketHeaderBE, 42 | BYTE_ORDER_MAGIC, 43 | BYTE_ORDER_MAGIC_SWAPPED, 44 | BYTE_ORDER_MAGIC_NANO, 45 | BYTE_ORDER_MAGIC_SWAPPED_NANO, 46 | }; 47 | } 48 | 49 | function initPcapNG() { 50 | const res = {}; 51 | 52 | res.BT_SHB = 0x0A0D0D0A; 53 | /* 54 | * Byte-order magic value. 55 | */ 56 | res.BYTE_ORDER_MAGIC = 0x1A2B3C4D; 57 | res.BYTE_ORDER_MAGIC_SWAP = 0x4D3C2B1A; 58 | 59 | /* 60 | * Current version number. If major_version isn't PCAP_NG_VERSION_MAJOR, 61 | * or if minor_version isn't PCAP_NG_VERSION_MINOR or 2, that means that 62 | * this code can't read the file. 63 | */ 64 | res.PCAP_NG_VERSION_MAJOR = 1; 65 | res.PCAP_NG_VERSION_MINOR = 0; 66 | 67 | /* 68 | * Interface Description Block. 69 | */ 70 | res.BT_IDB = 0x00000001; 71 | 72 | res.constants = {}; 73 | /* 74 | * Options in the IDB. 75 | */ 76 | res.constants.OPT_IF_NAME = 2; /* interface name string */ 77 | res.constants.OPT_IF_DESCRIPTION = 3; /* interface description string */ 78 | res.constants.OPT_IF_IPV4ADDR = 4; /* interface's IPv4 address and netmask */ 79 | res.constants.OPT_IF_IPV6ADDR = 5; /* interface's IPv6 address and prefix length */ 80 | res.constants.OPT_IF_MACADDR = 6; /* interface's MAC address */ 81 | res.constants.OPT_IF_EUIADDR = 7; /* interface's EUI address */ 82 | res.constants.OPT_IF_SPEED = 8; /* interface's speed, in bits/s */ 83 | res.constants.OPT_IF_TSRESOL = 9; /* interface's time stamp resolution */ 84 | res.constants.OPT_IF_TZONE = 10; /* interface's time zone */ 85 | res.constants.OPT_IF_FILTER = 11; /* filter used when capturing on interface */ 86 | res.constants.OPT_IF_OS = 12; /* string OS on which capture on this interface was done */ 87 | res.constants.OPT_IF_FCSLEN = 13; /* FCS length for this interface */ 88 | res.constants.OPT_IF_TSOFFSET = 14; /* time stamp offset for this interface */ 89 | 90 | res.constants.OPT_EOFOPT = 0; 91 | res.constants.OPT_COMMENT = 1; 92 | res.constants.OPT_SHB_HARDWARE = 2; 93 | res.constants.OPT_SHB_OS = 3; 94 | res.constants.OPT_SHB_USERAPPL = 4; 95 | res.constants.OPT_EPB_FLAGS = 2; 96 | res.constants.OPT_EPB_HASH = 3; 97 | res.constants.OPT_EPB_DROPCOUNT = 4; 98 | 99 | /* 100 | * Enhanced Packet Block. 101 | */ 102 | res.BT_EPB = 0x00000006; 103 | 104 | /* 105 | * Simple Packet Block. 106 | */ 107 | res.BT_SPB = 0x00000003; 108 | 109 | /* 110 | * Packet Block. 111 | */ 112 | res.BT_PB = 0x00000002; 113 | 114 | const structsInit = compile(` 115 | //@LE 116 | struct BlockHeader { 117 | uint32_t block_type; 118 | uint32_t total_length; 119 | }; 120 | 121 | //@LE 122 | struct BlockTrailer { 123 | uint32_t total_length; 124 | }; 125 | 126 | //@LE 127 | struct OptionHeader { 128 | uint16_t option_code; 129 | uint16_t option_length; 130 | }; 131 | 132 | //@LE 133 | struct SectionHeaderBlock { 134 | uint32_t byte_order_magic; 135 | uint16_t major_version; 136 | uint16_t minor_version; 137 | int64_t section_length; 138 | }; 139 | 140 | //@LE 141 | struct InterfaceDescriptionBlock { 142 | uint16_t linktype; 143 | uint16_t reserved; 144 | uint32_t snaplen; 145 | /* followed by options and trailer */ 146 | }; 147 | 148 | //@LE 149 | struct EnhancedPacketBlock { 150 | uint32_t interface_id; 151 | uint32_t timestamp_high; 152 | uint32_t timestamp_low; 153 | uint32_t caplen; 154 | uint32_t len; 155 | /* followed by packet data, options, and trailer */ 156 | }; 157 | 158 | //@LE 159 | struct SimplePacketBlock { 160 | uint32_t caplen; 161 | /* followed by packet data and trailer */ 162 | }; 163 | 164 | //@LE 165 | struct PacketBlock { 166 | uint16_t interface_id; 167 | uint16_t drops_count; 168 | uint32_t timestamp_high; 169 | uint32_t timestamp_low; 170 | uint32_t caplen; 171 | uint32_t len; 172 | /* followed by packet data, options, and trailer */ 173 | }; 174 | `); 175 | 176 | res.structs = [ 177 | structsInit, //LE 178 | Object.keys(structsInit).reduce((res, key) => ({ ...res, [key]: toBE(structsInit[key]) }), {}), //BE 179 | ]; 180 | 181 | return res; 182 | }; 183 | 184 | module.exports = { 185 | pcap: initPcap(), 186 | pcapng: initPcapNG(), 187 | }; 188 | -------------------------------------------------------------------------------- /lib/pick.js: -------------------------------------------------------------------------------- 1 | const has = (obj, k) => obj[k] !== undefined; 2 | 3 | const pick = (obj, ...keys) => keys.reduce((res, e) => has(obj, e) ? { ...res, [e]: obj[e] } : res, {}); 4 | 5 | const omit = (obj, ...keys) => Object.keys(obj).reduce((res, e) => (keys.includes(e) || !has(obj, e)) ? res : { ...res, [e]: obj[e] }, {}); 6 | 7 | module.exports = { 8 | pick, 9 | omit, 10 | }; 11 | -------------------------------------------------------------------------------- /lib/routing.js: -------------------------------------------------------------------------------- 1 | const os = require('node:os'); 2 | const fs = require('node:fs'); 3 | const readline = require('node:readline'); 4 | const { inetPton, inetNtop } = require('#lib/converters'); 5 | 6 | const platform = os.platform(); 7 | 8 | 9 | /** 10 | * Retrieves the routing table for all interfaces. 11 | * 12 | * @async 13 | * @function getRoutingTable 14 | * @returns {Promise} An object where the keys are interface names and the values are arrays of routing table records. 15 | * Each routing table record is represented by an object with `destination`, `gateway`, and `mask` properties. 16 | * 17 | * @example 18 | * const routingTable = await getRoutingTable(); 19 | * // routingTable = { 20 | * // eth0: [ 21 | * // { destination: '0.0.0.0', gateway: '192.168.1.1', mask: '0.0.0.0' }, 22 | * // { destination: '192.168.1.0', gateway: '0.0.0.0', mask: '255.255.255.0' } 23 | * // ], 24 | * // wlan0: [ 25 | * // { destination: '10.0.0.0', gateway: '10.0.0.1', mask: '255.255.255.0' } 26 | * // ] 27 | * // } 28 | */ 29 | 30 | if (platform === 'linux') { 31 | async function getRoutingTable() { 32 | const rl = readline.createInterface({ 33 | input: fs.createReadStream('/proc/net/route'), 34 | crlfDelay: Infinity, 35 | }); 36 | 37 | const res = {}; 38 | let fst = true; 39 | for await (const line of rl) { 40 | if (fst) { 41 | fst = false; 42 | continue; 43 | } 44 | 45 | const [iface, destination, gateway, flags,,,, mask] = line.split(/\s/).filter(e => e.length > 0); 46 | 47 | if (!res[iface]) { 48 | res[iface] = []; 49 | } 50 | 51 | res[iface].push({ 52 | destination: inetNtop(parseInt(destination, 16)), 53 | gateway: inetNtop(parseInt(gateway, 16)), 54 | flags: parseInt(flags, 16), 55 | mask: inetNtop(parseInt(mask, 16)), 56 | }); 57 | } 58 | return res; 59 | } 60 | 61 | module.exports = { getRoutingTable }; 62 | } 63 | else { 64 | const { getRoutingTable: getRoutingTableCxx } = require('./bindings'); 65 | 66 | module.exports = { 67 | async getRoutingTable() { 68 | return new Promise((resolve, reject) => { 69 | getRoutingTableCxx(res => { 70 | if (res instanceof Error) { 71 | reject(res); 72 | } 73 | resolve(res); 74 | }); 75 | }); 76 | }, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/socket.js: -------------------------------------------------------------------------------- 1 | const { socket } = require('#lib/bindings'); 2 | const { defaultFamily, updateDomain } = require('#lib/af'); 3 | 4 | const { SockAddr: SockAddrCxx } = socket; 5 | 6 | class SockAddr extends SockAddrCxx { 7 | constructor(obj) { 8 | super(obj); 9 | if (this.domain == -1) { 10 | updateDomain(this, obj.ip); 11 | } 12 | } 13 | 14 | set ip(val) { 15 | if (this.domain == -1) { 16 | updateDomain(this, val); 17 | } 18 | this.ip = ip; 19 | } 20 | }; 21 | 22 | socket.SockAddr = SockAddr; 23 | 24 | module.exports = socket; 25 | -------------------------------------------------------------------------------- /lib/struct.js: -------------------------------------------------------------------------------- 1 | const { fromConfig } = require('struct-compile'); 2 | 3 | const changeMeta = (struct, meta) => { 4 | const { config } = struct.prototype; 5 | return fromConfig({ ...config, meta }); 6 | } 7 | 8 | const toBE = struct => changeMeta(struct, { BE: true }); 9 | 10 | const addField = (proto, field) => { 11 | proto.config.fields[field] = { d: [] }; 12 | } 13 | 14 | module.exports = { toBE, addField }; 15 | -------------------------------------------------------------------------------- /lib/timestamp.js: -------------------------------------------------------------------------------- 1 | const isBigInt = v => typeof v == 'bigint'; 2 | const nullToBool = v => v !== null; 3 | const sign = v => { 4 | if (typeof v == 'bigint') { 5 | return v == 0n ? 0 : (v < 0n ? -1 : 1); 6 | } 7 | 8 | return v == 0 ? 0 : (v < 0 ? -1 : 1); 9 | } 10 | 11 | class TimeStamp { 12 | constructor({ s = null, ms = null, ns = null } = {}) { 13 | if (s === null && ms === null && ns === null) { 14 | return TimeStamp.now(); 15 | } 16 | 17 | if (isBigInt(s) || isBigInt(ms) || isBigInt(ns)) { 18 | this.useBigInt = true; 19 | } 20 | else { 21 | this.useBigInt = false; 22 | } 23 | 24 | this._s = s; 25 | this._ms = ms; 26 | this._ns = ns; 27 | } 28 | 29 | clone() { 30 | return new TimeStamp({ 31 | s: this._s, 32 | ms: this._ms, 33 | ns: this._ns, 34 | }); 35 | } 36 | 37 | compare(ts) { 38 | return sign(this.ns - ts.ns); 39 | } 40 | 41 | static now(unit = 'ms') { 42 | if (unit == 's') { 43 | return new TimeStamp({ s: Date.now() / 1e3 }); 44 | } 45 | 46 | if (unit == 'ms') { 47 | return new TimeStamp({ ms: Date.now() }); 48 | } 49 | 50 | if (unit == 'ns') { 51 | const now = performance.timeOrigin + performance.now(); 52 | const ms = Math.floor(now); 53 | const ns = Math.floor((now - ms) * 1e6); 54 | return new TimeStamp({ ms: Math.floor(now), ns }); 55 | } 56 | } 57 | 58 | get s() { 59 | if (this.useBigInt) { 60 | return Number(BigInt(this._s ?? 0) + BigInt(this._ms ?? 0) / BigInt(1e3) + BigInt(this._ns ?? 0) / BigInt(1e9)); 61 | } 62 | 63 | return (this._s ?? 0) + (this._ms ?? 0) / 1e3 + (this._ns ?? 0) / 1e9; 64 | } 65 | 66 | get ms() { 67 | if (this.useBigInt) { 68 | return Number(BigInt(this._s ?? 0) * BigInt(1e3) + BigInt(this._ms ?? 0) + BigInt(this._ns ?? 0) / BigInt(1e6)); 69 | } 70 | 71 | return (this._s ?? 0) * 1e3 + (this._ms ?? 0) + (this._ns ?? 0) / 1e6; 72 | } 73 | 74 | get ns() { 75 | return BigInt(this._s ?? 0) * BigInt(1e9) + BigInt(this._ms ?? 0) * BigInt(1e6) + BigInt(this._ns ?? 0); 76 | } 77 | 78 | //TODO seems like a not so optimal solution 79 | packedIn(units) { 80 | const { s = false, ms = false, ns = false } = units; 81 | const res = {}; 82 | 83 | if ( 84 | nullToBool(this._s) === s && 85 | nullToBool(this._ms) === ms && 86 | nullToBool(this._ns) === ns 87 | ) { 88 | if (s) { 89 | res.s = this._s; 90 | } 91 | if (ms) { 92 | res.ms = this._ms; 93 | } 94 | if (ns) { 95 | res.ns = this._ns; 96 | } 97 | 98 | return res; 99 | } 100 | 101 | let min = this.ns; 102 | 103 | if (s) { 104 | res.s = Number(min / BigInt(1e9)); 105 | min = min % BigInt(1e9); 106 | } 107 | if (ms) { 108 | res.ms = Number(min / BigInt(1e6)); 109 | min = min % BigInt(1e6); 110 | } 111 | if (ns) { 112 | res.ns = Number(min); 113 | } 114 | 115 | return res; 116 | } 117 | 118 | toDate() { 119 | return new Date(this.ms); 120 | } 121 | }; 122 | 123 | module.exports = { TimeStamp }; 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "over-the-wire", 3 | "version": "1.0.3", 4 | "description": "Network inspection library for Node", 5 | "main": "lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/vaguue/over-the-wire.git" 9 | }, 10 | "homepage": "https://github.com/vaguue/over-the-wire", 11 | "bugs": { 12 | "url": "https://github.com/vaguue/over-the-wire/issues" 13 | }, 14 | "author": "Seva D. ", 15 | "scripts": { 16 | "install": "prebuild-install -r node || cmake-js compile", 17 | "rebuild-debug": "cmake-js compile -D", 18 | "build-debug": "cmake-js build -D", 19 | "rebuild": "cmake-js compile", 20 | "precompile": "prebuild --backend cmake-js -r node -t 18.0.0 -t 20.0.0 -t 22.0.0 -t 23.0.0", 21 | "build": "cmake-js build", 22 | "test": "node --test test/*.test.js", 23 | "test-cov-text": "node --test --experimental-test-coverage test/*.test.js", 24 | "test-cov": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info test/*.test.js", 25 | "generate-docs": "jsdoc --configure jsdoc.json --verbose" 26 | }, 27 | "imports": { 28 | "#lib/*": "./lib/*.js" 29 | }, 30 | "keywords": [ 31 | "security", 32 | "pcap", 33 | "network", 34 | "packet-analyser", 35 | "packet-sniffer", 36 | "network-discovery", 37 | "network-analysis", 38 | "packet-crafting", 39 | "packet-capture", 40 | "network-security", 41 | "security-tools" 42 | ], 43 | "license": "ISC", 44 | "dependencies": { 45 | "bindings": "^1.5.0", 46 | "node-addon-api": "^8.3.1", 47 | "prebuild": "^13.0.1", 48 | "@vaguuue/prebuild-install": "^7.1.2", 49 | "struct-compile": "^1.2.2" 50 | }, 51 | "engines": { 52 | "node": ">=16.10.0" 53 | }, 54 | "files": [ 55 | "assets", 56 | "lib", 57 | "test" 58 | ], 59 | "devDependencies": { 60 | "clean-jsdoc-theme": "^4.3.0", 61 | "cmake-js": "^7.3.0", 62 | "jsdoc": "^4.0.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/Ethernet.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { extendAt, shrinkAt } = require('#lib/buffer'); 5 | const { Ethernet } = require('#lib/layers/Ethernet'); 6 | 7 | test('Ethernet', async (t) => { 8 | const eth = new Ethernet(Buffer.from('1111111111112222222222220800', 'hex')); 9 | eth.src = eth.dst; 10 | assert.deepEqual(eth.toObject(), { dst: '11:11:11:11:11:11', src: '11:11:11:11:11:11', type: 2048 }); 11 | 12 | assert.deepEqual(eth.toObject(), new Ethernet(eth.toObject()).toObject()); 13 | assert.deepEqual(new Ethernet(eth.toObject()).buffer, eth.buffer); 14 | }); 15 | -------------------------------------------------------------------------------- /test/IPv4.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { extendAt, shrinkAt } = require('#lib/buffer'); 5 | const { IPv4 } = require('#lib/layers/IPv4'); 6 | 7 | test('IPv4', async (t) => { 8 | const buf = Buffer.from('450000730000400040068cd2c0a80167cebd1ce6e33d5debb394ef8d', 'hex'); 9 | const ip = new IPv4(buf, { 10 | shrinkAt(...args) { 11 | return shrinkAt(buf, ...args); 12 | }, 13 | extendAt(...args) { 14 | return extendAt(buf, ...args); 15 | }, 16 | }); 17 | 18 | assert.deepEqual( 19 | ip.toObject(), 20 | { 21 | headerLength: 5, 22 | version: 4, 23 | typeOfService: 0, 24 | totalLength: 115, 25 | id: 0, 26 | fragmentOffsetRaw: 64, 27 | timeToLive: 64, 28 | protocol: 6, 29 | checksum: 36050, 30 | src: '192.168.1.103', 31 | dst: '206.189.28.230', 32 | fragmentInfo: { isFragment: false, value: 0, flags: 64 }, 33 | options: [], 34 | } 35 | ); 36 | 37 | assert.equal(ip.length, 20); 38 | 39 | const { checksum } = ip; 40 | ip.checksum = 0; 41 | ip.calculateChecksum(); 42 | assert.equal(ip.checksum, checksum); 43 | assert.equal(ip.buffer, buf); 44 | 45 | ip.dst = '192.168.1.1'; 46 | assert.equal(ip.dst, '192.168.1.1'); 47 | 48 | let options = [ 49 | { type: 1, recLength: 4, value: Buffer.from([0xaa, 0xaa, 0xaa, 0xaa]) }, 50 | { type: 2, recLength: 2, value: Buffer.from([0xbb, 0xbb]) }, 51 | { type: 0, recLength: 0, value: Buffer.from([]) } 52 | ]; 53 | 54 | ip.options = options; 55 | 56 | assert.deepEqual([...ip.options], options); 57 | assert.equal(ip.length, 32); 58 | 59 | options = [ 60 | { type: 1, recLength: 4, value: Buffer.from([0xaa, 0xaa, 0xaa, 0xaa]) }, 61 | { type: 0, recLength: 0, value: Buffer.from([]) } 62 | ]; 63 | 64 | ip.options = options; 65 | 66 | assert.deepEqual([...ip.options], options); 67 | 68 | assert.deepEqual(ip.toObject(), new IPv4(ip.toObject()).toObject()); 69 | assert.deepEqual(new IPv4(ip.toObject()).buffer, ip.buffer); 70 | }); 71 | -------------------------------------------------------------------------------- /test/TCP.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { extendAt, shrinkAt } = require('#lib/buffer'); 5 | const { TCP } = require('#lib/layers/TCP'); 6 | 7 | test('TCP', async (t) => { 8 | const buf = Buffer.from('cd8e5debee16992ebea89919801008000d1200000101080a52d3c650dd04cdd6', 'hex'); 9 | const opts = { 10 | shrinkAt(...args) { 11 | return shrinkAt(buf, ...args); 12 | }, 13 | extendAt(...args) { 14 | return extendAt(buf, ...args); 15 | }, 16 | prev: { 17 | src: '192.168.1.101', 18 | dst: '165.22.44.6', 19 | name: 'IPv4', 20 | } 21 | }; 22 | 23 | const tcp = new TCP(buf, opts); 24 | 25 | assert.deepEqual(tcp.toObject(), { 26 | src: 52622, 27 | dst: 24043, 28 | seq: 3994458414, 29 | ack: 3198720281, 30 | dataOffset: 8, 31 | windowSize: 2048, 32 | checksum: 3346, 33 | urgentPointer: 0, 34 | flags: { 35 | reserved: 0, 36 | cwr: 0, 37 | ece: 0, 38 | urg: 0, 39 | ack: 1, 40 | psh: 0, 41 | rst: 0, 42 | syn: 0, 43 | fin: 0 44 | }, 45 | options: [ 46 | { type: 1 }, 47 | { type: 1 }, 48 | { type: 8, recLength: 10, value: Buffer.from([0x52, 0xd3, 0xc6, 0x50, 0xdd, 0x04, 0xcd, 0xd6]) } 49 | ], 50 | }); 51 | 52 | const { buffer, checksum } = tcp; 53 | 54 | tcp.checksum = 0; 55 | tcp.calculateChecksum(); 56 | 57 | assert.equal(tcp.checksum, checksum); 58 | assert.equal(Buffer.compare(tcp.buffer, buffer), 0); 59 | 60 | tcp.flags.syn = 1; 61 | 62 | assert.equal(new TCP(tcp.buffer).flags.syn, 1); 63 | assert.deepEqual(new TCP(tcp.toObject()).toObject(), tcp.toObject()); 64 | assert.deepEqual(new TCP(tcp.toObject()).buffer, tcp.buffer); 65 | }); 66 | -------------------------------------------------------------------------------- /test/TLV.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { TLV_8, TLVIterator, TLVSerialize, TLVPadding_8 } = require('#lib/layers/TLV'); 5 | 6 | test('TLV', async (t) => { 7 | const buf = Buffer.from([0x01, 0x04, 0xAA, 0xAA, 0xAA, 0xAA, 0x02, 0x02, 0xBB, 0xBB]); 8 | const tlvOption = new TLV_8(buf); 9 | 10 | assert.deepEqual(tlvOption.toObject(), { type: 1, recLength: 4, value: Buffer.from([0xaa, 0xaa, 0xaa, 0xaa]) }); 11 | assert.equal(tlvOption.length, 6); 12 | 13 | const opts = [...new TLVIterator(TLV_8, buf)]; 14 | 15 | assert.deepEqual(opts, [ 16 | { type: 1, recLength: 4, value: Buffer.from([0xaa, 0xaa, 0xaa, 0xaa]) }, 17 | { type: 2, recLength: 2, value: Buffer.from([0xbb, 0xbb]) } 18 | ]); 19 | 20 | assert.deepEqual(TLVSerialize(TLV_8, TLVPadding_8, opts, { align: 4 }), Buffer.from([0x01, 0x04, 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x02, 0xbb, 0xbb, 0x00, 0x00])) 21 | }); 22 | -------------------------------------------------------------------------------- /test/UDP.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { extendAt, shrinkAt } = require('#lib/buffer'); 5 | const { UDP } = require('#lib/layers/UDP'); 6 | 7 | test('UDP', async (t) => { 8 | 9 | const buf = Buffer.from('01bbfbda001f096845785986219a31c939c38f0093b888f6b9de8ceaa67f61', 'hex'); 10 | const opts = { 11 | shrinkAt(...args) { 12 | return shrinkAt(buf, ...args); 13 | }, 14 | extendAt(...args) { 15 | return extendAt(buf, ...args); 16 | }, 17 | prev: { 18 | src: '188.114.96.7', 19 | dst: '172.20.10.6', 20 | name: 'IPv4', 21 | } 22 | }; 23 | 24 | const udp = new UDP(buf, opts); 25 | 26 | assert.deepEqual(udp.toObject(), { 27 | src: 443, 28 | dst: 64474, 29 | totalLength: 31, 30 | checksum: 0x0968, 31 | }); 32 | 33 | const { buffer, checksum } = udp; 34 | 35 | udp.checksum = 0; 36 | udp.calculateChecksum(); 37 | 38 | assert.equal(udp.checksum, checksum); 39 | assert.equal(Buffer.compare(udp.buffer, buffer), 0); 40 | 41 | assert.deepEqual(new UDP(udp.toObject()).toObject(), udp.toObject()); 42 | //assert.deepEqual(new UDP(udp.toObject()).buffer, udp.buffer); 43 | }); 44 | -------------------------------------------------------------------------------- /test/af.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { defaultFamily, updateDomain } = require('#lib/af'); 5 | const { socket } = require('#lib/bindings'); 6 | 7 | test('Address family', async (t) => { 8 | assert.equal(defaultFamily('192.168.1.1'), socket.AF_INET); 9 | assert.equal(defaultFamily('e61a:6e95:fad2:a2c9:7f09:7ab7:e009:b719'), socket.AF_INET6); 10 | assert.throws(() => defaultFamily('kek')); 11 | 12 | const obj = {}; 13 | updateDomain(obj, '192.168.1.1'); 14 | assert.equal(obj.domain, socket.AF_INET); 15 | }); 16 | -------------------------------------------------------------------------------- /test/arp.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { extendAt, shrinkAt } = require('#lib/buffer'); 5 | const { ARP } = require('#lib/layers/ARP'); 6 | 7 | test('Arp', async (t) => { 8 | const arp = new ARP(Buffer.from('0001080006040001424242424242c0a80182000000000000c0a80102', 'hex')); 9 | assert.deepEqual(arp.toObject(), { 10 | hardwareType: 1, 11 | protocolType: 2048, 12 | hardwareLength: 6, 13 | protocolLength: 4, 14 | opcode: 'who-has', 15 | hardwareSrc: '42:42:42:42:42:42', 16 | protocolSrc: '192.168.1.130', 17 | hardwareDst: '00:00:00:00:00:00', 18 | protocolDst: '192.168.1.2' 19 | }); 20 | 21 | assert.deepEqual(arp.toObject(), new ARP(arp.toObject()).toObject()); 22 | assert.deepEqual(new ARP(arp.toObject()).buffer, arp.buffer); 23 | }); 24 | -------------------------------------------------------------------------------- /test/arpTable.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { getArpTable } = require('#lib/arp'); 5 | 6 | test('getArpTable', async (t) => { 7 | const arp = await getArpTable(); 8 | assert.equal(typeof arp, 'object'); 9 | Object.keys(arp).forEach(iface => { 10 | arp[iface].forEach(rec => { 11 | assert.equal(typeof rec.ipAddr, 'string'); 12 | assert.equal(typeof rec.hwAddr, 'string'); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/bpfFilter.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const defaults = require('#lib/defaults'); 5 | const { Packet } = require('#lib/packet'); 6 | const { BpfFilter } = require('#lib/bpfFilter'); 7 | 8 | const pktBuf = () => 9 | Buffer.from('424242424242424242424242080045000034000040004006a79ac0a80165a5162c06cd8e5debee16992ebea89919801008000d1200000101080a52d3c650dd04cdd6', 'hex'); 10 | 11 | test('BPF filter', async (t) => { 12 | assert.throws(() => new BpfFilter('hello how are you')); 13 | 14 | const pkt = new Packet({ 15 | buffer: pktBuf(), 16 | }); 17 | 18 | console.time('match tcp'); 19 | assert.ok(new BpfFilter('tcp port 52622').match(pkt)); 20 | console.timeEnd('match tcp'); 21 | 22 | console.time('negative match tcp'); 23 | assert.ok(!(new BpfFilter('tcp port 80').match(pkt))); 24 | console.timeEnd('negative match tcp'); 25 | 26 | console.time('match ip'); 27 | assert.ok(new BpfFilter('ip src 192.168.1.101').match(pkt)); 28 | console.timeEnd('match ip'); 29 | 30 | console.time('negative match ip'); 31 | assert.ok(!new BpfFilter('ip src 127.0.0.1').match(pkt)); 32 | console.timeEnd('negative match ip'); 33 | }); 34 | -------------------------------------------------------------------------------- /test/converters.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { inetPton, inetNtop, htonl, ntohl, htons, ntohs } = require('#lib/converters'); 5 | const socket = require('#lib/socket'); 6 | 7 | test('Address family', async (t) => { 8 | const ipv4 = '192.168.1.1'; 9 | assert.equal(Buffer.compare(inetPton(ipv4), Buffer.from([0xc0, 0xa8, 0x01, 0x01])), 0); 10 | assert.equal(inetNtop(inetPton(ipv4)), ipv4); 11 | 12 | const ipv6 = 'e2fd:8a06:db3b:4cd0:a9da:30c7:c5de:9be8'; 13 | assert.equal(Buffer.compare(inetPton(ipv6), Buffer.from([0xe2, 0xfd, 0x8a, 0x06, 0xdb, 0x3b, 0x4c, 0xd0, 0xa9, 0xda, 0x30, 0xc7, 0xc5, 0xde, 0x9b, 0xe8])), 0); 14 | assert.equal(inetNtop(socket.AF_INET6, inetPton(ipv6)), ipv6); 15 | 16 | assert.equal(ntohs(htons(0xAABB)), 0xAABB); 17 | assert.equal(ntohl(htonl(0xAABB)), 0xAABB); 18 | }); 19 | -------------------------------------------------------------------------------- /test/data/example1.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaguue/over-the-wire/2e01afafb6844806b6bae9fd4615969f15f74f8e/test/data/example1.pcap -------------------------------------------------------------------------------- /test/data/example1.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaguue/over-the-wire/2e01afafb6844806b6bae9fd4615969f15f74f8e/test/data/example1.pcapng -------------------------------------------------------------------------------- /test/data/example2.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaguue/over-the-wire/2e01afafb6844806b6bae9fd4615969f15f74f8e/test/data/example2.pcapng -------------------------------------------------------------------------------- /test/liveDevice.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | const os = require('node:os'); 4 | 5 | const { LiveDevice } = require('#lib/liveDevice'); 6 | const { Packet } = require('#lib/packet'); 7 | 8 | test('LiveDevice', (t) => { 9 | const [ifaceName] = Object.entries(os.networkInterfaces()).find(([name, data]) => { 10 | if (data.some(e => e.internal)) return true; 11 | return false; 12 | }) ?? []; 13 | 14 | if (!ifaceName) return; 15 | 16 | try { 17 | const dev = new LiveDevice({ iface: 'anpi1' }); 18 | 19 | dev.on('error', err => { 20 | console.log('caught error', err.message); 21 | }); 22 | 23 | const { iface } = dev; 24 | 25 | console.log('iface', iface); 26 | 27 | assert.ok(iface.hasOwnProperty('name')); 28 | assert.ok(iface.hasOwnProperty('description')); 29 | assert.ok(iface.hasOwnProperty('mac')); 30 | assert.ok(iface.hasOwnProperty('gateway')); 31 | assert.ok(iface.hasOwnProperty('mtu')); 32 | assert.ok(iface.hasOwnProperty('linktype')); 33 | assert.ok(iface.hasOwnProperty('dnsServers')); 34 | assert.ok(iface.hasOwnProperty('addresses')); 35 | 36 | dev.destroy(); 37 | } catch(err) { 38 | console.log('try-catch', err); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /test/packet.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const defaults = require('#lib/defaults'); 5 | const { Packet } = require('#lib/packet'); 6 | 7 | const pktBuf = () => 8 | Buffer.from('424242424242424242424242080045000034000040004006a79ac0a80165a5162c06cd8e5debee16992ebea89919801008000d1200000101080a52d3c650dd04cdd6', 'hex'); 9 | 10 | test('Packet parsing', async (t) => { 11 | const buffer = pktBuf(); 12 | const pkt = new Packet({ buffer, iface: defaults }); 13 | 14 | assert.deepEqual(pkt.toObject(), { 15 | iface: { 16 | snaplen: 65535, 17 | linktype: 1 18 | }, 19 | layers: { 20 | Ethernet: { 21 | dst: '42:42:42:42:42:42', 22 | src: '42:42:42:42:42:42', 23 | type: 2048 24 | }, 25 | IPv4: { 26 | headerLength: 5, 27 | version: 4, 28 | typeOfService: 0, 29 | totalLength: 52, 30 | id: 0, 31 | fragmentOffsetRaw: 64, 32 | timeToLive: 64, 33 | protocol: 6, 34 | checksum: 42906, 35 | src: '192.168.1.101', 36 | dst: '165.22.44.6', 37 | fragmentInfo: { 38 | isFragment: false, 39 | value: 0, 40 | flags: 64 41 | }, 42 | options: [] 43 | }, 44 | TCP: { 45 | src: 52622, 46 | dst: 24043, 47 | seq: 3994458414, 48 | ack: 3198720281, 49 | dataOffset: 8, 50 | windowSize: 2048, 51 | checksum: 3346, 52 | urgentPointer: 0, 53 | flags: { 54 | reserved: 0, 55 | cwr: 0, 56 | ece: 0, 57 | urg: 0, 58 | ack: 1, 59 | psh: 0, 60 | rst: 0, 61 | syn: 0, 62 | fin: 0 63 | }, 64 | options: [ 65 | { type: 1 }, 66 | { type: 1 }, 67 | { 68 | type: 8, 69 | recLength: 10, 70 | value: Buffer.from([82, 211, 198, 80, 221, 4, 205, 214]), 71 | } 72 | ] 73 | } 74 | } 75 | }); 76 | 77 | }); 78 | 79 | test('Packet clone and compare', t => { 80 | const pkt = new Packet({ 81 | buffer: pktBuf(), 82 | iface: defaults, 83 | }); 84 | 85 | assert.ok(pkt.equals(pkt.clone())); 86 | }); 87 | 88 | test('Packet building', t => { 89 | const pkt = new Packet({ iface: { linktype: 1, mtu: 1500 } }) 90 | .Ethernet({ src: '42:42:42:42:42:42', dst: '42:42:42:42:42:42' }) 91 | .IPv4({ dst: '192.168.1.1' }) 92 | .Payload({ data: Buffer.from('kek') }); 93 | 94 | assert.equal(Buffer.compare(pkt.buffer, Buffer.from([0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x08, 0x00, 0x45, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x40, 0xff, 0xb8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x01, 0x6b, 0x65, 0x6b])), 0); 95 | 96 | assert.deepEqual(pkt.toObject()?.layers, { 97 | Ethernet: { dst: '42:42:42:42:42:42', src: '42:42:42:42:42:42', type: 2048 }, 98 | IPv4: { 99 | headerLength: 5, 100 | version: 4, 101 | typeOfService: 0, 102 | totalLength: 23, 103 | id: 0, 104 | fragmentOffsetRaw: 0, 105 | timeToLive: 64, 106 | protocol: 255, 107 | checksum: 47167, 108 | src: '0.0.0.0', 109 | dst: '192.168.1.1', 110 | fragmentInfo: { isFragment: false, value: 0, flags: 0 }, 111 | options: [] 112 | }, 113 | Payload: { data: Buffer.from([0x6b, 0x65, 0x6b]) } 114 | }) 115 | }); 116 | 117 | test('Packet building and cloning', t => { 118 | const pkt = new Packet({ iface: { linktype: 1, mtu: 1500 } }) 119 | .Ethernet({ src: '42:42:42:42:42:42', dst: '42:42:42:42:42:42' }) 120 | .IPv4({ dst: '192.168.1.1' }) 121 | .Payload({ data: Buffer.from('kek') }); 122 | 123 | const newPkt = pkt.clone(); 124 | 125 | assert.deepEqual(pkt.toObject(), newPkt.toObject()); 126 | }) 127 | -------------------------------------------------------------------------------- /test/routing.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { getRoutingTable } = require('#lib/routing'); 5 | 6 | test('getRoutingTable', async (t) => { 7 | const table = await getRoutingTable(); 8 | assert.equal(typeof table, 'object'); 9 | Object.keys(table).forEach(iface => { 10 | table[iface].forEach(rec => { 11 | assert.equal(typeof rec.destination, 'string'); 12 | assert.equal(typeof rec.gateway, 'string'); 13 | assert.equal(typeof rec.mask, 'string'); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/test.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaguue/over-the-wire/2e01afafb6844806b6bae9fd4615969f15f74f8e/test/test.pcapng -------------------------------------------------------------------------------- /test/timestamp.test.js: -------------------------------------------------------------------------------- 1 | const { strict: assert } = require('node:assert'); 2 | const test = require('node:test'); 3 | 4 | const { TimeStamp } = require('#lib/timestamp'); 5 | 6 | const closeEqual = (a, b, threshold = 1e-3) => Math.abs(a - b) < threshold; 7 | 8 | const sleep = (ms = 10) => new Promise((resolve) => setTimeout(resolve, ms)); 9 | 10 | test('TimeStamp', async (t) => { 11 | const ts = new TimeStamp({ s: 1000, ms: 1000, ns: 123 }); 12 | 13 | assert.equal(ts.s, 1001.000000123); 14 | assert.equal(ts.ns, 1001000000123n); 15 | assert.deepEqual(ts.packedIn({ s: true, ms: true }), { s: 1001, ms: 0 }); 16 | assert.deepEqual(ts.packedIn({ s: true, ns: true }), { s: 1001, ns: 123 }); 17 | 18 | const dateNow = Date.now(); 19 | await sleep(); 20 | const now = TimeStamp.now('ns'); 21 | 22 | assert.ok(closeEqual(now.ms / now.s, 1e3)); 23 | assert.ok(closeEqual(Number(now.ns / BigInt(Math.floor(now.ms))), 1e6)); 24 | assert.ok(new Date(TimeStamp.now('ns').ms).getTime() > dateNow); 25 | 26 | assert.equal(typeof new TimeStamp({ ns: BigInt(1e9) }).ns, 'bigint'); 27 | assert.equal(typeof new TimeStamp({ ns: BigInt(1e9) }).s, 'number'); 28 | assert.equal(typeof new TimeStamp({ ns: BigInt(1e9) }).ms, 'number'); 29 | 30 | assert.equal(typeof new TimeStamp().ms, 'number'); 31 | assert.equal(typeof TimeStamp.now('s').ms, 'number'); 32 | 33 | await sleep(); 34 | 35 | assert.ok(now.compare(TimeStamp.now()) < 0); 36 | assert.equal(now.compare(now.clone()), 0); 37 | }); 38 | -------------------------------------------------------------------------------- /test/tshark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | [ -z "$1" ] && src="data/exampl1.pcap" || src=$1 4 | 5 | tshark -r $src -T json | jq | less -r 6 | --------------------------------------------------------------------------------