diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 00579c0..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -updates: - - directory: / - package-ecosystem: "github-actions" - schedule: - interval: daily - - - directory: / - package-ecosystem: npm - schedule: - interval: monthly \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..ccda88e --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,49 @@ +name: Test +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test_linux: + name: Test Linux + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [ 16.x, 18.x, 20.x, latest ] + steps: + - name: Disable sudo PATH replace + run: | + sudo cat /etc/sudoers | grep -v "secure_path=" > /tmp/.sudoers.tmp + cat /tmp/.sudoers.tmp | sudo tee /etc/sudoers + rm /tmp/.sudoers.tmp + + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - uses: actions/setup-go@v4 + with: + go-version-file: addon/userspace/go/go.mod + go-version: ">=1.22.0" + + - name: "Setup zig" + uses: korandoru/setup-zig@v1 + with: + zig-version: "master" + + - name: Install build dependencies + run: sudo apt update && sudo apt install -y build-essential + + - name: Install node dependencies + run: npm install --no-save --no-audit --no-fund --ignore-scripts + + - name: Run tests + run: ./node_modules/.bin/rebory build && sudo node --no-warnings --loader ts-node/esm src/index_test.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index cba81de..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Test -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - test_js: - name: Test Javascript code - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - name: Checkout - - - uses: actions/setup-node@v4 - name: Setup Node.js - with: - node-version: 20.x - - - name: Install dependencies - run: | - export DEBIAN_FRONTEND=noninteractive - sudo apt update && sudo apt install -y "build-essential" - npm install --no-save --ignore-scripts - - - name: Build addon - run: npm run dev - - - name: Run tests - env: - FORCE_COLOR: "true" - run: sudo "$(command -v node)" --no-warnings --loader ts-node/esm src/index_test.ts - - build_addon: - runs-on: ubuntu-latest - needs: test_js - strategy: - matrix: - node_version: [ 16.x, 17.x, 18.x, 19.x, 20.x, 21.x ] - steps: - - uses: actions/checkout@v4 - name: Checkout - - - name: "Setup zig" - uses: korandoru/setup-zig@v1 - with: - zig-version: "0.11.0" - - - uses: actions/setup-node@v4 - name: Setup Node.js - with: - node-version: ${{ matrix.node_version }} - - - name: Install dependencies - run: | - export DEBIAN_FRONTEND=noninteractive - sudo apt update && sudo apt install -y "build-essential" - npm install --no-save --ignore-scripts - - - name: Build addon linux - run: npm run dev -- --target x86_64-linux --target aarch64-linux - - - name: Build addon macos - run: npm run dev -- --target x86_64-macos --target aarch64-macos - - - name: Build addon windows - run: npm run dev -- --target x86_64-windows --target aarch64-windows - - - name: Upload prebuilds interface - uses: actions/upload-artifact@v3 - with: - retention-days: 7 - name: build-${{ matrix.node_version }} - path: "build/*" diff --git a/.gitignore b/.gitignore index dd9fa1d..9532fa5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ node_modules/ .DS_Store *.log +# Addon +addon/userspace/wg-go.* + # Typescript *.tsbuildinfo src/**/*.d.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index c7fb26d..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,21 +0,0 @@ -image: node:lts -test: - stage: test - image: ghcr.io/catthehacker/ubuntu:act-latest - script: - - | - echo "Host arch: $(uname -m)" - export DEBIAN_FRONTEND=noninteractive - sudo apt update - wget -qO- https://deb.nodesource.com/setup_current.x | sudo bash - - packages=( "binutils-multiarch" "wget" "curl" "nodejs" "build-essential" ); - sudo apt install -y ${packages[@]} - - ZIGURL="https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz" - wget -qO- "${ZIGURL}" | sudo tar -xvJ -C /usr/local - sudo mv -v /usr/local/zig-* /usr/local/zig - sudo ln -vs /usr/local/zig/bin/zig /usr/bin/zig - - - npm install --no-save --ignore-scripts - - npm run dev - - sudo node --no-warnings --loader ts-node/esm ./src/index_test.ts diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5f5d11f..c2a9bf3 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,77 +1,16 @@ { - "version": 4, "configurations": [ - { - "name": "Win32", - "cStandard": "c17", - "cppStandard": "c++17", - "intelliSenseMode": "windows-msvc-x64", - "compilerArgs": [ - "-fpermissive", - "-fexceptions", - "-w", - "-fpermissive", - "-fPIC", - "-static" - ], - "defines": [ - "NAPI_DISABLE_CPP_EXCEPTIONS", - "ONSTARTADDON", - "LISTDEV", - "GETCONFIG", - "SETCONFIG", - "DELIFACE" - ], - "includePath": [ - "${env:appdata}/../Local/node-gyp/Cache/18.17.0/include/node", - "${workspaceFolder}/node_modules/node-addon-api", - "${workspaceFolder}/addons/genKey/**", - "${workspaceFolder}/addons/tools/**" - ] - }, - { - "name": "Linux", - "compilerPath": "/usr/bin/clang", - "cStandard": "c17", - "cppStandard": "c++17", - "intelliSenseMode": "linux-clang-x64", - "compilerArgs": [ - "-fpermissive", - "-fexceptions", - "-w", - "-fpermissive", - "-fPIC", - "-static" - ], - "defines": [ - "NAPI_DISABLE_CPP_EXCEPTIONS" - ], - "includePath": [ - "/usr/include/node", - "${workspaceFolder}/node_modules/node-addon-api/**", - "${workspaceFolder}/node_modules/**", - "${workspaceFolder}/addons/genKey/**", - "${workspaceFolder}/addons/tools/**" - ] - }, { "name": "Mac", "includePath": [ - "${workspaceFolder}/node_modules/node-addon-api", "/usr/local/include/node", - "${workspaceFolder}/addons/genKey/**", - "${workspaceFolder}/addons/tools/**" + "/usr/local/lib/zig//libc/include/any-linux-any", + "${workspaceFolder}/node_modules/node-addon-api", + "${workspaceFolder}/**", + "${workspaceFolder}/addon" ], "defines": [ - "NAPI_DISABLE_CPP_EXCEPTIONS" - ], - "compilerArgs": [ - "-fpermissive", - "-fexceptions", - "-w", - "-fpermissive", - "-fPIC", - "-static" + "AF_NETLINK=16" ], "macFrameworkPath": [ "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks" @@ -80,6 +19,28 @@ "cStandard": "c17", "cppStandard": "c++17", "intelliSenseMode": "macos-clang-x64" + }, + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/node_modules/node-addon-api", + "/usr/include/node", + "${workspaceFolder}/**", + "${workspaceFolder}/addon" + ] + }, + { + "name": "Win32", + "includePath": [ + "${env:appdata}/../.cache/rebory/latest/node/include/node", + "${workspaceFolder}/node_modules/node-addon-api", + "${workspaceFolder}/addon" + ], + "defines": [], + "intelliSenseMode": "windows-msvc-x64", + "cStandard": "c17", + "cppStandard": "c++17" } - ] + ], + "version": 4 } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 6da78ac..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug", - "cwd": "${workspaceFolder}", - "program": "node", - "preLaunchTask": { - "type": "npm", - "script": "dev" - }, - "args": [ - "node_modules/.bin/mocha", - "./" - ] - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 977f74e..c84b4a9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,8 +23,99 @@ "PATH": "${workspaceFolder}/node_modules/.bin:${env:PATH}" }, "files.associations": { + "*.dsc": "ini", + "*.gyp": "python", "random": "cpp", "limits": "cpp", - "xstring": "cpp" + "xstring": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "array": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "exception": "cpp", + "initializer_list": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "variant": "cpp", + "vector": "cpp", + "algorithm": "cpp", + "bit": "cpp", + "charconv": "cpp", + "codecvt": "cpp", + "format": "cpp", + "forward_list": "cpp", + "functional": "cpp", + "iomanip": "cpp", + "iterator": "cpp", + "list": "cpp", + "stop_token": "cpp", + "thread": "cpp", + "utility": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xtr1common": "cpp", + "xtree": "cpp", + "xutility": "cpp", + "fstream": "cpp" } } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index fc6adb7..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "npm: build", - "detail": "npm run dev", - "type": "npm", - "script": "dev", - "problemMatcher": [] - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index 3521576..e36f4c1 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,17 @@ -# Wireguard-tools.js +# Wireguard-tools for Nodejs -Efficiently manage your Wireguard interface right from nodejs, no `wg` required. +Manage your Wireguard interfaces directly from Node.js without any wrappers over `wg` or `wg-quick` -other tools are wrappers over `wg`, `wireguard-tools.js` is not like that, it is a `C/C++` addon in which you don't need to have `wg` installed, as this module has full compatibility of its own `wg`. +```js -## CommonJS droping support +``` -With a small disappointment I come to inform you that CommonJS will be ignored in the next updates and will be completely an ESM module, if you don't want to migrate to ESM I recommend staying on version `1.8.1` or even `1.8.3`, which will be the last versions but recent in CommonJS. +## Licences -## Support to: +- `Wireguard-tools.js`: GPL-3.0 -- Userspace [(wireguard-go)](https://git.zx2c4.com/wireguard-go/about/) support. -- Maneger wireguard interface (linux and windows create if not exist's). -- Generate `preshared`, `private` and `public` keys. -- [wg-quick](https://man7.org/linux/man-pages/man8/wg-quick.8.html) file support. -- More info and example check [`wiki`](https://sirherobrine23.org/Wireguard/Wireguard-tools.js/wiki). +### Wireguard -> [!NOTE] -> -> we have pre-copiled files for: -> - `Linux`: x64/amd64, arm64/aarch64 -> - `Windows`: x64, arm64 -> -> 1. To manage the Wireguard interfaces in linux, root access is required. -> 1. Another system's require `wireguard-go` [(check this page)](https://github.com/WireGuard/wireguard-go) \ No newline at end of file +- `Embeddable-wg-library`: LGPL-2.1+. +- `Wireguard-nt`: GPL-2.0 +- `Wireguard-go`: MIT \ No newline at end of file diff --git a/addons/genKey/wgkeys.cpp b/addon/genKey/wgkeys.cpp similarity index 58% rename from addons/genKey/wgkeys.cpp rename to addon/genKey/wgkeys.cpp index ff13c38..de20d61 100644 --- a/addons/genKey/wgkeys.cpp +++ b/addon/genKey/wgkeys.cpp @@ -1,8 +1,6 @@ #include "wgkeys.hh" #include -#include #include -#include #include #include @@ -15,20 +13,23 @@ static void encode_base64(char dest[4], const uint8_t src[3]) { const uint8_t input[] = { - static_cast((src[0] >> 2) & 63), - static_cast(((src[0] << 4) | (src[1] >> 4)) & 63), - static_cast(((src[1] << 2) | (src[2] >> 6)) & 63), - static_cast(src[2] & 63) - }; + static_cast((src[0] >> 2) & 63), + static_cast(((src[0] << 4) | (src[1] >> 4)) & 63), + static_cast(((src[1] << 2) | (src[2] >> 6)) & 63), + static_cast(src[2] & 63)}; unsigned int i; - for (i = 0; i < 4; ++i) dest[i] = input[i] + 'A' + (((25 - input[i]) >> 8) & 6) - (((51 - input[i]) >> 8) & 75) - (((61 - input[i]) >> 8) & 15) + (((62 - input[i]) >> 8) & 3); + for (i = 0; i < 4; ++i) + dest[i] = input[i] + 'A' + (((25 - input[i]) >> 8) & 6) - + (((51 - input[i]) >> 8) & 75) - (((61 - input[i]) >> 8) & 15) + + (((62 - input[i]) >> 8) & 3); } void keyToBase64(wg_key_b64_string base64, const wg_key key) { unsigned int i; - for (i = 0; i < 32 / 3; ++i) encode_base64(&base64[i * 4], &key[i * 3]); - const uint8_t tempKey[3] = { key[i * 3 + 0], key[i * 3 + 1], 0 }; + for (i = 0; i < 32 / 3; ++i) + encode_base64(&base64[i * 4], &key[i * 3]); + const uint8_t tempKey[3] = {key[i * 3 + 0], key[i * 3 + 1], 0}; encode_base64(&base64[i * 4], tempKey); base64[sizeof(wg_key_b64_string) - 2] = '='; base64[sizeof(wg_key_b64_string) - 1] = '\0'; @@ -38,19 +39,28 @@ static int decodeBase64(const char src[4]) { int val = 0; unsigned int i; - for (i = 0; i < 4; ++i) val |= (-1 + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64)) + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70)) + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5)) + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63) + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)) << (18 - 6 * i); + for (i = 0; i < 4; ++i) + val |= + (-1 + + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & + (src[i] - 64)) + + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & + (src[i] - 70)) + + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5)) + + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63) + + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)) + << (18 - 6 * i); return val; } #ifdef _WIN32 -static volatile void * (*memset_func)(void *, int, size_t) = (volatile void * (*)(void *, int, size_t))&memset; -void memzero_explicit(void *s, size_t count) { - memset_func(s, 0, count); -} +static volatile void *(*memset_func)(void *, int, size_t) = + (volatile void *(*)(void *, int, size_t)) & memset; +void memzero_explicit(void *s, size_t count) { memset_func(s, 0, count); } #else static __attribute__((noinline)) void memzero_explicit(void *s, size_t count) { memset(s, 0, count); - __asm__ __volatile__("": :"r"(s) :"memory"); + __asm__ __volatile__("" : : "r"(s) : "memory"); } #endif @@ -109,21 +119,25 @@ static void pack(uint8_t *o, const fe n) { static void add(fe o, const fe a, const fe b) { int i; - for (i = 0; i < 16; ++i) o[i] = a[i] + b[i]; + for (i = 0; i < 16; ++i) + o[i] = a[i] + b[i]; } static void subtract(fe o, const fe a, const fe b) { int i; - for (i = 0; i < 16; ++i) o[i] = a[i] - b[i]; + for (i = 0; i < 16; ++i) + o[i] = a[i] - b[i]; } static void multmod(fe o, const fe a, const fe b) { int i, j; - int64_t t[31] = { 0 }; + int64_t t[31] = {0}; for (i = 0; i < 16; ++i) { - for (j = 0; j < 16; ++j) t[i + j] += a[i] * b[j]; + for (j = 0; j < 16; ++j) + t[i + j] += a[i] * b[j]; } - for (i = 0; i < 15; ++i) t[i] += 38 * t[i + 16]; + for (i = 0; i < 15; ++i) + t[i] += 38 * t[i + 16]; memcpy(o, t, sizeof(fe)); carry(o); carry(o); @@ -136,38 +150,50 @@ static void invert(fe o, const fe i) { memcpy(c, i, sizeof(c)); for (a = 253; a >= 0; --a) { multmod(c, c, c); - if (a != 2 && a != 4) multmod(c, c, i); + if (a != 2 && a != 4) + multmod(c, c, i); } memcpy(o, c, sizeof(fe)); memzero_explicit(c, sizeof(c)); } void wgKeys::generatePreshared(wg_key preshared_key) { - #if _WIN32 || defined(__CYGWIN__) +#if _WIN32 || defined(__CYGWIN__) HCRYPTPROV hCryptProv; BOOL winStatus; - if ((winStatus = CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))) winStatus = CryptGenRandom(hCryptProv, sizeof(wg_key), (BYTE*)preshared_key); + if ((winStatus = CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT))) + winStatus = + CryptGenRandom(hCryptProv, sizeof(wg_key), (BYTE *)preshared_key); CryptReleaseContext(hCryptProv, 0); - if (winStatus) return; + if (winStatus) + return; - #elif defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) - if (!getentropy(preshared_key, sizeof(wg_key))) return; +#elif defined(__OpenBSD__) || \ + (defined(__APPLE__) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || \ + (defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) + if (!getentropy(preshared_key, sizeof(wg_key))) + return; - #elif defined(__NR_getrandom) && defined(__linux__) - if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key)) return; +#elif defined(__NR_getrandom) && defined(__linux__) + if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == + sizeof(wg_key)) + return; - #elif __linux__ || _ANDROID__ || __termux__ +#elif __linux__ || _ANDROID__ || __termux__ size_t ret, i; int fd; fd = open("/dev/urandom", O_RDONLY); - assert(fd >= 0); + if (fd <= 0); for (i = 0; i < sizeof(wg_key); i += ret) { ret = read(fd, preshared_key + i, sizeof(wg_key) - i); - assert(ret > 0); + if (ret < 0); } close(fd); return; - #endif +#endif std::random_device rd; for (uint8_t i = 0; i < sizeof(wg_key); ++i) { @@ -191,7 +217,7 @@ void wgKeys::generatePrivate(wg_key private_key) { void wgKeys::generatePublic(wg_key public_key, const wg_key private_key) { int i, r; uint8_t z[32]; - fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f; + fe a = {1}, b = {9}, c = {0}, d = {1}, e, f; memcpy(z, private_key, sizeof(z)); clamp_key(z); @@ -212,12 +238,12 @@ void wgKeys::generatePublic(wg_key public_key, const wg_key private_key) { subtract(a, a, c); multmod(b, a, a); subtract(c, d, f); - const fe abc = { 0xdb41, 1 }; + const fe abc = {0xdb41, 1}; multmod(a, c, abc); add(a, a, d); multmod(c, c, a); multmod(a, d, f); - const fe abc2 = { 9 }; + const fe abc2 = {9}; multmod(d, b, abc2); multmod(b, e, e); cswap(a, b, r); @@ -237,8 +263,15 @@ void wgKeys::generatePublic(wg_key public_key, const wg_key private_key) { memzero_explicit(f, sizeof(f)); } -bool key_is_zero(const uint8_t key[32]) -{ +std::string wgKeys::generatePublic(const std::string private_key) { + wg_key public_key; + wg_key private_key_; + stringToKey(private_key_, private_key); + generatePublic(public_key, private_key_); + return toString(public_key); +} + +bool key_is_zero(const uint8_t key[32]) { volatile uint8_t acc = 0; for (unsigned int i = 0; i < 32; ++i) { @@ -250,7 +283,10 @@ bool key_is_zero(const uint8_t key[32]) void wgKeys::stringToKey(wg_key key, std::string keyBase64) { auto base64 = keyBase64.c_str(); - if (keyBase64.length() != B64_WG_KEY_LENGTH || base64[B64_WG_KEY_LENGTH - 1] != '=') throw std::string("invalid key, length: ").append(std::to_string(keyBase64.length())); + if (keyBase64.length() != B64_WG_KEY_LENGTH || + base64[B64_WG_KEY_LENGTH - 1] != '=') + throw std::string("invalid key, length: ") + .append(std::to_string(keyBase64.length())); unsigned int i; int val; @@ -263,24 +299,43 @@ void wgKeys::stringToKey(wg_key key, std::string keyBase64) { key[i * 3 + 1] = (val >> 8) & 0xff; key[i * 3 + 2] = val & 0xff; } - const char tempDecode[4] = {base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A'}; + const char tempDecode[4] = {base64[i * 4 + 0], base64[i * 4 + 1], + base64[i * 4 + 2], 'A'}; val = decodeBase64(tempDecode); ret |= ((uint32_t)val >> 31) | (val & 0xff); key[i * 3 + 0] = (val >> 16) & 0xff; key[i * 3 + 1] = (val >> 8) & 0xff; int status = EINVAL & ~((ret - 1) >> 8); - if (status != 0) throw std::string("Cannot decode key, ret code: ").append(std::to_string(status)); + if (status != 0) + throw std::string("Cannot decode key, ret code: ") + .append(std::to_string(status)); } std::string wgKeys::toString(const wg_key key) { wg_key_b64_string base64; unsigned int i; - for (i = 0; i < 32 / 3; ++i) encode_base64(&base64[i * 4], &key[i * 3]); - const uint8_t tempKey[3] = { key[i * 3 + 0], key[i * 3 + 1], 0 }; + for (i = 0; i < 32 / 3; ++i) + encode_base64(&base64[i * 4], &key[i * 3]); + const uint8_t tempKey[3] = {key[i * 3 + 0], key[i * 3 + 1], 0}; encode_base64(&base64[i * 4], tempKey); base64[sizeof(wg_key_b64_string) - 2] = '='; base64[sizeof(wg_key_b64_string) - 1] = '\0'; return std::string(base64); +} + +std::string wgKeys::toHex(const std::string keyBase64) { + wg_key key; + wgKeys::stringToKey(key, keyBase64); + char hex[65]; + for (int i = 0; i < 32; ++i) sprintf(hex + i * 2, "%02x", key[i]); + hex[64] = '\0'; + return std::string(hex); +} + +std::string wgKeys::HextoBase64(const std::string keyHex) { + wg_key key; + for (int i = 0; i < 32; ++i) sscanf(keyHex.c_str() + i * 2, "%02x", &key[i]); + return wgKeys::toString(key); } \ No newline at end of file diff --git a/addon/genKey/wgkeys.hh b/addon/genKey/wgkeys.hh new file mode 100644 index 0000000..7422c83 --- /dev/null +++ b/addon/genKey/wgkeys.hh @@ -0,0 +1,37 @@ +#ifndef _WGKEY_ +#define _WGKEY_ +#include + +const int WG_KEY_LENGTH = 32, B64_WG_KEY_LENGTH = ((WG_KEY_LENGTH + 2) / 3) * 4; +const int wgKeyLength = 32, Base64WgKeyLength = ((wgKeyLength + 2) / 3) * 4, HexWgKeyLength = wgKeyLength * 2; + +typedef unsigned char wg_key[wgKeyLength]; +typedef char wg_key_b64_string[Base64WgKeyLength + 1]; +typedef long long fe[16]; + +namespace wgKeys { + // Convert wg_key to std::string in base64 + std::string toString(const wg_key key); + + // Convert base64 to hex key + std::string toHex(const std::string keyBase64); + + // Convert hex to base64 + std::string HextoBase64(const std::string keyHex); + + // Convert base64 to wg_key + void stringToKey(wg_key key, std::string keyBase64); + + // Generate preshared key + void generatePreshared(wg_key key); + + // Generate private key + void generatePrivate(wg_key private_key); + + // Get public key from private key + void generatePublic(wg_key public_key, const wg_key private_key); + + std::string generatePublic(const std::string private_key); +} + +#endif \ No newline at end of file diff --git a/addon/linux/wginterface.cpp b/addon/linux/wginterface.cpp new file mode 100644 index 0000000..384adc7 --- /dev/null +++ b/addon/linux/wginterface.cpp @@ -0,0 +1,437 @@ +#include "wginterface.hh" +#include "genKey/wgkeys.hh" +extern "C" { +#include "wireguard.h" +} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::string getWireguardVersion() { + #ifdef WIREGUARD_VERSION + return std::string(WIREGUARD_VERSION); + #endif + + std::string version = "Unknown"; + + // /sys/module/wireguard/version - for kernel module + if (std::filesystem::exists("/sys/module/wireguard/version")) { + std::ifstream file("/sys/module/wireguard/version"); + file >> version; + file.close(); + } + + return version; +} + +std::string driveLoad(std::map load) {} + +void WireguardDevices::getInterfaces() { + size_t len; char *device_name, *devicesList = wg_list_device_names(); + + if (!devicesList) throw std::string("Unable to get device names"); + + // Clear list + this->clear(); + + // Set new devices + for (device_name = devicesList, len = 0; (len = strlen(device_name)); device_name += len + 1) this->push_back(std::string(device_name)); + + // Free memory + free(devicesList); +} + +void WireguardDevices::deleteInterface(std::string wgName) { + // Check if exist, if not skip + if (this->exist(wgName)) { + int status = wg_del_device(wgName.c_str()); + if (status < 0) throw std::string("Cannot delete interface, code: ").append(std::to_string(status)); + } +} + +std::string HostAdresses(bool addPort, const sockaddr* addr) { + char host[4096 + 1], service[512 + 1]; + static char buf[sizeof(host) + sizeof(service) + 4]; + memset(buf, 0, sizeof(buf)); + int ret; + socklen_t addr_len = 0; + if (addr->sa_family == AF_INET) addr_len = sizeof(struct sockaddr_in); + else if (addr->sa_family == AF_INET6) addr_len = sizeof(struct sockaddr_in6); + + ret = getnameinfo(addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST); + if (ret) { + strncpy(buf, gai_strerror(ret), sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + } else { + if (addPort) snprintf(buf, sizeof(buf), (addr->sa_family == AF_INET6 && strchr(host, ':')) ? "[%s]:%s" : "%s:%s", host, service); + else snprintf(buf, sizeof(buf), "%s", host); + } + return std::string(buf); +}; + +void WireguardConfig::getWireguardConfig() { + if (this->name.length() == 0) throw std::string("Set wireguard name!"); + else if (this->name.length() > IFNAMSIZ) throw std::string("Wireguard interface name is long, max name length is ").append(std::to_string(IFNAMSIZ)); + else if (!(WireguardDevices().exist(this->name))) throw std::string("Wireguard interface not exist"); + int status; wg_device *devConfig; wg_peer *peer; + if ((status = wg_get_device(&devConfig, this->name.c_str())) < 0) throw std::string("It was not possible to get the Wireguard interface settings, code: ").append(std::to_string(status)); + if (devConfig->flags & WGDEVICE_HAS_PRIVATE_KEY) this->privateKey = wgKeys::toString(devConfig->private_key); + if (devConfig->flags & WGDEVICE_HAS_PUBLIC_KEY) this->publicKey = wgKeys::toString(devConfig->public_key); + if (!!devConfig->listen_port && devConfig->listen_port > 0) this->portListen = devConfig->listen_port; + this->interfaceAddress.GetInInterface(this->name); + + for ((peer) = (devConfig)->first_peer; (peer); (peer) = (peer)->next_peer) { + auto PeerConfig = Peer(); + if (peer->flags & WGPEER_HAS_PRESHARED_KEY) PeerConfig.presharedKey = wgKeys::toString(peer->preshared_key); + if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) PeerConfig.keepInterval = peer->persistent_keepalive_interval; + if (peer->endpoint.addr.sa_family == AF_INET||peer->endpoint.addr.sa_family == AF_INET6) PeerConfig.endpoint = HostAdresses(true, &peer->endpoint.addr); + + PeerConfig.lastHandshake = peer->last_handshake_time.tv_sec*1000; + PeerConfig.rxBytes = peer->rx_bytes; + PeerConfig.txBytes = peer->tx_bytes; + + wg_allowedip *allowedip; + for ((allowedip) = (peer)->first_allowedip; (allowedip); (allowedip) = (allowedip)->next_allowedip) { + static char buf[INET6_ADDRSTRLEN + 1]; + memset(buf, 0, INET6_ADDRSTRLEN + 1); + if (allowedip->family == AF_INET) inet_ntop(AF_INET, &allowedip->ip4, buf, INET6_ADDRSTRLEN); + else if (allowedip->family == AF_INET6) inet_ntop(AF_INET6, &allowedip->ip6, buf, INET6_ADDRSTRLEN); + PeerConfig.allowedIPs.push_back(std::string(buf).append("/").append(std::to_string(allowedip->cidr))); + } + this->Peers[wgKeys::toString(peer->public_key)] = PeerConfig; + } +} + +void WireguardConfig::setWireguardConfig() { + int status; + if (this->name.length() > IFNAMSIZ) throw std::string("Wireguard interface name is long, max name length is ").append(std::to_string(IFNAMSIZ)); + else if (!(WireguardDevices().exist(this->name)) && (status = wg_add_device(this->name.c_str())) < 0) throw std::string("Unable to create Wireguard interface, code: ").append(std::to_string(status)); + if (this->privateKey.length() != Base64WgKeyLength) throw std::string("Set Wireguard interface private key!"); + + auto wgConfig = new wg_device({}); + if (!wgConfig) throw std::string("Cannot alloc memory to set interface configuration!"); + strncpy(wgConfig->name, this->name.c_str(), this->name.length()); + + wgConfig->flags = wg_device_flags::WGDEVICE_HAS_PRIVATE_KEY; + wgKeys::stringToKey(wgConfig->private_key, this->privateKey); + + if (this->replacePeers) wgConfig->flags = (wg_device_flags)(wgConfig->flags|wg_device_flags::WGDEVICE_REPLACE_PEERS); + + if (this->publicKey.length() == Base64WgKeyLength) { + wgConfig->flags = (wg_device_flags)(wgConfig->flags|wg_device_flags::WGDEVICE_HAS_PUBLIC_KEY); + wgKeys::stringToKey(wgConfig->public_key, this->publicKey); + } + + if (this->portListen >= 0) { + wgConfig->flags = (wg_device_flags)(wgConfig->flags|wg_device_flags::WGDEVICE_HAS_LISTEN_PORT); + wgConfig->listen_port = this->portListen; + } + + if (this->fwmark >= 0) { + wgConfig->flags = (wg_device_flags)(wgConfig->flags|wg_device_flags::WGDEVICE_HAS_FWMARK); + wgConfig->fwmark = this->fwmark; + } + + for (auto &PeerConfig : this->Peers) { + auto peer = new wg_peer({}); + peer->flags = wg_peer_flags::WGPEER_HAS_PUBLIC_KEY; + wgKeys::stringToKey(peer->public_key, PeerConfig.first); + if (PeerConfig.second.removeMe) peer->flags = (wg_peer_flags)(peer->flags|wg_peer_flags::WGPEER_REMOVE_ME); + else { + if (PeerConfig.second.presharedKey.length() == Base64WgKeyLength) { + peer->flags = (wg_peer_flags)(peer->flags|wg_peer_flags::WGPEER_HAS_PRESHARED_KEY); + wgKeys::stringToKey(peer->preshared_key, PeerConfig.second.presharedKey); + } + + if (PeerConfig.second.keepInterval > 0) { + peer->flags = (wg_peer_flags)(peer->flags|wg_peer_flags::WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL); + peer->persistent_keepalive_interval = PeerConfig.second.keepInterval; + } + + if (PeerConfig.second.endpoint.length() > 0) { + sockaddr endpoint; int ret, retries; + char *begin, *end, *Endpoint = strdup(PeerConfig.second.endpoint.c_str()); + if (Endpoint[0] == '[') { + begin = &Endpoint[1]; + end = strchr(Endpoint, ']'); + if (!end) { + for ((peer) = (wgConfig)->first_peer; (peer); (peer) = (peer)->next_peer) { delete peer; } + delete wgConfig; + free(Endpoint); + throw std::string("Unable to find matching brace of endpoint"); + return; + } + *end++ = '\0'; + if (*end++ != ':' || !*end) { + for ((peer) = (wgConfig)->first_peer; (peer); (peer) = (peer)->next_peer) { delete peer; } + delete wgConfig; + free(Endpoint); + throw std::string("Unable to find port of endpoint"); + return; + } + } else { + begin = Endpoint; + end = strrchr(Endpoint, ':'); + if (!end || !*(end + 1)) { + for ((peer) = (wgConfig)->first_peer; (peer); (peer) = (peer)->next_peer) { delete peer; } + delete wgConfig; + free(Endpoint); + throw std::string("Unable to find port of endpoint"); + } + *end++ = '\0'; + } + addrinfo *resolved, hints = { ai_family: AF_UNSPEC, ai_socktype: SOCK_DGRAM, ai_protocol: IPPROTO_UDP }; + for (unsigned int timeout = 1000000;; timeout = ((20000000) < (timeout * 6 / 5) ? (20000000) : (timeout * 6 / 5))) { + ret = getaddrinfo(begin, end, &hints, &resolved); + if (!ret) break; + if (ret == EAI_NONAME || ret == EAI_FAIL || + #ifdef EAI_NODATA + ret == EAI_NODATA || + #endif + (retries >= 0 && !retries--)) { + for ((peer) = (wgConfig)->first_peer; (peer); (peer) = (peer)->next_peer) { delete peer; } + delete wgConfig; + free(Endpoint); + fprintf(stderr, "%s: `%s'\n", ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret), PeerConfig.second.endpoint.c_str()); + throw std::string("Unable to resolve endpoint"); + return; + } + fprintf(stderr, "%s: `%s'. Trying again in %.2f seconds...\n", ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret), PeerConfig.second.endpoint.c_str(), timeout / 1000000.0); + usleep(timeout); + } + if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(sockaddr_in)) || (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(sockaddr_in6))) { + memcpy(&endpoint, resolved->ai_addr, resolved->ai_addrlen); + memccpy(&peer->endpoint.addr, &endpoint, 0, sizeof(peer->endpoint.addr)); + if (resolved->ai_family == AF_INET) { + peer->endpoint.addr4.sin_addr.s_addr = ((sockaddr_in *)&endpoint)->sin_addr.s_addr; + peer->endpoint.addr4.sin_port = ((sockaddr_in *)&endpoint)->sin_port; + peer->endpoint.addr4.sin_family = AF_INET; + } else { + peer->endpoint.addr6.sin6_addr = ((struct sockaddr_in6 *)&endpoint)->sin6_addr; + peer->endpoint.addr6.sin6_port = ((struct sockaddr_in6 *)&endpoint)->sin6_port; + peer->endpoint.addr6.sin6_family = AF_INET6; + } + } else { + freeaddrinfo(resolved); + for ((peer) = (wgConfig)->first_peer; (peer); (peer) = (peer)->next_peer) { delete peer; } + delete wgConfig; + free(Endpoint); + throw std::string("Neither IPv4 nor IPv6 address found"); + } + + freeaddrinfo(resolved); + // Free memory + for ((peer) = (wgConfig)->first_peer; (peer); (peer) = (peer)->next_peer) { delete peer; } + delete wgConfig; + free(Endpoint); + } + + for (const auto Ip : PeerConfig.second.allowedIPs.getIpParsed()) { + if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS) peer->flags = (wg_peer_flags)(peer->flags|WGPEER_REPLACE_ALLOWEDIPS); + auto newAllowedIP = new wg_allowedip({}); + newAllowedIP->cidr = Ip.Mask; + if (Ip.Proto == 6 && inet_pton(AF_INET6, Ip.Address.c_str(), &newAllowedIP->ip6) == 1) newAllowedIP->family = AF_INET6; + else if (Ip.Proto == 4 && inet_pton(AF_INET, Ip.Address.c_str(), &newAllowedIP->ip4) == 1) newAllowedIP->family = AF_INET; + else { + delete newAllowedIP; + continue; + } + + if (peer->first_allowedip) newAllowedIP->next_allowedip = peer->first_allowedip; + peer->first_allowedip = newAllowedIP; + } + } + + if (wgConfig->first_peer) peer->next_peer = wgConfig->first_peer; + wgConfig->first_peer = peer; + } + + // Set config + status = wg_set_device(wgConfig); + + // Free memory + for (wg_peer* peer = wgConfig->first_peer; peer; peer = peer->next_peer) { + for (wg_allowedip *newAllowedIP = peer->first_allowedip; newAllowedIP; newAllowedIP = newAllowedIP->next_allowedip) delete newAllowedIP; + delete peer; + } + delete wgConfig; + + // Return status to tool + if (status < 0) throw std::string("Unable to configure settings, code: ").append(std::to_string(status)); + else { + this->interfaceAddress.SetInInterface(this->name); + } +} + +void IpManeger::GetInInterface(std::string interfaceName) { + ifaddrs* ptr_ifaddrs = nullptr; + if(getifaddrs(&ptr_ifaddrs) >= 0) { + for (ifaddrs* ptr_entry = ptr_ifaddrs; ptr_entry != nullptr; ptr_entry = ptr_entry->ifa_next) { + if (ptr_entry->ifa_addr == nullptr) continue; + else if (strcmp(ptr_entry->ifa_name, interfaceName.c_str()) != 0) continue; + else if (ptr_entry->ifa_addr->sa_family == AF_INET) this->addIPMask(HostAdresses(false, ptr_entry->ifa_addr)); + else if (ptr_entry->ifa_addr->sa_family == AF_INET6) this->addIPMask(HostAdresses(false, ptr_entry->ifa_addr)); + } + freeifaddrs(ptr_ifaddrs); + } +} + +struct rtnl_handle { + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +typedef struct { + __u8 family; + __u8 bytelen; + __s16 bitlen; + __u32 flags; + __u32 data[8]; +} inet_prefix; + +// This function is to open the netlink socket as the name suggests. +void netlink_open(struct rtnl_handle* rth) { + int addr_len; + memset(rth, 0, sizeof(rth)); + + // Creating the netlink socket of family NETLINK_ROUTE + + rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (rth->fd < 0) throw std::string("cannot open netlink socket"); + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = 0; + + // Binding the netlink socket + if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) throw std::string("cannot bind netlink socket"); + + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, (socklen_t*) &addr_len) < 0) throw std::string("cannot getsockname"); + if (addr_len != sizeof(rth->local)) throw std::string("wrong address lenght").append(std::to_string(addr_len)); + if (rth->local.nl_family != AF_NETLINK) throw std::string("wrong address family").append(std::to_string(rth->local.nl_family)); + + rth->seq = time(NULL); +} + +// This function does the actual reading and writing to the netlink socket +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, unsigned groups, struct nlmsghdr *answer) { + int status; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + // Forming the iovector with the netlink packet. + struct iovec iov = { (void*)n, n->nlmsg_len }; + char buf[8192]; + // Forming the message to be sent. + struct msghdr msg = { (void*)&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 }; + // Filling up the details of the netlink socket to be contacted in the + // kernel. + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + n->nlmsg_seq = ++rtnl->seq; + if (answer == NULL) n->nlmsg_flags |= NLM_F_ACK; + // Actual sending of the message, status contains success/failure + return sendmsg(rtnl->fd, &msg, 0); + // if (status < 0) return -1; +} + +// This is the utility function for adding the parameters to the packet. +int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) { + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) + return -1; + rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +void IpManeger::SetInInterface(std::string interfaceName) { + if (this->size() == 0) return; + if (!(WireguardDevices().exist(interfaceName))) throw std::string("Wireguard interface not exists!"); + int status; + unsigned int ifa_index; + wg_device *devConfig; + + if ((status = wg_get_device(&devConfig, interfaceName.c_str())) < 0) throw std::string("It was not possible to get the Wireguard interface settings, code: ").append(std::to_string(status)); + + ifa_index = devConfig->ifindex; + free(devConfig); + + for (const auto ip : this->getIpParsed()) { + struct rtnl_handle * rth; + rth = (rtnl_handle*)malloc(sizeof(rtnl_handle)); + netlink_open(rth); + int err; + inet_prefix lcl; + + // structure of the netlink packet. + struct { + struct nlmsghdr n; + struct ifaddrmsg ifa; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.n.nlmsg_type = RTM_NEWADDR; + req.n.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST; + + req.ifa.ifa_family = ip.Proto == 4 ? AF_INET : AF_INET6; + req.ifa.ifa_prefixlen = ip.Mask; + req.ifa.ifa_index = ifa_index ; // get the loopback index + req.ifa.ifa_scope = 0; + + memset(&lcl, 0, sizeof(lcl)); + lcl.family = req.ifa.ifa_family; + lcl.bytelen = (req.ifa.ifa_family == AF_INET) ? 4 : 16; + lcl.bitlen = (req.ifa.ifa_family == AF_INET) ? 32 : 128; + + if (inet_pton(req.ifa.ifa_family, ip.Address.c_str(), &lcl.data) <= 0) throw std::string("Invalid IP address: ").append(ip.Address); + if (req.ifa.ifa_family == AF_UNSPEC) req.ifa.ifa_family = lcl.family; + + addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen); + if ((err = rtnl_talk(rth, &req.n, 0, 0, NULL)) < 0) throw std::string("Cannot set interface IP, code: ").append(std::to_string(err)); + } + + // Get INET socket + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) throw std::string("Error creating socket to set interface flags"); + + struct ifreq ifr; + strcpy(ifr.ifr_name, interfaceName.c_str()); + + // Set interface flags + ifr.ifr_flags = IFF_POINTOPOINT | IFF_NOARP | IFF_UP | IFF_RUNNING; + if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) { + close(sockfd); + throw std::string("Error setting interface flags"); + } + + close(sockfd); +} \ No newline at end of file diff --git a/addons/tools/linux/wireguard.c b/addon/linux/wireguard.c similarity index 100% rename from addons/tools/linux/wireguard.c rename to addon/linux/wireguard.c diff --git a/addons/tools/linux/wireguard.h b/addon/linux/wireguard.h similarity index 100% rename from addons/tools/linux/wireguard.h rename to addon/linux/wireguard.h diff --git a/addon/main.cpp b/addon/main.cpp new file mode 100644 index 0000000..107051f --- /dev/null +++ b/addon/main.cpp @@ -0,0 +1,83 @@ +#include "wg.hh" +#include + +Napi::Object StartAddon(const Napi::Env env, const Napi::Object exports) { + std::map LoadConfig; + const Napi::Array keysOnLoadsNames = exports.GetPropertyNames(); + for (unsigned int keyIndex = 0; keyIndex < keysOnLoadsNames.Length(); keyIndex++) { + if (!(exports.Get(keysOnLoadsNames[keyIndex]).IsString())) continue; + LoadConfig[keysOnLoadsNames[keyIndex].ToString().Utf8Value()] = exports.Get(keysOnLoadsNames[keyIndex]).ToString().Utf8Value(); + } + + #ifdef _WIN32 + std::string statusLoading = driveLoad(LoadConfig); + if (!!(statusLoading.length())) { + Napi::Error::New(env, statusLoading).ThrowAsJavaScriptException(); + return exports; + } + #endif + + const Napi::Object Constants = Napi::Object::New(env); + if (getWireguardVersion().length() > 0) Constants.Set("driveVersion", getWireguardVersion()); + + exports.Set("deleteInterface", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value { + const Napi::Env env = info.Env(); + if (!(info[0].IsString())) { + Napi::Error::New(env, "Set interface name").ThrowAsJavaScriptException(); + return env.Undefined(); + } + try { + DeleteInterface *worker = new DeleteInterface(env, info[0].ToString()); + worker->Queue(); + return worker->NodePromise.Promise(); + } catch (std::string &err) { + Napi::Error::New(env, err).ThrowAsJavaScriptException(); + return env.Undefined(); + } + })); + + exports.Set("listDevices", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value { + const Napi::Env env = info.Env(); + try { + ListDevices *worker = new ListDevices(env); + worker->Queue(); + return worker->NodePromise.Promise(); + } catch (std::string &err) { + Napi::Error::New(env, err).ThrowAsJavaScriptException(); + return env.Undefined(); + } + })); + + exports.Set("setConfig", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value { + const Napi::Env env = info.Env(); + if (!(info[0].IsObject())) Napi::Error::New(env, "Set wireguard config!").ThrowAsJavaScriptException(); + try { + SetConfig *worker = new SetConfig(env, info[0].ToObject()); + worker->Queue(); + return worker->NodePromise.Promise(); + } catch (std::string &err) { + Napi::Error::New(env, err).ThrowAsJavaScriptException(); + } + return env.Undefined(); + })); + + exports.Set("getConfig", Napi::Function::New(env, [](const Napi::CallbackInfo &info) -> Napi::Value { + const Napi::Env env = info.Env(); + if (!(info[0].IsString())) { + Napi::Error::New(env, "Set interface name in fist argument!").ThrowAsJavaScriptException(); + return env.Undefined(); + } + try { + GetConfig *worker = new GetConfig(env, info[0].ToString()); + worker->Queue(); + return worker->NodePromise.Promise(); + } catch (std::string &err) { + Napi::Error::New(env, err).ThrowAsJavaScriptException(); + return env.Undefined(); + } + })); + + exports.Set("constants", Constants); + return exports; +} +NODE_API_MODULE(addon, StartAddon); diff --git a/addon/userspace/go/go.mod b/addon/userspace/go/go.mod new file mode 100644 index 0000000..daee043 --- /dev/null +++ b/addon/userspace/go/go.mod @@ -0,0 +1,14 @@ +module sirherobrine23.org/Wireguard/wireguard-tools.js/wg-tun + +go 1.21.6 + +require ( + golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 +) + +require ( + golang.org/x/sys v0.12.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect +) diff --git a/addon/userspace/go/go.sum b/addon/userspace/go/go.sum new file mode 100644 index 0000000..91d1675 --- /dev/null +++ b/addon/userspace/go/go.sum @@ -0,0 +1,16 @@ +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= diff --git a/addon/userspace/go/main.go b/addon/userspace/go/main.go new file mode 100644 index 0000000..bf9848d --- /dev/null +++ b/addon/userspace/go/main.go @@ -0,0 +1,211 @@ +package main + +import "C" + +import ( + "fmt" + "os" + "os/signal" + "runtime/debug" + "strings" + "syscall" + + "golang.zx2c4.com/wireguard/conn" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/ipc" + "golang.zx2c4.com/wireguard/tun" +) + +const levelLog = device.LogLevelError + +// End process function callbacks +var TunsEndProcess = make(map[string]func()) + +//export callEndProcess +func callEndProcess() { + for _, f := range TunsEndProcess { + f() + } +} + +func main() {} +func init() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + callEndProcess() + }() +} + +// Get wireguard-go version +// +//export wgVersion +func wgVersion() *C.char { + info, ok := debug.ReadBuildInfo() + if !ok { + return C.CString("unknown") + } + for _, dep := range info.Deps { + if dep.Path == "golang.zx2c4.com/wireguard" { + return C.CString(dep.Version) + } + } + return C.CString("unknown") +} + +// Check if tunnel exist +// +//export existTun +func existTun(tunName string) bool { + Files, err := os.ReadDir(socketDirectory) + if err != nil { + return false + } + for _, file := range Files { + if file.IsDir() { + continue + } + splits := strings.Split(file.Name(), "/") + splits[len(splits)-1] = strings.TrimSuffix(splits[len(splits)-1], ".sock") + if splits[len(splits)-1] == tunName { + return true + } + } + return false +} + +// Delete wireguard tunnel if exist +// +//export deleteTun +func deleteTun(_tunName *C.char) *C.char { + tunName := C.GoString(_tunName) + if !existTun(tunName) { + return C.CString("Tun does not exist") + } + Files, err := os.ReadDir(socketDirectory) + if err != nil { + return C.CString("Tun does not exist") + } + for _, file := range Files { + if file.IsDir() { + continue + } + splits := strings.Split(file.Name(), "/") + splits[len(splits)-1] = strings.TrimSuffix(splits[len(splits)-1], ".sock") + if splits[len(splits)-1] == tunName { + os.Remove(strings.Join(([]string{socketDirectory, file.Name()}), "/")) + return C.CString("") + } + } + return C.CString("Tun does not exist") +} + +// Create wireguard tunnel +// +//export createTun +func createTun(_tunName *C.char) *C.char { + interfaceName := C.GoString(_tunName) + if existTun(interfaceName) { + errStr := C.GoString(deleteTun(_tunName)) + if errStr != "" { + return C.CString(errStr) + } + } + logger := device.NewLogger(levelLog, fmt.Sprintf("(%s) ", interfaceName)) + + // open TUN device (or use supplied fd) + tdev, err := tun.CreateTUN(interfaceName, device.DefaultMTU) + if err == nil { + realInterfaceName, err2 := tdev.Name() + if err2 == nil { + interfaceName = realInterfaceName + } + } + + if err != nil { + return C.CString(fmt.Sprintf("Failed to create TUN device: %v", err)) + } + + // open UAPI file (or use supplied fd) + fileUAPI, err := ipc.UAPIOpen(interfaceName) + if err != nil { + tdev.Close() + return C.CString(fmt.Sprintf("Failed to open UAPI file: %v", err)) + } + + // create device + dev := device.NewDevice(tdev, conn.NewDefaultBind(), logger) + + uapi, err := ipc.UAPIListen(interfaceName, fileUAPI) + if err != nil { + dev.Close() + tdev.Close() + return C.CString(fmt.Sprintf("Failed to listen on UAPI file: %v", err)) + } + + // UAPI Listener + uapiListened := uapi.Addr().String() + + clean := func() { + logger.Verbosef("Shutting down") + + uapi.Close() + dev.Close() + tdev.Close() + if uapiListened[:1] == "/" { + os.Remove(uapiListened) + } + delete(TunsEndProcess, uapiListened) + } + + TunsEndProcess[uapiListened] = clean + + go func() { + for { + conn, err := uapi.Accept() + if err != nil { + logger.Verbosef("UAPI listener closed") + clean() + break + } + logger.Verbosef("UAPI recive message") + go dev.IpcHandle(conn) + } + }() + + go func() { + logger.Verbosef("Device started") + <-dev.Wait() + logger.Verbosef("Device closing") + clean() + }() + + // Wait for device listener socket to be ready + for { + _, err := os.Stat(uapiListened) + if err == nil { + break + } + } + return C.CString("") +} + +// List wireguard-go UAPI's sockets +// first\0second\0third\0forth\0last\0\0 +// +//export listUapis +func listUapis() *C.char { + Files, err := os.ReadDir(socketDirectory) + if err != nil { + return C.CString("") + } + var uapis []string + for _, file := range Files { + if file.IsDir() { + continue + } + uapis = append(uapis, strings.Join(([]string{socketDirectory, file.Name()}), "/")) + } + return C.CString(strings.Join(uapis, "\x00") + "\x00") +} diff --git a/addon/userspace/go/main_unix.go b/addon/userspace/go/main_unix.go new file mode 100644 index 0000000..5fdbe60 --- /dev/null +++ b/addon/userspace/go/main_unix.go @@ -0,0 +1,11 @@ +//go:build linux || darwin || freebsd || openbsd + +package main + +import ( + _ "golang.zx2c4.com/wireguard/ipc" + _ "unsafe" +) + +//go:linkname socketDirectory golang.xz2c4.com/wireguard/ipc.socketDirectory +var socketDirectory = "/var/run/wireguard" diff --git a/addon/userspace/go/main_windows.go b/addon/userspace/go/main_windows.go new file mode 100644 index 0000000..ff982e7 --- /dev/null +++ b/addon/userspace/go/main_windows.go @@ -0,0 +1,5 @@ +//go:build windows + +package main + +var socketDirectory = `\\.\pipe\ProtectedPrefix\Administrators\WireGuard` diff --git a/addon/userspace/ipc.cpp b/addon/userspace/ipc.cpp new file mode 100644 index 0000000..e65de77 --- /dev/null +++ b/addon/userspace/ipc.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +FILE *interfaceFile(std::string sockPath) { + struct stat sbuf; + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + int fd = -1, ret; + FILE *f = NULL; + + errno = EINVAL; + ret = snprintf(addr.sun_path, sizeof(addr.sun_path), sockPath.c_str()); + if (ret < 0) + goto out; + + ret = stat(addr.sun_path, &sbuf); + if (ret < 0) + goto out; + + errno = EBADF; + if (!S_ISSOCK(sbuf.st_mode)) + goto out; + + ret = fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ret < 0) + goto out; + + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + if (errno == ECONNREFUSED) /* If the process is gone, we try to clean up the socket. */ + unlink(addr.sun_path); + goto out; + } + f = fdopen(fd, "r+"); + if (f) + errno = 0; +out: + ret = -errno; + if (ret) { + if (fd >= 0) + close(fd); + errno = -ret; + return NULL; + } + return f; +} \ No newline at end of file diff --git a/addon/userspace/wginterface.cpp b/addon/userspace/wginterface.cpp new file mode 100644 index 0000000..b8ccf08 --- /dev/null +++ b/addon/userspace/wginterface.cpp @@ -0,0 +1,176 @@ +#include "wginterface.hh" +#include "userspace/wg-go.h" +#include "userspace/ipc.cpp" +#include "genKey/wgkeys.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Ignore if required to windows +std::string driveLoad(std::map load) {} + +std::string getWireguardVersion() { + return std::string(wgVersion()); +} + +void WireguardDevices::getInterfaces() { + size_t len; char *device_name, *devicesList = listUapis(); + for (device_name = devicesList, len = 0; (len = strlen(device_name)); device_name += len + 1) this->push_back(std::string(device_name)); +} + +void WireguardDevices::deleteInterface(std::string wgName) { + std::string deleteStatus = deleteTun((char*)wgName.c_str()); + if (!deleteStatus.empty()) throw deleteStatus; +} + +bool char_is_digit(int c) { + return (unsigned int)(('0' - 1 - c) & (c - ('9' + 1))) >> (sizeof(c) * 8 - 1); +} + +void WireguardConfig::getWireguardConfig() { + if (this->name.length() == 0) throw std::string("Set wireguard name!"); + else if (!(WireguardDevices().exist(this->name))) throw std::string("Wireguard interface not exist"); + size_t line_buffer_len = 0, line_len; + char *key = NULL, *value; + int ret = -EPROTO; + FILE *f = interfaceFile(WireguardDevices().findSock(this->name).c_str()); + if (!f) throw std::string("Failed to open interface file"); + fprintf(f, "get=1\n\n"); + fflush(f); + std::string peerPubKey; + bool peer = false; + + while (getline(&key, &line_buffer_len, f) > 0) { + line_len = strlen(key); + if (line_len == 1 && key[0] == '\n') return; + value = strchr(key, '='); + if (!value || line_len == 0 || key[line_len - 1] != '\n') break; + *value++ = key[--line_len] = '\0'; + + if (this->Peers.size() == 0 && !strcmp(key, "private_key")) { + this->privateKey = wgKeys::HextoBase64(value); + this->publicKey = wgKeys::generatePublic(this->privateKey); + } else if (this->Peers.size() == 0 && !strcmp(key, "listen_port")) this->portListen = ({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = strtoull(value, &end, 10); if (*end || num > 0xffffU) break; num; }); + else if (this->Peers.size() == 0 && !strcmp(key, "fwmark")) this->fwmark = ({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = strtoull(value, &end, 10); if (*end || num > 0xffffffffU) break; num; }); + else if (!strcmp(key, "public_key")) { + Peer new_peer; + peerPubKey = wgKeys::HextoBase64(value); + this->Peers[peerPubKey] = new_peer; + peer = true; + } else if (peer && !strcmp(key, "preshared_key")) { + if (std::string(value) == "0000000000000000000000000000000000000000000000000000000000000000") continue; + if (strlen(value) == HexWgKeyLength) this->Peers[peerPubKey].presharedKey = wgKeys::HextoBase64(value); + } else if (peer && !strcmp(key, "persistent_keepalive_interval")) this->Peers[peerPubKey].keepInterval = ({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = strtoull(value, &end, 10); if (*end || num > 0xffffU) break; num; }); + else if (peer && !strcmp(key, "allowed_ip")) this->Peers[peerPubKey].allowedIPs.push_back(value); + else if (peer && !strcmp(key, "last_handshake_time_sec")) {} + else if (peer && !strcmp(key, "last_handshake_time_nsec")) this->Peers[peerPubKey].lastHandshake = ({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = strtoull(value, &end, 10); if (*end || num > 0x7fffffffffffffffULL) break; num; }); + else if (peer && !strcmp(key, "rx_bytes")) this->Peers[peerPubKey].rxBytes = ({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = strtoull(value, &end, 10); if (*end || num > 0xffffffffffffffffULL) break; num; }); + else if (peer && !strcmp(key, "tx_bytes")) this->Peers[peerPubKey].txBytes = ({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = strtoull(value, &end, 10); if (*end || num > 0xffffffffffffffffULL) break; num; }); + else if (!strcmp(key, "errno")) ret = -({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = strtoull(value, &end, 10); if (*end || num > 0x7fffffffU) break; num; }); + else if (peer && !strcmp(key, "endpoint")) { + char *begin, *end; + struct addrinfo *resolved; + struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_protocol = IPPROTO_UDP }; + if (!strlen(value)) break; + if (value[0] == '[') { + begin = &value[1]; + end = strchr(value, ']'); + if (!end) break; + *end++ = '\0'; + if (*end++ != ':' || !*end) break; + } else { + begin = value; + end = strrchr(value, ':'); + if (!end || !*(end + 1)) break; + *end++ = '\0'; + } + if (getaddrinfo(begin, end, &hints, &resolved) != 0) { + ret = ENETUNREACH; + throw std::string("Failed to resolve endpoint"); + } + if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) || (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6))) this->Peers[peerPubKey].endpoint = value; + else { + freeaddrinfo(resolved); + break; + } + freeaddrinfo(resolved); + } + } + + free(key); + fclose(f); + if (ret < 0) throw std::string("Failed to get wireguard config"); +} + +void WireguardConfig::setWireguardConfig() { + if (this->name.length() == 0) throw std::string("Set wireguard name!"); + else if (!(WireguardDevices().exist(this->name))) { + std::string PathError = createTun((char*)this->name.c_str()); + if (PathError.size() > 0) throw PathError; + } + + FILE* f = interfaceFile(WireguardDevices().findSock(this->name)); + fprintf(f, "set=1\n"); + + if (this->privateKey.length() == Base64WgKeyLength) fprintf(f, "private_key=%s\n", wgKeys::toHex(this->privateKey).c_str()); + if (this->portListen >= 0) fprintf(f, "listen_port=%u\n", this->portListen); + if (this->fwmark >= 0) fprintf(f, "fwmark=%u\n", this->fwmark); + if (this->replacePeers) fprintf(f, "replace_peers=true\n"); + + for (auto peer : this->Peers) { + fprintf(f, "public_key=%s\n", wgKeys::toHex(peer.first).c_str()); + if (peer.second.removeMe) { + fprintf(f, "remove=true\n"); + continue; + } + if (peer.second.presharedKey.length() == Base64WgKeyLength) fprintf(f, "preshared_key=%s\n", wgKeys::toHex(peer.second.presharedKey).c_str()); + if (peer.second.keepInterval) fprintf(f, "persistent_keepalive_interval=%u\n", peer.second.keepInterval); + if (peer.second.endpoint.length() > 2) fprintf(f, "endpoint=%s\n", peer.second.endpoint.c_str()); + if (peer.second.allowedIPs.size() > 0) { + fprintf(f, "replace_allowed_ips=true\n"); + for (auto s : peer.second.allowedIPs.getIpParsed()) fprintf(f, "allowed_ip=%s/%d\n", s.Address.c_str(), s.Mask); + } + } + + fprintf(f, "\n"); + fflush(f); + + int ret, set_errno = -EPROTO; + size_t line_buffer_len = 0, line_len; + char *key = NULL, *value; + while (getline(&key, &line_buffer_len, f) > 0) { + line_len = strlen(key); + ret = set_errno; + if (line_len == 1 && key[0] == '\n') break; + value = strchr(key, '='); + if (!value || line_len == 0 || key[line_len - 1] != '\n') break; + *value++ = key[--line_len] = '\0'; + + if (!strcmp(key, "errno")) { + long long num; + char *end; + if (value[0] != '-' && !char_is_digit(value[0])) break; + num = strtoll(value, &end, 10); + if (*end || num > INT_MAX || num < INT_MIN) break; + set_errno = num; + } + } + + free(key); + fclose(f); + ret = -(errno ? -errno : -EPROTO); + if (ret < 0) throw std::string("Cannot set configuration, code: ").append(std::to_string(ret)); +} + +void IpManeger::SetInInterface(std::string interfaceName) {} +void IpManeger::GetInInterface(std::string interfaceName) {} \ No newline at end of file diff --git a/addon/wg.hh b/addon/wg.hh new file mode 100644 index 0000000..3a5f84b --- /dev/null +++ b/addon/wg.hh @@ -0,0 +1,175 @@ +#ifndef __WG_NODE__ +#define __WG_NODE__ +#include "wginterface.hh" +#include +#include +#include + +class Promised : public Napi::AsyncWorker { + public: + Napi::Promise::Deferred NodePromise; + Promised(const Napi::Env &env): AsyncWorker(env), NodePromise{env} {} + + void OnError(const Napi::Error& e) override { + Napi::HandleScope scope(Env()); + NodePromise.Reject(e.Value()); + } + + void OnOK() override { + Napi::HandleScope scope(Env()); + auto call = [&](Napi::Value data) -> void { NodePromise.Resolve(data); }; + runOk(call); + } + + virtual void runOk(std::function callback) { + Napi::HandleScope scope(Env()); + callback(Env().Undefined()); + }; +}; + +class DeleteInterface : public Promised { + std::string wgName; + public: + DeleteInterface(const Napi::Env &env, const Napi::String &name): Promised(env), wgName{name.Utf8Value()} {} + + void Execute() override { + WireguardDevices wgDevs = WireguardDevices(); + try { + wgDevs.deleteInterface(wgName); + } catch (std::string &err) { SetError(err); } + } +}; + +class ListDevices : public Promised { + WireguardDevices wgDevs; + public: + ListDevices(const Napi::Env &env): Promised(env), wgDevs{} {} + + void Execute() override { + try { + wgDevs.getInterfaces(); + } catch (std::string &err) { SetError(err); } + } + + void runOk(std::function callback) override { + Napi::HandleScope scope(Env()); + const Napi::Env env = Env(); + const Napi::Array interf = Napi::Array::New(env); + for (auto &ip : wgDevs) interf.Set(interf.Length(), ip); + callback(interf); + } +}; + +class SetConfig : public WireguardConfig, public Promised { + public: + void Execute() { + try { + this->setWireguardConfig(); + } catch (std::string err) { + SetError(err); + } + } + + SetConfig(const Napi::Env &env, const Napi::Object config): Promised(env) { + if (!(config.Has("name"))) throw std::string("Set wireguard interface name!"); + if (!(config.Has("privateKey") && config.Get("privateKey").IsString())) throw std::string("Set wireguard private key!"); + this->name = config.Get("name").ToString().Utf8Value(); + this->privateKey = config.Get("privateKey").ToString().Utf8Value(); + if (config.Has("publicKey") && config.Get("publicKey").IsString() && config.Get("publicKey").ToString().Utf8Value().length() == Base64WgKeyLength) this->publicKey = config.Get("publicKey").ToString().Utf8Value(); + if (config.Has("portListen") && config.Get("portListen").IsNumber() && config.Get("portListen").ToNumber().Int32Value() >= 0) this->portListen = config.Get("portListen").ToNumber().Int32Value(); + if (config.Has("fwmark") && config.Get("fwmark").IsNumber() && config.Get("fwmark").ToNumber().Int32Value() >= 0) this->fwmark = config.Get("fwmark").ToNumber().Int32Value(); + if (config.Has("replacePeers") && config.Get("replacePeers").IsBoolean()) this->replacePeers = config.Get("replacePeers").ToBoolean().Value(); + + if (config.Has("address") && config.Get("address").IsArray() && config.Get("address").As().Length() > 0) { + const Napi::Array Addrs(config.Get("address").As()); + for (unsigned int AddrIndex = 0; AddrIndex < Addrs.Length(); AddrIndex++) { + if (!(Addrs[AddrIndex].IsString())) continue; + this->interfaceAddress.addIPMask(Addrs[AddrIndex].ToString().Utf8Value()); + } + } + + if (config.Has("peers") && config.Get("peers").IsObject()) { + const Napi::Object PeersObject(config.Get("peers").ToObject()); + const Napi::Array PeersKeys(PeersObject.GetPropertyNames()); + for (unsigned int peerIndex = 0; peerIndex < PeersKeys.Length(); peerIndex++) { + if (!(PeersObject.Get(PeersKeys[peerIndex].ToString()).IsObject())) continue; + const std::string publicKey(PeersKeys[peerIndex].ToString().Utf8Value()); + const Napi::Object peerConfig(PeersObject.Get(publicKey).ToObject()); + Peer peer; + + if (peerConfig.Has("removeMe") && peerConfig.Get("removeMe").IsBoolean() && peerConfig.Get("removeMe").As().Value()) peer.removeMe = true; + else { + if (peerConfig.Has("presharedKey") && peerConfig.Get("presharedKey").IsString() && peerConfig.Get("presharedKey").ToString().Utf8Value().length() == Base64WgKeyLength) peer.presharedKey = peerConfig.Get("presharedKey").ToString().Utf8Value(); + if (peerConfig.Has("keepInterval") && peerConfig.Get("keepInterval").IsNumber() && peerConfig.Get("keepInterval").ToNumber().Int32Value() > 0) peer.keepInterval = peerConfig.Get("keepInterval").ToNumber().Int32Value(); + if (peerConfig.Has("endpoint") && peerConfig.Get("endpoint").IsString() && peerConfig.Get("endpoint").ToString().Utf8Value().length() == Base64WgKeyLength) peer.endpoint = peerConfig.Get("endpoint").ToString().Utf8Value(); + if (peerConfig.Has("allowedIPs") && peerConfig.Get("allowedIPs").IsArray()) { + const Napi::Array ips = peerConfig.Get("allowedIPs").As(); + for (unsigned int ipIndex = 0; ipIndex < ips.Length(); ipIndex++) peer.allowedIPs.addIPMask(ips[ipIndex].ToString().Utf8Value()); + } + } + + // Set peer in map + this->Peers[publicKey] = peer; + } + } + } +}; + +class GetConfig : public WireguardConfig, public Promised { + public: + GetConfig(const Napi::Env &env, const Napi::String name): Promised(env) { + this->name = name.Utf8Value(); + } + void Execute() { + try { + this->getWireguardConfig(); + } catch (std::string err) { + SetError(err); + } + } + + void runOk(std::function callback) { + Napi::HandleScope scope(Env()); + const Napi::Env env = Env(); + const Napi::Object config = Napi::Object::New(env); + config.Set("name", this->name); + config.Set("privateKey", this->privateKey); + if (this->publicKey.length() == Base64WgKeyLength) config.Set("publicKey", this->publicKey); + if (this->portListen > 0) config.Set("portListen", this->portListen); + if (this->fwmark > 0) config.Set("fwmark", this->fwmark); + + // Interface IPs + const Napi::Array ips = Napi::Array::New(env); + for (auto ip : this->interfaceAddress) ips.Set(ips.Length(), ip); + config.Set("address", ips); + + // Peers + const Napi::Object peersObj = Napi::Object::New(env); + for (auto &__peer : this->Peers) { + std::string publicKey = __peer.first; + if (publicKey.length() != Base64WgKeyLength) continue; + const Napi::Object peerConfig = Napi::Object::New(env); + const Peer config = __peer.second; + + if (config.presharedKey.length() == Base64WgKeyLength) peerConfig.Set("presharedKey", config.presharedKey); + if (config.keepInterval >= 0) peerConfig.Set("keepInterval", config.keepInterval); + if (config.endpoint.length() > 0) peerConfig.Set("endpoint", config.endpoint); + + peerConfig.Set("rxBytes", Napi::BigInt::New(env, (uint64_t)config.rxBytes)); + peerConfig.Set("txBytes", Napi::BigInt::New(env, (uint64_t)config.txBytes)); + peerConfig.Set("lastHandshake", Napi::Date::New(env, config.lastHandshake)); + + const Napi::Array ips = Napi::Array::New(env); + for (auto &ip : config.allowedIPs) ips.Set(ips.Length(), ip); + peerConfig.Set("allowedIPs", ips); + + peersObj.Set(publicKey, peerConfig); + } + config.Set("peers", peersObj); + + // Set data + callback(config); + } +}; + +#endif diff --git a/addon/wginterface.hh b/addon/wginterface.hh new file mode 100644 index 0000000..d4611a2 --- /dev/null +++ b/addon/wginterface.hh @@ -0,0 +1,237 @@ +#ifndef __WIREGUARD_ADDON__ +#define __WIREGUARD_ADDON__ +#include "genKey/wgkeys.hh" +#include +#include +#include + +extern const int wgKeyLength; +extern const int Base64WgKeyLength; + +std::string getWireguardVersion(); +std::string driveLoad(std::map load); + +class WireguardDevices : public std::vector { + public: + ~WireguardDevices() { this->clear(); } + + /** Get all interfaces from kernel and insert in vector */ + void getInterfaces(); + + /** Delete interface from kernel network */ + void deleteInterface(std::string wgName); + + /** Check if exists wireguard interface intterface */ + bool exist(std::string name) { + this->getInterfaces(); + for (auto wgDev = this->begin(); wgDev != this->end(); ++wgDev) { + if (name == *wgDev) return true; + else { + std::string __nDev = std::string(wgDev->substr(wgDev->find_last_of("/")+1)); + if (__nDev.find(".sock") != std::string::npos) __nDev = __nDev.substr(0, __nDev.find(".sock")); + if (__nDev == name) return true; + } + } + + return false; + } + + /** Find sock */ + std::string findSock(std::string name) { + this->getInterfaces(); + for (auto wgDev = this->begin(); wgDev != this->end(); ++wgDev) { + if (name == *wgDev) return *wgDev; + else if (wgDev->find_last_of(".sock") != std::string::npos) { + std::string __nDev = std::string(wgDev->substr(wgDev->find_last_of("/")+1)); + if (__nDev.find(".sock") != std::string::npos) __nDev = __nDev.substr(0, __nDev.find(".sock")); + if (__nDev == name) return *wgDev; + } + } + + return std::string(""); + } +}; + +typedef struct { + std::string Address, Subnet; + int Mask, Proto; +} IpReference; + +/** Maneger Interface IPs */ +class IpManeger : public std::vector { + private: + std::string getSubNet4(int Mask) { + switch (Mask) { + case 1: return "128.0.0.0"; + case 2: return "192.0.0.0"; + case 3: return "224.0.0.0"; + case 4: return "240.0.0.0"; + case 5: return "248.0.0.0"; + case 6: return "252.0.0.0"; + case 7: return "254.0.0.0"; + case 8: return "255.0.0.0"; + case 9: return "255.128.0.0"; + case 10: return "255.192.0.0"; + case 11: return "255.224.0.0"; + case 12: return "255.240.0.0"; + case 13: return "255.248.0.0"; + case 14: return "255.252.0.0"; + case 15: return "255.254.0.0"; + case 16: return "255.255.0.0"; + case 17: return "255.255.128.0"; + case 18: return "255.255.192.0"; + case 19: return "255.255.224.0"; + case 20: return "255.255.240.0"; + case 21: return "255.255.248.0"; + case 22: return "255.255.252.0"; + case 23: return "255.255.254.0"; + case 24: return "255.255.255.0"; + case 25: return "255.255.255.128"; + case 26: return "255.255.255.192"; + case 27: return "255.255.255.224"; + case 28: return "255.255.255.240"; + case 29: return "255.255.255.248"; + case 30: return "255.255.255.252"; + case 31: return "255.255.255.254"; + + default: + case 32: return "255.255.255.255"; + } + } + public: + void SetInInterface(std::string interfaceName); + void GetInInterface(std::string interfaceName); + + void addIPMask(std::string ip) { + IpReference xTop; + auto maskStart = ip.find("/"); + auto isIPv6 = ip.find(":") != std::string::npos; + if (isIPv6) xTop.Mask = 128; + else xTop.Mask = 32; + if (maskStart == std::string::npos) xTop.Address = ip; + else { + xTop.Address = ip.substr(0, maskStart); + xTop.Mask = std::stoi(ip.substr(maskStart+1).c_str()); + if (!isIPv6 && xTop.Mask > 32) throw std::string("Set valid mask to ipv4 address!"); + } + xTop.Proto = isIPv6 ? 6 : 4; + if (!isIPv6) xTop.Subnet = this->getSubNet4(xTop.Mask); + this->push_back(xTop.Address.append("/").append(std::to_string(xTop.Mask))); + } + + std::vector getIpParsed() { + std::vector xTops; + for (auto ipAddrr = this->begin(); ipAddrr != this->end(); ++ipAddrr) { + IpReference nTop; + auto maskStart = ipAddrr->find("/"); + nTop.Address = ipAddrr->substr(0, maskStart); + nTop.Mask = std::stoi(ipAddrr->substr(maskStart+1).c_str()); + nTop.Proto = (nTop.Address.find(":") != std::string::npos) ? 6 : 4; + if (nTop.Proto == 6) nTop.Subnet = this->getSubNet4(nTop.Mask); + xTops.push_back(nTop); + } + return xTops; + } +}; + +/** + * peer class + */ +class Peer { + public: + Peer() { + this->removeMe = false; + this->keepInterval = 0; + this->rxBytes = 0; + this->txBytes = 0; + this->lastHandshake = 0; + } + + /** Remove specifies if the peer with this public key should be removed from a device's peer list. */ + bool removeMe; + + /** PresharedKey is an optional preshared key which may be used as an additional layer of security for peer communications. */ + std::string presharedKey; + + /** Endpoint is the most recent source address used for communication by this Peer. */ + std::string endpoint; + + /** PersistentKeepaliveInterval specifies how often an "empty" packet is sent to a peer to keep a connection alive. + * + * A value of 0 indicates that persistent keepalives are disabled. + */ + unsigned int keepInterval; + + /** Last peer handshake in milisseconds */ + unsigned long long lastHandshake; + + /** Peer data received (RX) and transferred (TX) */ + unsigned long long rxBytes, txBytes; + + /** Peer ips allowed to recive data */ + IpManeger allowedIPs; +}; + +/** +* Set and Get Wireguard configs +*/ +class WireguardConfig { + public: + + WireguardConfig() { + this->portListen = 0; + this->fwmark = 0; + this->replacePeers = false; + } + + /** Wireguard interface name */ + std::string name; + + /** Wireguard interface private key */ + std::string privateKey; + + /** Wireguard interface public key */ + std::string publicKey; + + /** Wireguard port listening or listened */ + unsigned short portListen; + + /** FirewallMark specifies a device's firewall mark else set to 0, the firewall mark will be cleared */ + unsigned int fwmark; + + /** Replace Wireguard peers if wireguard interface exists */ + bool replacePeers; + + /** Wireguard interface IPs */ + IpManeger interfaceAddress; + + /** Wireguard peers */ + std::map Peers; + + /** Set wireguard config in interface */ + void setWireguardConfig(); + + /** Get configuration from wireguard */ + void getWireguardConfig(); +}; + +namespace WireguardUserspace { + // Get Wireguard-go version + std::string getWireguardVersion(); + + // Close all wireguard tunels + void closeAllWireguardTunnels(); + + // Create tunel and return path to tunel + std::string createWireguardTunnel(std::string wgName); + + // Delete tunel by name and return true if success + void deleteWireguardTunnel(std::string wgName); + + // List all tunels + std::vector listTunnels(); + + // Check if tunel exist + bool checkIfExistTunnel(std::string wgName); +}; +#endif diff --git a/addons/tools/win/wireguard-nt/LICENSE.txt b/addon/win/LICENSE.txt similarity index 100% rename from addons/tools/win/wireguard-nt/LICENSE.txt rename to addon/win/LICENSE.txt diff --git a/addons/tools/win/wireguard-nt/README.md b/addon/win/README.md similarity index 100% rename from addons/tools/win/wireguard-nt/README.md rename to addon/win/README.md diff --git a/addons/tools/win/wireguard-nt/bin/amd64/wireguard.dll b/addon/win/amd64/wireguard.dll similarity index 100% rename from addons/tools/win/wireguard-nt/bin/amd64/wireguard.dll rename to addon/win/amd64/wireguard.dll diff --git a/addon/win/arm/wireguard.dll b/addon/win/arm/wireguard.dll new file mode 100644 index 0000000..3977c5d Binary files /dev/null and b/addon/win/arm/wireguard.dll differ diff --git a/addons/tools/win/wireguard-nt/bin/arm64/wireguard.dll b/addon/win/arm64/wireguard.dll similarity index 100% rename from addons/tools/win/wireguard-nt/bin/arm64/wireguard.dll rename to addon/win/arm64/wireguard.dll diff --git a/addon/win/wginterface.cpp b/addon/win/wginterface.cpp new file mode 100644 index 0000000..6d52617 --- /dev/null +++ b/addon/win/wginterface.cpp @@ -0,0 +1,530 @@ +#include "wginterface.hh" +#include "genKey/wgkeys.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const DEVPROPKEY devpkey_name = { { 0x65726957, 0x7547, 0x7261, { 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x4b, 0x65, 0x79 } }, DEVPROPID_FIRST_USABLE + 1 }; +#define IFNAMSIZ MAX_ADAPTER_NAME - 1 + +// Create Wireguard adapter +static WIREGUARD_CREATE_ADAPTER_FUNC *WireGuardCreateAdapter; +// Open Wireguard adapter by name if exists +static WIREGUARD_OPEN_ADAPTER_FUNC *WireGuardOpenAdapter; +// Close Wireguard adapter +static WIREGUARD_CLOSE_ADAPTER_FUNC *WireGuardCloseAdapter; +// Get Wireguard adapter LUID +static WIREGUARD_GET_ADAPTER_LUID_FUNC *WireGuardGetAdapterLUID; +// Get running Wireguard driver version +static WIREGUARD_GET_RUNNING_DRIVER_VERSION_FUNC *WireGuardGetRunningDriverVersion; +// Delete Wireguard driver if non exists adapters +static WIREGUARD_DELETE_DRIVER_FUNC *WireGuardDeleteDriver; +// Set logger for Wireguard adapter +static WIREGUARD_SET_LOGGER_FUNC *WireGuardSetLogger; +// Set adapter logging for Wireguard adapter +static WIREGUARD_SET_ADAPTER_LOGGING_FUNC *WireGuardSetAdapterLogging; +// Get adapter state for Wireguard adapter +static WIREGUARD_GET_ADAPTER_STATE_FUNC *WireGuardGetAdapterState; +// Set adapter state for Wireguard adapter +static WIREGUARD_SET_ADAPTER_STATE_FUNC *WireGuardSetAdapterState; +// Get Wireguard adapter configuration +static WIREGUARD_GET_CONFIGURATION_FUNC *WireGuardGetConfiguration; +// Set Wireguard adapter configuration +static WIREGUARD_SET_CONFIGURATION_FUNC *WireGuardSetConfiguration; + +LPCWSTR WireguardAddonDescription = WireguardAddonDescription; + +// Function to check if the current user has administrator privileges +bool IsRunAsAdmin() { + BOOL fRet = FALSE; + HANDLE hToken = NULL; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { + TOKEN_ELEVATION Elevation; + DWORD cbSize = sizeof(TOKEN_ELEVATION); + if (GetTokenInformation(hToken, TokenElevation, &Elevation, sizeof(Elevation), &cbSize)) { + fRet = Elevation.TokenIsElevated; + } + } + if (hToken) CloseHandle(hToken); + return !!fRet; +} + +std::string convertWcharString(const wchar_t *wideStr) { + // Create a wstring from the wide character string + std::wstring wideString(wideStr); + + // Use std::wstring_convert for conversion + std::wstring_convert> converter; + std::string narrowString = converter.to_bytes(wideString); + + // Now narrowString is a std::string containing the converted string + return narrowString; +} + +LPCWSTR toLpcwstr(std::string s) { + wchar_t* wString = new wchar_t[s.length()+1]; + MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, wString, s.length()+1); + return wString; +} + +std::string parseEndpoint(SOCKADDR_INET *input) { + if (!(input->si_family == AF_INET || input->si_family == AF_INET6)) return ""; + char saddr[INET6_ADDRSTRLEN]; + input->si_family == AF_INET ? inet_ntop(AF_INET, &input->Ipv4.sin_addr, saddr, INET_ADDRSTRLEN) : inet_ntop(AF_INET6, &input->Ipv6.sin6_addr, saddr, INET6_ADDRSTRLEN); + + if (input->si_family == AF_INET6) return std::string("[").append(saddr).append("]:").append(std::to_string(htons(input->Ipv6.sin6_port))); + return std::string(saddr).append(":").append(std::to_string(htons(input->Ipv4.sin_port))); +} + +std::string getErrorString(DWORD errorMessageID) { + if (errorMessageID == 0 || errorMessageID < 0) std::string("Error code: ").append(std::to_string(errorMessageID)); + LPSTR messageBuffer = nullptr; + //Ask Win32 to give us the string version of that message ID. + //The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be). + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + //Copy the error message into a std::string. + std::string message(messageBuffer, size); + //Free the Win32's string's buffer. + LocalFree(messageBuffer); + return std::string("Error code: ").append(std::to_string(errorMessageID)).append(", Message: ").append(message); +} + +std::string driveLoad(std::map load) { + if (!IsRunAsAdmin()) return "Run nodejs with administrator privilegies"; + auto DLLPATH = load["WIN32DLLPATH"]; + if (!(DLLPATH.length())) return "Require Wireguard DLL file path!"; + LPCWSTR dllPath = toLpcwstr(DLLPATH); + + HMODULE WireGuardDll = LoadLibraryExW(dllPath, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!WireGuardDll) return std::string("Failed to initialize WireGuardNT, ").append(getErrorString(GetLastError()));; + #define X(Name) ((*(FARPROC *)&Name = GetProcAddress(WireGuardDll, #Name)) == NULL) + if (X(WireGuardCreateAdapter) || X(WireGuardOpenAdapter) || X(WireGuardCloseAdapter) || X(WireGuardGetAdapterLUID) || X(WireGuardGetRunningDriverVersion) || X(WireGuardDeleteDriver) || X(WireGuardSetLogger) || X(WireGuardSetAdapterLogging) || X(WireGuardGetAdapterState) || X(WireGuardSetAdapterState) || X(WireGuardGetConfiguration) || X(WireGuardSetConfiguration)) + #undef X + { + DWORD LastError = GetLastError(); + FreeLibrary(WireGuardDll); + SetLastError(LastError); + return std::string("Failed to set Functions from WireGuardNT DLL, ").append(getErrorString(GetLastError()));; + } + + return ""; +} + +std::string getWireguardVersion() { + // Create interface to get version + WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardCreateAdapter(L"getWgVersion", WireguardAddonDescription, NULL); + WireGuardCloseAdapter(Adapter); + DWORD Version = WireGuardGetRunningDriverVersion(); + if (Version == 0) { + auto statusErr = GetLastError(); + WireGuardCloseAdapter(Adapter); + if (statusErr == ERROR_FILE_NOT_FOUND) return "Driver not loaded"; + return std::string("Cannot get version drive, ").append(getErrorString(GetLastError())); + } + return std::string("WireGuardNT v").append(std::to_string((Version >> 16) & 0xff)).append(".").append(std::to_string((Version >> 0) & 0xff)).append(".").append(std::to_string((Version >> 8) & 0xff)); +} + +void WireguardDevices::getInterfaces() { + HDEVINFO dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, L"SWD\\WireGuard", NULL, DIGCF_PRESENT, NULL, NULL, NULL); + if (dev_info == INVALID_HANDLE_VALUE) throw std::string("Cannot get devices"); + for (DWORD devIndex = 0;; ++devIndex) { + SP_DEVINFO_DATA dev_info_data; + dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA); + if (!SetupDiEnumDeviceInfo(dev_info, devIndex, &dev_info_data)) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) break; + continue; + } + + DWORD buf_len; ULONG status, problem_code; + WCHAR adapter_name[MAX_ADAPTER_NAME]; + char *interface_name; + DEVPROPTYPE prop_type; + + if (!SetupDiGetDevicePropertyW(dev_info, &dev_info_data, &devpkey_name, &prop_type, (PBYTE)adapter_name, sizeof(adapter_name), NULL, 0) || prop_type != DEVPROP_TYPE_STRING) continue; + adapter_name[_countof(adapter_name) - 1] = L'0'; + if (!adapter_name[0]) continue; + buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, NULL, 0, NULL, NULL); + if (!buf_len) continue; + interface_name = (char *)malloc(buf_len); + if (!interface_name) continue; + buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, interface_name, buf_len, NULL, NULL); + if (!buf_len) { + free(interface_name); + continue; + } + + if (CM_Get_DevNode_Status(&status, &problem_code, dev_info_data.DevInst, 0) == CR_SUCCESS && (status & (DN_DRIVER_LOADED | DN_STARTED)) == (DN_DRIVER_LOADED | DN_STARTED)) this->push_back(std::string(interface_name)); + free(interface_name); + } + SetupDiDestroyDeviceInfoList(dev_info); +} + +/* + Delete wireguard interface + + Current bug from Addon: Set interface down and not delete wireguard interface +*/ +void WireguardDevices::deleteInterface(std::string wgName) { + WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(wgName)); + if (!Adapter) throw std::string("This interface not exists in Wireguard-Tools.js addon!"); + if (!(WireGuardSetAdapterState(Adapter, WIREGUARD_ADAPTER_STATE::WIREGUARD_ADAPTER_STATE_DOWN))) throw std::string("Failed to set down interface, ").append(getErrorString(GetLastError())); + WireGuardCloseAdapter(Adapter); +} + +/** + * Change point from calloc or malloc, T: from, C: to + */ +template C* changePoint(T *x) { + return reinterpret_cast(((char*)x) + sizeof(T)); +} + +void WireguardConfig::getWireguardConfig() { + if (this->name.length() == 0) throw std::string("Require interface name!"); + else if (!(WireguardDevices().exist(this->name))) throw std::string("This interface not exists in Wireguard-Tools.js addon!"); + + WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(this->name)); + if (!Adapter) throw std::string("This interface not exists in Wireguard-Tools.js addon!"); + NET_LUID InterfaceLuid; + WireGuardGetAdapterLUID(Adapter, &InterfaceLuid); + this->interfaceAddress.GetInInterface(this->name); + + DWORD buf_len = 0; + WIREGUARD_INTERFACE *wg_iface = nullptr; + + while (!(WireGuardGetConfiguration(Adapter, wg_iface, &buf_len))) { + free(wg_iface); + if (GetLastError() != ERROR_MORE_DATA) throw std::string("Failed get interface config, code: ").append(std::to_string(GetLastError())); + wg_iface = (WIREGUARD_INTERFACE *)malloc(buf_len); + if (!wg_iface) throw std::string("Failed get interface config, ").append(std::to_string(-errno)); + } + + if (wg_iface->Flags & WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_PRIVATE_KEY) this->privateKey = wgKeys::toString(wg_iface->PrivateKey); + if (wg_iface->Flags & WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_PUBLIC_KEY) this->publicKey = wgKeys::toString(wg_iface->PublicKey); + if (wg_iface->ListenPort > 0) this->portListen = wg_iface->ListenPort; + + WIREGUARD_PEER *wg_peer = changePoint(wg_iface); + for (DWORD i = 0; i < wg_iface->PeersCount; i++) { + auto pubKey = wgKeys::toString(wg_peer->PublicKey); + Peer peerConfig; + + peerConfig.txBytes = wg_peer->TxBytes; + peerConfig.rxBytes = wg_peer->RxBytes; + peerConfig.lastHandshake = (wg_peer->LastHandshake - 116444736000000000LL) / (100 / 1000); + + if (wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PRESHARED_KEY) peerConfig.presharedKey = wgKeys::toString(wg_peer->PresharedKey); + if (wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_ENDPOINT) peerConfig.endpoint = parseEndpoint(&wg_peer->Endpoint); + if (wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PERSISTENT_KEEPALIVE) peerConfig.keepInterval = wg_peer->PersistentKeepalive; + + WIREGUARD_ALLOWED_IP* wg_aip = changePoint(wg_peer); + for (DWORD __aip = 0; __aip < wg_peer->AllowedIPsCount; __aip++) { + char saddr[INET6_ADDRSTRLEN]; + if (wg_aip->AddressFamily == AF_INET) { + inet_ntop(AF_INET, &wg_aip->Address.V6, saddr, INET_ADDRSTRLEN); + peerConfig.allowedIPs.push_back(std::string(saddr).append("/").append(std::to_string(wg_aip->Cidr))); + } else if (wg_aip->AddressFamily == AF_INET6) { + inet_ntop(AF_INET6, &wg_aip->Address.V6, saddr, INET6_ADDRSTRLEN); + peerConfig.allowedIPs.push_back(std::string(saddr).append("/").append(std::to_string(wg_aip->Cidr))); + } + ++wg_aip; + } + wg_peer = reinterpret_cast(wg_aip); + this->Peers[pubKey] = peerConfig; + } + + free(wg_iface); +} + +void WireguardConfig::setWireguardConfig() { + if (this->name.length() == 0) throw std::string("Require interface name!"); + else if (this->name.length() > IFNAMSIZ) throw std::string("Interface name too long"); + + DWORD buf_len = sizeof(WIREGUARD_INTERFACE); + for (auto peer : this->Peers) { + if (DWORD_MAX - buf_len < sizeof(WIREGUARD_PEER)) throw std::string("Buffer overflow"); + buf_len += sizeof(WIREGUARD_PEER); + for (auto aip : peer.second.allowedIPs) { + if (DWORD_MAX - buf_len < sizeof(WIREGUARD_ALLOWED_IP)) throw std::string("Buffer overflow"); + buf_len += sizeof(WIREGUARD_ALLOWED_IP); + } + } + WIREGUARD_INTERFACE *wg_iface = reinterpret_cast(calloc(1, buf_len)); + if (!wg_iface) throw std::string("Cannot alloc buff"); + wg_iface->PeersCount = 0; + + wgKeys::stringToKey(wg_iface->PrivateKey, this->privateKey); + wg_iface->Flags = WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_PRIVATE_KEY; + + wg_iface->ListenPort = this->portListen; + if (this->portListen >= 0) wg_iface->Flags = (WIREGUARD_INTERFACE_FLAG)(wg_iface->Flags|WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_LISTEN_PORT); + if (this->replacePeers) wg_iface->Flags = (WIREGUARD_INTERFACE_FLAG)(wg_iface->Flags|WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_REPLACE_PEERS); + + WIREGUARD_ALLOWED_IP *wg_aip; + WIREGUARD_PEER *wg_peer = changePoint(wg_iface); + for (auto __peer : this->Peers) { + auto peerPublicKey = __peer.first; auto peerConfig = __peer.second; + wgKeys::stringToKey(wg_peer->PublicKey, peerPublicKey); + wg_peer->Flags = WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PUBLIC_KEY; + wg_peer->AllowedIPsCount = 0; + wg_iface->PeersCount++; + + if (peerConfig.removeMe) { + wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_REMOVE); + wg_peer = changePoint(wg_peer); + } else { + if (peerConfig.presharedKey.size() == B64_WG_KEY_LENGTH) { + wgKeys::stringToKey(wg_peer->PresharedKey, peerConfig.presharedKey); + wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PRESHARED_KEY); + } + + wg_peer->PersistentKeepalive = peerConfig.keepInterval; + if (peerConfig.keepInterval >= 0) wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PERSISTENT_KEEPALIVE); + + if (peerConfig.endpoint.size() > 0) { + int ret, retries = ([]() -> int { + unsigned long ret; + char *retries = getenv("WG_ENDPOINT_RESOLUTION_RETRIES"), *end; + + if (!retries) return 15; + if (!strcmp(retries, "infinity")) return -1; + + ret = strtoul(retries, &end, 10); + if (*end || ret > INT_MAX) { + fprintf(stderr, "Unable to parse WG_ENDPOINT_RESOLUTION_RETRIES: `%s'\n", retries); + exit(1); + } + return (int)ret; + })(); + + char *begin, *end; + auto mmutable = strdup(peerConfig.endpoint.c_str()); + if (!mmutable) throw std::string("strdup"); + if (!peerConfig.endpoint.size()) { + free(mmutable); + throw std::string("Unable to parse empty endpoint"); + } + if (mmutable[0] == '[') { + begin = &mmutable[1]; + end = strchr(mmutable, ']'); + if (!end) { + free(mmutable); + throw std::string("Unable to find matching brace of endpoint: ").append(peerConfig.endpoint); + } + *end++ = '\0'; + if (*end++ != ':' || !*end) { + free(mmutable); + throw std::string("Unable to find port of endpoint: ").append(peerConfig.endpoint); + } + } else { + begin = mmutable; + end = strrchr(mmutable, ':'); + if (!end || !*(end + 1)) { + free(mmutable); + throw std::string("Unable to find port of endpoint: ").append(peerConfig.endpoint); + } + *end++ = '\0'; + } + + ADDRINFOA *resolved; + for (unsigned int timeout = 1000000;; timeout = ((20000000) < (timeout * 6 / 5) ? (20000000) : (timeout * 6 / 5))) { + // ret = getaddrinfo(begin, end, &hints, &resolved); + ret = getaddrinfo(begin, end, NULL, &resolved); + if (!ret) break; + if (ret == EAI_NONAME || ret == EAI_FAIL || + #ifdef EAI_NODATA + ret == EAI_NODATA || + #endif + (retries >= 0 && !retries--)) { + free(mmutable); + throw std::string("Error code: ").append(std::to_string(ret)); + } + std::this_thread::sleep_for(std::chrono::microseconds(timeout)); + } + + if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(SOCKADDR_IN))) memcpy(&wg_peer->Endpoint.Ipv4, resolved->ai_addr, resolved->ai_addrlen); + else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(SOCKADDR_IN6)) memcpy(&wg_peer->Endpoint.Ipv6, resolved->ai_addr, resolved->ai_addrlen); + else { + freeaddrinfo(resolved); + throw std::string("Neither IPv4 nor IPv6 address found: ").append(peerConfig.endpoint); + } + freeaddrinfo(resolved); + free(mmutable); + + wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_ENDPOINT); + } + + wg_aip = changePoint(wg_peer); + for (auto Ip : peerConfig.allowedIPs.getIpParsed()) { + wg_aip->AddressFamily = Ip.Proto == 4 ? AF_INET : AF_INET6; + wg_aip->Cidr = Ip.Mask; + + if (Ip.Proto == 6 && inet_pton(AF_INET6, Ip.Address.c_str(), &wg_aip->Address.V6) == 1) wg_aip->AddressFamily = AF_INET6; + else if (Ip.Proto == 4 && inet_pton(AF_INET, Ip.Address.c_str(), &wg_aip->Address.V4) == 1) wg_aip->AddressFamily = AF_INET; + else continue; + + wg_peer->AllowedIPsCount++; + wg_aip = changePoint(wg_aip); + if (!(wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_REPLACE_ALLOWED_IPS)) wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_REPLACE_ALLOWED_IPS); + } + wg_peer = reinterpret_cast(((char*)wg_aip)); + } + } + + WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(this->name)); + if (!Adapter) Adapter = WireGuardCreateAdapter(toLpcwstr(this->name), WireguardAddonDescription, NULL); + if (!Adapter) throw std::string("Failed to create adapter, ").append(getErrorString(GetLastError())); + + auto status = WireGuardSetConfiguration(Adapter, reinterpret_cast(wg_iface), buf_len) && WireGuardSetAdapterState(Adapter, WIREGUARD_ADAPTER_STATE::WIREGUARD_ADAPTER_STATE_UP); + free(wg_iface); + + if (!status) { + auto status = GetLastError(); + WireGuardCloseAdapter(Adapter); + throw std::string("Failed to set interface config, ").append(getErrorString(status)); + } + + this->interfaceAddress.SetInInterface(this->name); +} + +void IpManeger::GetInInterface(std::string interfaceName) { + // Define the InterfaceLuid of the network interface you want to query + WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(interfaceName)); + if (!Adapter) throw std::string("Cannot open wireguard adapter"); + NET_LUID InterfaceLuid; + WireGuardGetAdapterLUID(Adapter, &InterfaceLuid); + + ULONG bufferSize = 0; // Initial buffer size + // Call GetAdaptersAddresses to get the required buffer size + if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &bufferSize) == ERROR_BUFFER_OVERFLOW) { + // Allocate memory for the buffer + PIP_ADAPTER_ADDRESSES addresses = reinterpret_cast(malloc(bufferSize)); + // Call GetAdaptersAddresses again with the allocated buffer + if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, addresses, &bufferSize) == NO_ERROR) { + // Iterate through the list of adapters + for (PIP_ADAPTER_ADDRESSES adapter = addresses; adapter != nullptr; adapter = adapter->Next) { + // Check if the adapter matches the specified InterfaceLuid + if (memcmp(&adapter->Luid, &InterfaceLuid, sizeof(NET_LUID)) == 0) { + // Iterate through the list of IP addresses associated with the adapter + for (PIP_ADAPTER_UNICAST_ADDRESS address = adapter->FirstUnicastAddress; address != nullptr; address = address->Next) { + // Access the IP address in the address structure + sockaddr* sa = address->Address.lpSockaddr; + char ip[INET6_ADDRSTRLEN]; + if (sa->sa_family == AF_INET) { + inet_ntop(AF_INET, &reinterpret_cast(sa)->sin_addr, ip, sizeof(ip)); + } else if (sa->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &reinterpret_cast(sa)->sin6_addr, ip, sizeof(ip)); + } else continue; + // Print or use the IP address as needed + this->addIPMask(std::string(ip).append("/").append(std::to_string(address->Address.iSockaddrLength))); + } + } + } + } + + // Free the allocated buffer + free(addresses); + } +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry +void IpManeger::SetInInterface(std::string interfaceName) { + if (this->size() == 0) return; + WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(interfaceName)); + if (!Adapter) throw std::string("Cannot open wireguard adapter"); + + NET_LUID InterfaceLuid; + WireGuardGetAdapterLUID(Adapter, &InterfaceLuid); + ULONG bufferSize = 0; // Initial buffer size + // Call GetAdaptersAddresses to get the required buffer size + if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &bufferSize) == ERROR_BUFFER_OVERFLOW) { + // Allocate memory for the buffer + PIP_ADAPTER_ADDRESSES addresses = reinterpret_cast(malloc(bufferSize)); + // Call GetAdaptersAddresses again with the allocated buffer + if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, addresses, &bufferSize) == NO_ERROR) { + // Iterate through the list of adapters + for (PIP_ADAPTER_ADDRESSES adapter = addresses; adapter != nullptr; adapter = adapter->Next) { + // Check if the adapter matches the specified InterfaceLuid + if (memcmp(&adapter->Luid, &InterfaceLuid, sizeof(NET_LUID)) == 0) { + // Iterate through the list of IP addresses associated with the adapter + for (PIP_ADAPTER_UNICAST_ADDRESS address = adapter->FirstUnicastAddress; address != nullptr; address = address->Next) { + // Access the IP address in the address structure + sockaddr* sa = address->Address.lpSockaddr; + char ip[INET6_ADDRSTRLEN]; + if (sa->sa_family == AF_INET) { + inet_ntop(AF_INET, &reinterpret_cast(sa)->sin_addr, ip, sizeof(ip)); + } else if (sa->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &reinterpret_cast(sa)->sin6_addr, ip, sizeof(ip)); + } else continue; + + // Delete the IP address + MIB_UNICASTIPADDRESS_ROW row; + memset(&row, 0, sizeof(row)); + WireGuardGetAdapterLUID(Adapter, &row.InterfaceLuid); + if (sa->sa_family == AF_INET) { + row.Address.si_family = AF_INET; + inet_pton(AF_INET, ip, &row.Address.Ipv4.sin_addr); + } else if (sa->sa_family == AF_INET6) { + row.Address.si_family = AF_INET6; + inet_pton(AF_INET6, ip, &row.Address.Ipv6.sin6_addr); + } else continue; + + if (DeleteUnicastIpAddressEntry(&row) != NO_ERROR) throw std::string("Cannot delete IP address from interface"); + } + } + } + } + + // Free the allocated buffer + free(addresses); + } + + for (auto ip : this->getIpParsed()) { + MIB_UNICASTIPADDRESS_ROW row; + memset(&row, 0, sizeof(row)); + memcpy(&row.InterfaceLuid, &InterfaceLuid, sizeof(NET_LUID)); + + row.DadState = NldsPreferred; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + row.OnLinkPrefixLength = ip.Mask; + if (ip.Proto == 4) { + row.Address.si_family = AF_INET; + inet_pton(AF_INET, ip.Address.c_str(), &row.Address.Ipv4.sin_addr); + } else if (ip.Proto == 6) { + row.Address.si_family = AF_INET6; + inet_pton(AF_INET6, ip.Address.c_str(), &row.Address.Ipv6.sin6_addr); + } else continue; + + if (CreateUnicastIpAddressEntry(&row) != NO_ERROR) throw std::string("Cannot add IP address to interface"); + } +} \ No newline at end of file diff --git a/addons/tools/win/wireguard-nt/include/wireguard.h b/addon/win/wireguard.h similarity index 100% rename from addons/tools/win/wireguard-nt/include/wireguard.h rename to addon/win/wireguard.h diff --git a/addon/win/x86/wireguard.dll b/addon/win/x86/wireguard.dll new file mode 100644 index 0000000..ecca024 Binary files /dev/null and b/addon/win/x86/wireguard.dll differ diff --git a/addons/genKey/key_gen.cpp b/addons/genKey/key_gen.cpp deleted file mode 100644 index c4782d4..0000000 --- a/addons/genKey/key_gen.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include -#include "wgkeys.hh" - -class privateKeyWorker : public Napi::AsyncWorker { - private: - std::string pskString; - Napi::Promise::Deferred genPromise; - public: - ~privateKeyWorker() {} - privateKeyWorker(const Napi::Env env) : AsyncWorker(env), genPromise{env} {} - Napi::Promise getPromise() { return genPromise.Promise(); } - void Execute() override { - wg_key keyg; - wgKeys::generatePrivate(keyg); - pskString = wgKeys::toString(keyg); - } - void OnOK() override { - Napi::HandleScope scope(Env()); - genPromise.Resolve(Napi::String::New(Env(), pskString)); - } - void OnError(const Napi::Error& e) override { - Napi::HandleScope scope(Env()); - genPromise.Reject(e.Value()); - } -}; - -class publicKeyWorker : public Napi::AsyncWorker { - private: - std::string privKey, pubString; - Napi::Promise::Deferred genPromise; - public: - ~publicKeyWorker() {} - publicKeyWorker(const Napi::Env env, std::string privateKey) : AsyncWorker(env), privKey(privateKey), genPromise{env} {} - Napi::Promise getPromise() { return genPromise.Promise(); } - void Execute() override { - wg_key interfacePrivateKey, interfacePublicKey; - try { - wgKeys::stringToKey(interfacePrivateKey, privKey); - wgKeys::generatePublic(interfacePublicKey, interfacePrivateKey); - pubString = wgKeys::toString(interfacePublicKey); - } catch (std::string &err) { - SetError(err); - } - } - void OnOK() override { - Napi::HandleScope scope(Env()); - genPromise.Resolve(Napi::String::New(Env(), pubString)); - } - void OnError(const Napi::Error& e) override { - Napi::HandleScope scope(Env()); - genPromise.Reject(e.Value()); - } -}; - -class presharedKeyWorker : public Napi::AsyncWorker { - private: - std::string pskString; - Napi::Promise::Deferred genPromise; - public: - ~presharedKeyWorker() {} - presharedKeyWorker(const Napi::Env env) : AsyncWorker(env), genPromise{env} {} - Napi::Promise getPromise() { return genPromise.Promise(); } - void Execute() override { - wg_key keyg; - wgKeys::generatePreshared(keyg); - pskString = wgKeys::toString(keyg); - } - void OnOK() override { - Napi::HandleScope scope(Env()); - genPromise.Resolve(Napi::String::New(Env(), pskString)); - } - void OnError(const Napi::Error& e) override { - Napi::HandleScope scope(Env()); - genPromise.Reject(e.Value()); - } -}; - -class genKeysWorker : public Napi::AsyncWorker { - private: - std::string privateKey, publicKey, presharedKey; - bool withPreshared = false; - Napi::Promise::Deferred genPromise; - public: - ~genKeysWorker() {} - genKeysWorker(const Napi::Env env, bool withPresharedKey) : AsyncWorker(env), withPreshared(withPresharedKey), genPromise{env} {} - Napi::Promise getPromise() { return genPromise.Promise(); } - void Execute() override { - wg_key keyPriv, preshe, pub; - - wgKeys::generatePrivate(keyPriv); - privateKey = wgKeys::toString(keyPriv); - - wgKeys::generatePublic(pub, keyPriv); - publicKey = wgKeys::toString(pub); - - if (!withPreshared) return; - wgKeys::generatePreshared(preshe); - presharedKey = wgKeys::toString(preshe); - } - void OnOK() override { - Napi::HandleScope scope(Env()); - const Napi::Env env = Env(); - auto keys = Napi::Object::New(env); - keys.Set("privateKey", privateKey); - keys.Set("publicKey", publicKey); - if (withPreshared) keys.Set("presharedKey", presharedKey); - - genPromise.Resolve(keys); - } - void OnError(const Napi::Error& e) override { - Napi::HandleScope scope(Env()); - genPromise.Reject(e.Value()); - } -}; - -Napi::Object Init(Napi::Env exportsEnv, Napi::Object exports) { - auto constants = Napi::Object::New(exportsEnv); - constants.Set("WG_KEY_LENGTH", WG_KEY_LENGTH); - constants.Set("B64_WG_KEY_LENGTH", B64_WG_KEY_LENGTH); - exports.Set("constants", constants); - - exports.Set("presharedKey", Napi::Function::New(exportsEnv, [&](const Napi::CallbackInfo& info) { - const Napi::Env env = info.Env(); - - // Callback function is latest argument - auto *Gen = new presharedKeyWorker(env); - Gen->Queue(); - return Gen->getPromise(); - })); - - exports.Set("privateKey", Napi::Function::New(exportsEnv, [&](const Napi::CallbackInfo& info) { - const Napi::Env env = info.Env(); - - // Callback function is latest argument - auto *Gen = new privateKeyWorker(env); - Gen->Queue(); - return Gen->getPromise(); - })); - - exports.Set("publicKey", Napi::Function::New(exportsEnv, [&](const Napi::CallbackInfo& info) -> Napi::Value { - const Napi::Env env = info.Env(); - if (!(info[0].IsString())) { - Napi::Error::New(env, "Require private key").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - // Callback function is latest argument - auto *Gen = new publicKeyWorker(env, info[0].ToString().Utf8Value().c_str()); - Gen->Queue(); - return Gen->getPromise(); - })); - - exports.Set("genKey", Napi::Function::New(exportsEnv, [&](const Napi::CallbackInfo &info) { - const Napi::Env env = info.Env(); - bool withPreshared = false; - if (info[0].IsBoolean()) withPreshared = info[0].ToBoolean().Value(); - auto Gen = new genKeysWorker(env, withPreshared); - Gen->Queue(); - return Gen->getPromise(); - })); - return exports; -} -NODE_API_MODULE(addon, Init); \ No newline at end of file diff --git a/addons/genKey/wgkeys.hh b/addons/genKey/wgkeys.hh deleted file mode 100644 index 5490684..0000000 --- a/addons/genKey/wgkeys.hh +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _WGKEY_ -#define _WGKEY_ -#include - -const int WG_KEY_LENGTH = 32, B64_WG_KEY_LENGTH = ((WG_KEY_LENGTH + 2) / 3) * 4; -typedef unsigned char wg_key[WG_KEY_LENGTH]; -typedef char wg_key_b64_string[B64_WG_KEY_LENGTH + 1]; -typedef long long fe[16]; - -namespace wgKeys { - /* Convert wg_key to std::string */ - std::string toString(const wg_key key); - - /* base64 to wg_key */ - void stringToKey(wg_key key, std::string keyBase64); - - // bool key_is_zero(wg_key key); - - /* Generate preshared key */ - void generatePreshared(wg_key key); - - /* Generate private key (based on generatePreshared) */ - void generatePrivate(wg_key private_key); - - /* Get public key from private key */ - void generatePublic(wg_key public_key, const wg_key private_key); -} - -#endif \ No newline at end of file diff --git a/addons/tools/linux/set_ip.cpp b/addons/tools/linux/set_ip.cpp deleted file mode 100644 index 3e0cab0..0000000 --- a/addons/tools/linux/set_ip.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include - -std::string setIps(std::string name, std::vector ips) { - return ""; -} \ No newline at end of file diff --git a/addons/tools/wginterface-dummy.cpp b/addons/tools/wginterface-dummy.cpp deleted file mode 100644 index 0cc062e..0000000 --- a/addons/tools/wginterface-dummy.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -unsigned long maxName() { - return 16; -} - -std::string versionDrive() { - return "Userspace"; -} - -void listDevices::Execute() {} -void deleteInterface::Execute() {} -void setConfig::Execute() {} -void getConfig::Execute() {} \ No newline at end of file diff --git a/addons/tools/wginterface-linux.cpp b/addons/tools/wginterface-linux.cpp deleted file mode 100644 index 2470b32..0000000 --- a/addons/tools/wginterface-linux.cpp +++ /dev/null @@ -1,353 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "wginterface.hh" -#include "linux/set_ip.cpp" -extern "C" { - #include "linux/wireguard.h" -} - -std::string getKernelMesage(int errStatus) { - std::string message = std::string("Error code: ").append(std::to_string(errStatus)); - return message; -} - -unsigned long maxName() { - return IFNAMSIZ; -} - -std::string versionDrive() { - return "Kernel"; -} - -class List { - public: - std::vector devs; - - List() {} - ~List() { - devs.clear(); - } - - void getAll() { - char *device_name, *devicesList = wg_list_device_names(); - if (!devicesList) throw std::string("Unable to get device names"); - size_t len; - for ((device_name) = (devicesList), (len) = 0; ((len) = strlen(device_name)); (device_name) += (len) + 1) devs.push_back(std::string(device_name)); - free(devicesList); - } - - bool exist(std::string ifname) { - this->getAll(); - for (auto wg : devs) if (wg == ifname) return true; - return false; - } -}; - -void listDevices::Execute() { - List l; - try { - l.getAll(); - for (auto ifname : l.devs) { - listInfo setInfo; - setInfo.tunType = "kernel"; - deviceNames[ifname] = setInfo; - } - } catch (std::string err) { - SetError(err); - } - l.~List(); -} - -void deleteInterface::Execute() { - int status = wg_del_device(wgName.c_str()); - if (status < 0) SetError(std::string("Cannot delete interface, code status: ").append(std::to_string(status))); -} - -int createInterface(std::string &wgName) { - bool createInterface = true; - size_t len = 0; - char *device_name, *devicesList = wg_list_device_names(); - if (!!devicesList) { - for ((device_name) = (devicesList), (len) = 0; ((len) = strlen(device_name)); (device_name) += (len) + 1) { - if (std::string(device_name) == wgName) { - createInterface = false; - break; - } - } - free(devicesList); - } - if (createInterface) return wg_add_device(wgName.c_str()); - return 0; -} - -void setConfig::Execute() { - int res = createInterface(wgName); - if (res < 0) { - SetError(std::string("Cannot create wireguard interface, Code: ").append(std::to_string(res))); - return; - } - - // Set device struct - auto deviceStruct = new wg_device({}); - strncpy(deviceStruct->name, wgName.c_str(), wgName.length()); - - // Set private key - wg_key_from_base64(deviceStruct->private_key, privateKey.c_str()); - deviceStruct->flags = (wg_device_flags)WGDEVICE_HAS_PRIVATE_KEY; - - // Set public key - if (publicKey.length() > 0) { - wg_key_from_base64(deviceStruct->public_key, publicKey.c_str()); - deviceStruct->flags = (wg_device_flags)WGDEVICE_HAS_PUBLIC_KEY; - } - - // Port listenings - if (portListen > 0 && 25565 < portListen) { - deviceStruct->listen_port = portListen; - deviceStruct->flags = (wg_device_flags)(deviceStruct->flags|WGDEVICE_HAS_LISTEN_PORT); - } - - // Linux firewall mark - if (fwmark >= 0) { - deviceStruct->fwmark = fwmark; - deviceStruct->flags = (wg_device_flags)(deviceStruct->flags|WGDEVICE_HAS_FWMARK); - } - - // Replace Peers - if (replacePeers) deviceStruct->flags = (wg_device_flags)(deviceStruct->flags|WGDEVICE_REPLACE_PEERS); - - unsigned int peerIndex = 0; - for (auto it = peersVector.begin(); it != peersVector.end(); ++it) { - const std::string peerPubKey = it->first; - auto peerConfig = it->second; - peerIndex++; - - wg_peer *peerStruct = new wg_peer({}); - // Set public key - wg_key_from_base64(peerStruct->public_key, peerPubKey.c_str()); - peerStruct->flags = (wg_peer_flags)WGPEER_HAS_PUBLIC_KEY; - - // Remove Peer - if (peerConfig.removeMe) peerStruct->flags = (wg_peer_flags)(peerStruct->flags|WGPEER_REMOVE_ME); - else { - // Set preshared key if present - if (peerConfig.presharedKey.length() > 0) { - wg_key_from_base64(peerStruct->preshared_key, peerConfig.presharedKey.c_str()); - peerStruct->flags = (wg_peer_flags)(peerStruct->flags|WGPEER_HAS_PRESHARED_KEY); - } - - // Set Keepalive - if (peerConfig.keepInterval > 0) { - peerStruct->persistent_keepalive_interval = peerConfig.keepInterval; - peerStruct->flags = (wg_peer_flags)(peerStruct->flags|WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL); - } - - // Set endpoint - if (peerConfig.endpoint.length() > 0) { - sockaddr endpoint; - int ret, retries; - char *begin, *end; - char *Endpoint = strdup(peerConfig.endpoint.c_str()); - if (Endpoint[0] == '[') { - begin = &Endpoint[1]; - end = strchr(Endpoint, ']'); - if (!end) { - free(Endpoint); - SetError("Unable to find matching brace of endpoint"); - return; - } - *end++ = '\0'; - if (*end++ != ':' || !*end) { - free(Endpoint); - SetError("Unable to find port of endpoint"); - return; - } - } else { - begin = Endpoint; - end = strrchr(Endpoint, ':'); - if (!end || !*(end + 1)) { - free(Endpoint); - SetError("Unable to find port of endpoint"); - return; - } - *end++ = '\0'; - } - addrinfo *resolved; - addrinfo hints = { - ai_family: AF_UNSPEC, - ai_socktype: SOCK_DGRAM, - ai_protocol: IPPROTO_UDP - }; - #define min(a, b) ((a) < (b) ? (a) : (b)) - for (unsigned int timeout = 1000000;; timeout = min(20000000, timeout * 6 / 5)) { - ret = getaddrinfo(begin, end, &hints, &resolved); - if (!ret) break; - if (ret == EAI_NONAME || ret == EAI_FAIL || - #ifdef EAI_NODATA - ret == EAI_NODATA || - #endif - (retries >= 0 && !retries--)) { - free(Endpoint); - fprintf(stderr, "%s: `%s'\n", ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret), peerConfig.endpoint.c_str()); - SetError("Unable to resolve endpoint"); - return; - } - fprintf(stderr, "%s: `%s'. Trying again in %.2f seconds...\n", ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret), peerConfig.endpoint.c_str(), timeout / 1000000.0); - usleep(timeout); - } - if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(sockaddr_in)) || (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(sockaddr_in6))) { - memcpy(&endpoint, resolved->ai_addr, resolved->ai_addrlen); - memccpy(&peerStruct->endpoint.addr, &endpoint, 0, sizeof(peerStruct->endpoint.addr)); - if (resolved->ai_family == AF_INET) { - peerStruct->endpoint.addr4.sin_addr.s_addr = ((sockaddr_in *)&endpoint)->sin_addr.s_addr; - peerStruct->endpoint.addr4.sin_port = ((sockaddr_in *)&endpoint)->sin_port; - peerStruct->endpoint.addr4.sin_family = AF_INET; - } else { - peerStruct->endpoint.addr6.sin6_addr = ((struct sockaddr_in6 *)&endpoint)->sin6_addr; - peerStruct->endpoint.addr6.sin6_port = ((struct sockaddr_in6 *)&endpoint)->sin6_port; - peerStruct->endpoint.addr6.sin6_family = AF_INET6; - } - } else { - freeaddrinfo(resolved); - free(Endpoint); - SetError("Neither IPv4 nor IPv6 address found"); - return; - } - freeaddrinfo(resolved); - free(Endpoint); - } - - // Set allowed IPs - if (peerConfig.allowedIPs.size() > 0) { - peerStruct->flags = (wg_peer_flags)(peerStruct->flags|WGPEER_REPLACE_ALLOWEDIPS); - for (unsigned int allowIndex = 0; allowIndex < peerConfig.allowedIPs.size(); allowIndex++) { - auto ip = peerConfig.allowedIPs[allowIndex]; - unsigned long cidr = 0; - if (ip.find("/") != std::string::npos) { - cidr = std::stoi(ip.substr(ip.find("/")+1)); - ip = ip.substr(0, ip.find("/")); - } - wg_allowedip *newAllowedIP = new wg_allowedip({family: AF_UNSPEC}); - if (strchr(ip.c_str(), ':')) { - if (inet_pton(AF_INET6, ip.c_str(), &newAllowedIP->ip6) == 1) { - newAllowedIP->family = AF_INET6; - if (cidr == 0) cidr = 128; - } - } else { - if (inet_pton(AF_INET, ip.c_str(), &newAllowedIP->ip4) == 1) { - newAllowedIP->family = AF_INET; - if (cidr == 0) cidr = 32; - } - } - if (newAllowedIP->family == AF_UNSPEC || cidr <= 0) continue; - newAllowedIP->cidr = cidr; - if (allowIndex > 0) newAllowedIP->next_allowedip = peerStruct->first_allowedip; - peerStruct->first_allowedip = newAllowedIP; - } - } - } - - // Add to Peer struct - if (peerIndex > 0) peerStruct->next_peer = deviceStruct->first_peer; - deviceStruct->first_peer = peerStruct; - } - - // Set interface config - if ((res = wg_set_device(deviceStruct)) < 0) SetError(getKernelMesage(res)); -} - -const char* getHostAddress(bool addPort, const sockaddr* addr) { - char host[4096 + 1], service[512 + 1]; - static char buf[sizeof(host) + sizeof(service) + 4]; - memset(buf, 0, sizeof(buf)); - int ret; - socklen_t addr_len = 0; - if (addr->sa_family == AF_INET) addr_len = sizeof(struct sockaddr_in); - else if (addr->sa_family == AF_INET6) addr_len = sizeof(struct sockaddr_in6); - - ret = getnameinfo(addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST); - if (ret) { - strncpy(buf, gai_strerror(ret), sizeof(buf) - 1); - buf[sizeof(buf) - 1] = '\0'; - } else { - if (addPort) snprintf(buf, sizeof(buf), (addr->sa_family == AF_INET6 && strchr(host, ':')) ? "[%s]:%s" : "%s:%s", host, service); - else snprintf(buf, sizeof(buf), "%s", host); - } - return buf; -} - -std::string keyTo64(const uint8_t *key) { - wg_key_b64_string strKey; - wg_key_to_base64(strKey, key); - return strKey; -} - -void getConfig::Execute() { - int res; wg_device *device; - if ((res = wg_get_device(&device, strdup(wgName.c_str()))) < 0) { - SetError(std::string("Device not exists or cannot get config from this interface!, code error: ").append(std::to_string(res))); - return; - } - - if (device->flags & WGDEVICE_HAS_PRIVATE_KEY) privateKey = keyTo64(device->private_key); - if (device->flags & WGDEVICE_HAS_PUBLIC_KEY) publicKey = keyTo64(device->public_key); - if (device->listen_port > 0) portListen = device->listen_port; - - // Set Address array and get interface ip addresses - ifaddrs* ptr_ifaddrs = nullptr; - if(getifaddrs(&ptr_ifaddrs) > 0) { - for (ifaddrs* ptr_entry = ptr_ifaddrs; ptr_entry != nullptr; ptr_entry = ptr_entry->ifa_next) { - if (ptr_entry->ifa_addr == nullptr) continue; - else if (strcmp(ptr_entry->ifa_name, wgName.c_str()) != 0) continue; - else if (ptr_entry->ifa_addr->sa_family == AF_INET) Address.push_back(getHostAddress(false, ptr_entry->ifa_addr)); - else if (ptr_entry->ifa_addr->sa_family == AF_INET6) Address.push_back(getHostAddress(false, ptr_entry->ifa_addr)); - } - freeifaddrs(ptr_ifaddrs); - } - - wg_peer *peer; - for ((peer) = (device)->first_peer; (peer); (peer) = (peer)->next_peer) { - auto PeerConfig = Peer(); - if (peer->flags & WGPEER_HAS_PRESHARED_KEY) PeerConfig.presharedKey = keyTo64(peer->preshared_key); - if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL && peer->persistent_keepalive_interval > 0) PeerConfig.keepInterval = peer->persistent_keepalive_interval; - if (peer->endpoint.addr.sa_family == AF_INET||peer->endpoint.addr.sa_family == AF_INET6) PeerConfig.endpoint = getHostAddress(true, &peer->endpoint.addr); - if (peer->last_handshake_time.tv_sec > 0) PeerConfig.last_handshake = peer->last_handshake_time.tv_sec*1000; - if (peer->rx_bytes > 0) PeerConfig.rxBytes = peer->rx_bytes; - if (peer->tx_bytes > 0) PeerConfig.txBytes = peer->tx_bytes; - if (peer->first_allowedip) { - wg_allowedip *allowedip; - for ((allowedip) = (peer)->first_allowedip; (allowedip); (allowedip) = (allowedip)->next_allowedip) { - static char buf[INET6_ADDRSTRLEN + 1]; - memset(buf, 0, INET6_ADDRSTRLEN + 1); - if (allowedip->family == AF_INET) inet_ntop(AF_INET, &allowedip->ip4, buf, INET6_ADDRSTRLEN); - else if (allowedip->family == AF_INET6) inet_ntop(AF_INET6, &allowedip->ip6, buf, INET6_ADDRSTRLEN); - snprintf(buf + strlen(buf), INET6_ADDRSTRLEN - strlen(buf), "/%d", allowedip->cidr); - PeerConfig.allowedIPs.push_back(buf); - } - } - - peersVector[keyTo64(peer->public_key)] = PeerConfig; - } -} \ No newline at end of file diff --git a/addons/tools/wginterface-win.cpp b/addons/tools/wginterface-win.cpp deleted file mode 100644 index 8120e62..0000000 --- a/addons/tools/wginterface-win.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "wginterface.hh" -#include "win/shared.cpp" -#include - -const DEVPROPKEY devpkey_name = { { 0x65726957, 0x7547, 0x7261, { 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x4b, 0x65, 0x79 } }, DEVPROPID_FIRST_USABLE + 1 }; -#define IFNAMSIZ MAX_ADAPTER_NAME - 1 - -static WIREGUARD_CREATE_ADAPTER_FUNC *WireGuardCreateAdapter; -static WIREGUARD_OPEN_ADAPTER_FUNC *WireGuardOpenAdapter; -static WIREGUARD_CLOSE_ADAPTER_FUNC *WireGuardCloseAdapter; -static WIREGUARD_GET_ADAPTER_LUID_FUNC *WireGuardGetAdapterLUID; -static WIREGUARD_GET_RUNNING_DRIVER_VERSION_FUNC *WireGuardGetRunningDriverVersion; -static WIREGUARD_DELETE_DRIVER_FUNC *WireGuardDeleteDriver; -static WIREGUARD_SET_LOGGER_FUNC *WireGuardSetLogger; -static WIREGUARD_SET_ADAPTER_LOGGING_FUNC *WireGuardSetAdapterLogging; -static WIREGUARD_GET_ADAPTER_STATE_FUNC *WireGuardGetAdapterState; -static WIREGUARD_SET_ADAPTER_STATE_FUNC *WireGuardSetAdapterState; -static WIREGUARD_GET_CONFIGURATION_FUNC *WireGuardGetConfiguration; -static WIREGUARD_SET_CONFIGURATION_FUNC *WireGuardSetConfiguration; - -unsigned long maxName() { - return IFNAMSIZ; -} - -std::string getErrorString(DWORD errorMessageID) { - if (errorMessageID == 0 || errorMessageID < 0) std::string("Error code: ").append(std::to_string(errorMessageID)); - LPSTR messageBuffer = nullptr; - //Ask Win32 to give us the string version of that message ID. - //The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be). - size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); - //Copy the error message into a std::string. - std::string message(messageBuffer, size); - //Free the Win32's string's buffer. - LocalFree(messageBuffer); - return std::string("Error code: ").append(std::to_string(errorMessageID)).append(", Message: ").append(message); -} - -std::string startAddon(const Napi::Env env, Napi::Object exports) { - if (!IsRunAsAdmin()) return "Run nodejs with administrator privilegies"; - auto DLLPATH = exports.Get("WIN32DLLPATH"); - if (!(DLLPATH.IsString())) return "Require WIREGUARD_DLL_PATH in addon load!"; - LPCWSTR dllPath = toLpcwstr(DLLPATH.ToString()); - - HMODULE WireGuardDll = LoadLibraryExW(dllPath, NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!WireGuardDll) return std::string("Failed to initialize WireGuardNT, ").append(getErrorString(GetLastError()));; - #define X(Name) ((*(FARPROC *)&Name = GetProcAddress(WireGuardDll, #Name)) == NULL) - if (X(WireGuardCreateAdapter) || X(WireGuardOpenAdapter) || X(WireGuardCloseAdapter) || X(WireGuardGetAdapterLUID) || X(WireGuardGetRunningDriverVersion) || X(WireGuardDeleteDriver) || X(WireGuardSetLogger) || X(WireGuardSetAdapterLogging) || X(WireGuardGetAdapterState) || X(WireGuardSetAdapterState) || X(WireGuardGetConfiguration) || X(WireGuardSetConfiguration)) - #undef X - { - DWORD LastError = GetLastError(); - FreeLibrary(WireGuardDll); - SetLastError(LastError); - return std::string("Failed to set Functions from WireGuardNT DLL, ").append(getErrorString(GetLastError()));; - } - - return ""; -} - -std::string versionDrive() { - WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardCreateAdapter(L"getWgVersion", L"Wireguard-tools.js", NULL); - DWORD Version = WireGuardGetRunningDriverVersion(); - if (Version == 0) { - auto statusErr = GetLastError(); - WireGuardCloseAdapter(Adapter); - if (statusErr == ERROR_FILE_NOT_FOUND) return "Driver not loaded"; - return std::string("Cannot get version drive, ").append(getErrorString(GetLastError())); - } - WireGuardCloseAdapter(Adapter); - return std::string("WireGuardNT v").append(std::to_string((Version >> 16) & 0xff)).append(".").append(std::to_string((Version >> 0) & 0xff)); -} - -void listDevices::Execute() { - std::vector arrayPrefix; - arrayPrefix.push_back("ProtectedPrefix\\Administrators\\WireGuard\\"); - arrayPrefix.push_back("WireGuard\\"); - - WIN32_FIND_DATA find_data; - HANDLE find_handle; - for (auto &preit : arrayPrefix) { - int ret = 0; - find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data); - if (find_handle == INVALID_HANDLE_VALUE) continue; - - char *iface; - do { - if (strncmp(preit.c_str(), find_data.cFileName, strlen(preit.c_str()))) continue; - iface = find_data.cFileName + strlen(preit.c_str()); - listInfo setInfo; - setInfo.tunType = "userspace"; - setInfo.pathSock = std::string("\\\\.\\pipe\\").append(preit).append(iface); - deviceNames[std::string(iface)] = setInfo; - } while (FindNextFile(find_handle, &find_data)); - FindClose(find_handle); - if (ret < 0) return SetError(std::string("Erro code: ").append(std::to_string(ret))); - } - - HDEVINFO dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, L"SWD\\WireGuard", NULL, DIGCF_PRESENT, NULL, NULL, NULL); - if (dev_info == INVALID_HANDLE_VALUE) return SetError("Cannot get devices"); - - for (DWORD i = 0;; ++i) { - DWORD buf_len; - WCHAR adapter_name[MAX_ADAPTER_NAME]; - SP_DEVINFO_DATA dev_info_data; - dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA); - DEVPROPTYPE prop_type; - ULONG status, problem_code; - char *interface_name; - - if (!SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data)) { - if (GetLastError() == ERROR_NO_MORE_ITEMS) break; - continue; - } - - if (!SetupDiGetDevicePropertyW(dev_info, &dev_info_data, &devpkey_name, &prop_type, (PBYTE)adapter_name, sizeof(adapter_name), NULL, 0) || prop_type != DEVPROP_TYPE_STRING) continue; - adapter_name[_countof(adapter_name) - 1] = L'0'; - if (!adapter_name[0]) continue; - buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, NULL, 0, NULL, NULL); - if (!buf_len) continue; - interface_name = (char *)malloc(buf_len); - if (!interface_name) continue; - buf_len = WideCharToMultiByte(CP_UTF8, 0, adapter_name, -1, interface_name, buf_len, NULL, NULL); - if (!buf_len) { - free(interface_name); - continue; - } - - if (CM_Get_DevNode_Status(&status, &problem_code, dev_info_data.DevInst, 0) == CR_SUCCESS && (status & (DN_DRIVER_LOADED | DN_STARTED)) == (DN_DRIVER_LOADED | DN_STARTED)) { - listInfo setInfo; - setInfo.tunType = "kernel"; - deviceNames[std::string(interface_name)] = setInfo; - } - free(interface_name); - } - SetupDiDestroyDeviceInfoList(dev_info); -} - -void deleteInterface::Execute() { - WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(wgName)); - if (!Adapter) return SetError("This interface not exists in Wireguard-Tools.js addon!"); - if (!(WireGuardSetAdapterState(Adapter, WIREGUARD_ADAPTER_STATE::WIREGUARD_ADAPTER_STATE_DOWN))) return SetError(std::string("Failed to set down interface, ").append(getErrorString(GetLastError()))); - WireGuardCloseAdapter(Adapter); -} - -/** - * Change point from calloc or malloc - * - * T: to - * C: From - */ -template C* changePoint(T *x) { - // reinterpret_cast(((char*)x) + sizeof(WIREGUARD_PEER)); - // std::cout << "Sizeof: " << sizeof(C) << ", " << typeid(T).name() << " -> " << typeid(C).name() << std::endl; - return reinterpret_cast(((char*)x) + sizeof(T)); -} - -void getConfig::Execute() { - WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(wgName)); - if (!Adapter) return SetError("This interface not exists in Wireguard-Tools.js addon!"); - NET_LUID InterfaceLuid; - WireGuardGetAdapterLUID(Adapter, &InterfaceLuid); - try { - for (auto aip : getIpAddr(InterfaceLuid)) Address.push_back(aip); - } catch (std::string err) { - return SetError(err); - } - - DWORD buf_len = 0; - WIREGUARD_INTERFACE *wg_iface = nullptr; - - while (!(WireGuardGetConfiguration(Adapter, wg_iface, &buf_len))) { - free(wg_iface); - if (GetLastError() != ERROR_MORE_DATA) return SetError((std::string("Failed get interface config, code: ")).append(std::to_string(GetLastError()))); - wg_iface = (WIREGUARD_INTERFACE *)malloc(buf_len); - if (!wg_iface) return SetError(std::string("Failed get interface config, ").append(std::to_string(-errno))); - } - - if (wg_iface->Flags & WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_PRIVATE_KEY) privateKey = wgKeys::toString(wg_iface->PrivateKey); - if (wg_iface->Flags & WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_PUBLIC_KEY) publicKey = wgKeys::toString(wg_iface->PublicKey); - portListen = 0; - if (wg_iface->Flags & WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_LISTEN_PORT) portListen = wg_iface->ListenPort; - - - WIREGUARD_PEER *wg_peer = changePoint(wg_iface); - for (DWORD i = 0; i < wg_iface->PeersCount; i++) { - auto pubKey = wgKeys::toString(wg_peer->PublicKey); - Peer peerConfig; - peerConfig.last_handshake = 0; - peerConfig.txBytes = wg_peer->TxBytes; - peerConfig.rxBytes = wg_peer->RxBytes; - - if (wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PRESHARED_KEY) peerConfig.presharedKey = wgKeys::toString(wg_peer->PresharedKey); - if (wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_ENDPOINT) peerConfig.endpoint = parseEndpoint(&wg_peer->Endpoint); - if (wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PERSISTENT_KEEPALIVE) peerConfig.keepInterval = wg_peer->PersistentKeepalive; - if (wg_peer->LastHandshake > 0) peerConfig.last_handshake = (wg_peer->LastHandshake / 10000000 - 11644473600LL) * 1000; - - WIREGUARD_ALLOWED_IP* wg_aip = changePoint(wg_peer); - for (DWORD __aip = 0; __aip < wg_peer->AllowedIPsCount; __aip++) { - char saddr[INET6_ADDRSTRLEN]; - if (wg_aip->AddressFamily == AF_INET) { - inet_ntop(AF_INET, &wg_aip->Address.V6, saddr, INET_ADDRSTRLEN); - peerConfig.allowedIPs.push_back(std::string(saddr).append("/").append(std::to_string(wg_aip->Cidr))); - } else if (wg_aip->AddressFamily == AF_INET6) { - inet_ntop(AF_INET6, &wg_aip->Address.V6, saddr, INET6_ADDRSTRLEN); - peerConfig.allowedIPs.push_back(std::string(saddr).append("/").append(std::to_string(wg_aip->Cidr))); - } - ++wg_aip; - } - wg_peer = reinterpret_cast(wg_aip); - peersVector[pubKey] = peerConfig; - } - - free(wg_iface); -} - -void setConfig::Execute() { - DWORD buf_len = sizeof(WIREGUARD_INTERFACE); - for (auto peer : peersVector) { - if (DWORD_MAX - buf_len < sizeof(WIREGUARD_PEER)) return SetError("Buffer overflow"); - buf_len += sizeof(WIREGUARD_PEER); - for (auto aip : peer.second.allowedIPs) { - if (DWORD_MAX - buf_len < sizeof(WIREGUARD_ALLOWED_IP)) return SetError("Buffer overflow"); - buf_len += sizeof(WIREGUARD_ALLOWED_IP); - } - } - WIREGUARD_INTERFACE *wg_iface = reinterpret_cast(calloc(1, buf_len)); - if (!wg_iface) return SetError("Cannot alloc buff"); - wg_iface->PeersCount = 0; - - wgKeys::stringToKey(wg_iface->PrivateKey, privateKey); - wg_iface->Flags = WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_PRIVATE_KEY; - - wg_iface->ListenPort = portListen; - if (portListen >= 0 && 65535 <= portListen) wg_iface->Flags = (WIREGUARD_INTERFACE_FLAG)(wg_iface->Flags|WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_HAS_LISTEN_PORT); - - if (replacePeers) wg_iface->Flags = (WIREGUARD_INTERFACE_FLAG)(wg_iface->Flags|WIREGUARD_INTERFACE_FLAG::WIREGUARD_INTERFACE_REPLACE_PEERS); - - WIREGUARD_ALLOWED_IP *wg_aip; - WIREGUARD_PEER *wg_peer = changePoint(wg_iface); - for (auto __peer : peersVector) { - auto peerPublicKey = __peer.first; auto peerConfig = __peer.second; - try { - wgKeys::stringToKey(wg_peer->PublicKey, peerPublicKey); - } catch (std::string &err) { - SetError(err); - free(wg_iface); - return; - } - wg_peer->Flags = WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PUBLIC_KEY; - wg_peer->AllowedIPsCount = 0; - wg_iface->PeersCount++; - - if (peerConfig.removeMe) { - wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_REMOVE); - wg_peer = changePoint(wg_peer); - } else { - if (peerConfig.presharedKey.size() == B64_WG_KEY_LENGTH) { - try { - wgKeys::stringToKey(wg_peer->PresharedKey, peerConfig.presharedKey); - wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PRESHARED_KEY); - } catch (std::string &err) { - SetError(err); - free(wg_iface); - return; - } - } - - wg_peer->PersistentKeepalive = peerConfig.keepInterval; - if (peerConfig.keepInterval >= 0) wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_PERSISTENT_KEEPALIVE); - - if (peerConfig.endpoint.size() > 0) { - try { - insertEndpoint(&wg_peer->Endpoint, peerConfig.endpoint.c_str()); - wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_HAS_ENDPOINT); - } catch (std::string &err) { - SetError(std::string("Cannot parse endpoint, ").append(err)); - free(wg_iface); - return; - } - } - - wg_aip = changePoint(wg_peer); - for (auto aip : peerConfig.allowedIPs) { - unsigned long cidr = 0; - if (aip.find("/") != std::string::npos) { - cidr = std::stoi(aip.substr(aip.find("/")+1)); - aip = aip.substr(0, aip.find("/")); - } - aip = aip.substr(0, aip.find("/")); - wg_aip->AddressFamily = strchr(aip.c_str(), ':') ? AF_INET6 : AF_INET; - auto status = wg_aip->AddressFamily == AF_INET6 ? inet_pton(wg_aip->AddressFamily, aip.c_str(), &wg_aip->Address.V6) : inet_pton(wg_aip->AddressFamily, aip.c_str(), &wg_aip->Address.V4); - if (status == 1) { - if (cidr == 0) cidr = wg_aip->AddressFamily == AF_INET6 ? 128 : 32; - } else continue; - wg_aip->Cidr = cidr; - wg_peer->AllowedIPsCount++; - wg_aip = changePoint(wg_aip); - if (!(wg_peer->Flags & WIREGUARD_PEER_FLAG::WIREGUARD_PEER_REPLACE_ALLOWED_IPS)) wg_peer->Flags = (WIREGUARD_PEER_FLAG)(wg_peer->Flags|WIREGUARD_PEER_FLAG::WIREGUARD_PEER_REPLACE_ALLOWED_IPS); - } - wg_peer = reinterpret_cast(((char*)wg_aip)); - } - } - - WIREGUARD_ADAPTER_HANDLE Adapter = WireGuardOpenAdapter(toLpcwstr(wgName)); - if (!Adapter) Adapter = WireGuardCreateAdapter(toLpcwstr(wgName), L"Wireguard-tools.js", NULL); - if (!Adapter) SetError(std::string("Failed to create adapter, ").append(getErrorString(GetLastError()))); - else if (!WireGuardSetConfiguration(Adapter, reinterpret_cast(wg_iface), buf_len)) { - auto status = GetLastError(); - SetError(std::string("Failed to set interface config, ").append(getErrorString(status))); - WireGuardCloseAdapter(Adapter); - } else if (!WireGuardSetAdapterState(Adapter, WIREGUARD_ADAPTER_STATE::WIREGUARD_ADAPTER_STATE_UP)) { - auto status = GetLastError(); - SetError(std::string("Failed to set interface up, ").append(getErrorString(status))); - WireGuardCloseAdapter(Adapter); - } else { - if (Address.size() > 0) { - std::string IPv4, IPv6; - for (auto aip : Address) { - aip = aip.substr(0, aip.find("/")); - auto family = strchr(aip.c_str(), ':') ? AF_INET6 : AF_INET; - SOCKADDR_INET address; - int status = family == AF_INET ? inet_pton(family, aip.c_str(), &address.Ipv4.sin_addr) : inet_pton(family, aip.c_str(), &address.Ipv6.sin6_addr); - if (status != 1) continue; - char saddr[INET6_ADDRSTRLEN]; - family == AF_INET ? inet_ntop(AF_INET, &address.Ipv4.sin_addr, saddr, INET_ADDRSTRLEN) : inet_ntop(AF_INET6, &address.Ipv6.sin6_addr, saddr, INET6_ADDRSTRLEN); - if (family == AF_INET) IPv4 = std::string(saddr); - // else IPv6 = std::string(saddr); - } - - if (IPv4.size() > 0 || IPv6.size() > 0) { - NET_LUID InterfaceLuid; - WireGuardGetAdapterLUID(Adapter, &InterfaceLuid); - auto setStatus = insertIpAddr(InterfaceLuid, IPv4, IPv6); - if (setStatus.size() > 0) SetError(setStatus); - } - } - } - free(wg_iface); -} \ No newline at end of file diff --git a/addons/tools/wginterface.cpp b/addons/tools/wginterface.cpp deleted file mode 100644 index d75ba3c..0000000 --- a/addons/tools/wginterface.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include -#include "wginterface.hh" - -Napi::Object Init(Napi::Env initEnv, Napi::Object exports) { - /// Call Addon - #ifdef ONSTARTADDON - auto status = startAddon(initEnv, exports); - if (status.length() >= 1) { - Napi::Error::New(initEnv, status).ThrowAsJavaScriptException(); - return exports; - } - #endif - - // Wireguard constants set - const Napi::Object constants = Napi::Object::New(initEnv); - // Set wireguard version if present - constants.Set("driveVersion", versionDrive()); - - // Wireguard max name length - constants.Set("nameLength", maxName()); - - constants.Set("base64Length", B64_WG_KEY_LENGTH); - constants.Set("keyLength", WG_KEY_LENGTH); - - // Set addon constants - exports.Set("constants", constants); - - // Function's - #ifdef USERSPACE_GO - exports.Set("createTun", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { return info.Env().Undefined(); })); - exports.Set("deleteTun", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { return info.Env().Undefined(); })); - exports.Set("checkTun", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { return info.Env().Undefined(); })); - exports.Set("getTun", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { return info.Env().Undefined(); })); - #endif - - #ifdef SETCONFIG - exports.Set("setConfig", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { - const Napi::Env env = info.Env(); - const auto wgName = info[0]; - const auto wgConfig = info[1]; - Napi::Value ret = env.Undefined(); - if (!(wgName.IsString())) { - Napi::Error::New(env, "Require wireguard interface name").ThrowAsJavaScriptException(); - return env.Undefined(); - } else if (wgName.ToString().Utf8Value().length() >= maxName()) { - Napi::Error::New(env, "interface name is so long").ThrowAsJavaScriptException(); - return env.Undefined(); - } else if (!(wgConfig.IsObject())) { - Napi::Error::New(env, "Require wireguard config object").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - try { - auto worker = new setConfig(env, wgName.ToString().Utf8Value(), wgConfig.ToObject()); - worker->Queue(); - return worker->setPromise.Promise(); - } catch (const Napi::Error &err) { - err.ThrowAsJavaScriptException(); - } - return ret; - })); - #endif - - #ifdef DELIFACE - exports.Set("deleteInterface", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { - const Napi::Env env = info.Env(); - const auto wgName = info[0]; - if (!(wgName.IsString())) { - Napi::Error::New(env, "Require wireguard interface name").ThrowAsJavaScriptException(); - return env.Undefined(); - } else if (wgName.ToString().Utf8Value().length() >= maxName()) { - Napi::Error::New(env, "interface name is so long").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - auto worker = new deleteInterface(env, wgName.ToString().Utf8Value()); - worker->Queue(); - return worker->deletePromise.Promise(); - })); - #endif - - #ifdef GETCONFIG - exports.Set("getConfig", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { - const Napi::Env env = info.Env(); - const auto wgName = info[0]; - if (!(wgName.IsString())) { - Napi::Error::New(env, "Require wireguard interface name").ThrowAsJavaScriptException(); - return env.Undefined(); - } else if (wgName.ToString().Utf8Value().length() >= maxName()) { - Napi::Error::New(env, "interface name is so long").ThrowAsJavaScriptException(); - return env.Undefined(); - } - - try { - auto worker = new getConfig(env, wgName.ToString().Utf8Value()); - worker->Queue(); - return worker->getPromise.Promise(); - } catch (const Napi::Error &err) { - err.ThrowAsJavaScriptException(); - } - return env.Undefined(); - })); - #endif - - #ifdef LISTDEV - exports.Set("listDevices", Napi::Function::New(initEnv, [&](const Napi::CallbackInfo &info) -> Napi::Value { - const Napi::Env env = info.Env(); - auto worker = new listDevices(env); - worker->Queue(); - return worker->listDevicesPromise.Promise(); - })); - #endif - return exports; -} -NODE_API_MODULE(addon, Init); \ No newline at end of file diff --git a/addons/tools/wginterface.hh b/addons/tools/wginterface.hh deleted file mode 100644 index 481af29..0000000 --- a/addons/tools/wginterface.hh +++ /dev/null @@ -1,338 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -// Get wireguard max name length -unsigned long maxName(); - -// Get wireguard version -std::string versionDrive(); - -// On start module call this function -std::string startAddon(const Napi::Env env, Napi::Object exports); - -class deleteInterface : public Napi::AsyncWorker { - private: - std::string wgName; - public: - deleteInterface(const Napi::Env env, std::string name): AsyncWorker(env), wgName{name}, deletePromise{env} {} - ~deleteInterface() {} - const Napi::Promise::Deferred deletePromise; - - void OnError(const Napi::Error &e) override { - Napi::HandleScope scope(Env()); - deletePromise.Reject(e.Value()); - } - - void OnOK() override { - Napi::HandleScope scope(Env()); - deletePromise.Resolve(Env().Undefined()); - }; - - // Set platform Execute script - void Execute() override; -}; - -class listInfo { - public: - std::string tunType, pathSock; -}; - -class listDevices : public Napi::AsyncWorker { - private: - std::map deviceNames; - public: - ~listDevices() {} - listDevices(const Napi::Env env) : AsyncWorker(env), listDevicesPromise{env} {} - const Napi::Promise::Deferred listDevicesPromise; - - void OnError(const Napi::Error& e) override { - Napi::HandleScope scope(Env()); - listDevicesPromise.Reject(e.Value()); - } - - void OnOK() override { - Napi::HandleScope scope(Env()); - const Napi::Env env = Env(); - const auto deviceArray = Napi::Array::New(env); - for (auto it : deviceNames) { - auto name = it.first; auto infoSrc = it.second; - auto info = Napi::Object::New(env); - info.Set("name", name); - info.Set("from", infoSrc.tunType); - if (infoSrc.pathSock.size() > 0) info.Set("path", infoSrc.pathSock); - deviceArray.Set(deviceArray.Length(), info); - } - listDevicesPromise.Resolve(deviceArray); - }; - void Execute() override; -}; - -class Peer { - public: - // Remove specifies if the peer with this public key should be removed - // from a device's peer list. - bool removeMe; - - // PresharedKey is an optional preshared key which may be used as an - // additional layer of security for peer communications. - std::string presharedKey; - - // Endpoint is the most recent source address used for communication by - // this Peer. - std::string endpoint; - - // AllowedIPs specifies which IPv4 and IPv6 addresses this peer is allowed - // to communicate on. - // - // 0.0.0.0/0 indicates that all IPv4 addresses are allowed, and ::/0 - // indicates that all IPv6 addresses are allowed. - std::vector allowedIPs; - - // PersistentKeepaliveInterval specifies how often an "empty" packet is sent - // to a peer to keep a connection alive. - // - // A value of 0 indicates that persistent keepalives are disabled. - unsigned int keepInterval = 0; - - // LastHandshakeTime indicates the most recent time a handshake was performed - // with this peer. - // - // A zero-value time.Time indicates that no handshake has taken place with - // this peer. - long long last_handshake = 0; - - // rxBytes indicates the number of bytes received from this peer. - unsigned long long rxBytes = 0; - - // txBytes indicates the number of bytes transmitted to this peer. - unsigned long long txBytes = 0; - - // ProtocolVersion specifies which version of the WireGuard protocol is used - // for this Peer. - // - // A value of 0 indicates that the most recent protocol version will be used. - int ProtocolVersion = 0; -}; - -/* -Configure uma interface do Wireguard. -*/ -class setConfig : public Napi::AsyncWorker { - private: - // Wireguard interface name (required) - std::string wgName; - - // Wireguard private key (required) - std::string privateKey; - - // Wireguard interface publicKey - std::string publicKey; - - // Wireguard port listen - unsigned short portListen = 0; - - // FirewallMark specifies a device's firewall mark - // else set to 0, the firewall mark will be cleared. - int fwmark = -1; - - // Interface address'es - std::vector Address; - - // Replace peers - bool replacePeers = false; - - // Wireguard peers, Map: - std::map peersVector; - public: - const Napi::Promise::Deferred setPromise; - - void OnOK() override { - Napi::HandleScope scope(Env()); - // Callback().Call({ Env().Undefined() }); - setPromise.Resolve(Env().Undefined()); - }; - - void OnError(const Napi::Error& e) override { - Napi::HandleScope scope(Env()); - // Callback().Call({ e.Value() }); - setPromise.Reject(e.Value()); - } - - ~setConfig() {} - setConfig(const Napi::Env env, std::string name, const Napi::Object &config) : AsyncWorker(env), wgName{name}, setPromise{env} { - // Wireguard public key - const auto sppk = config.Get("publicKey"); - if (sppk.IsString()) { - publicKey = sppk.ToString().Utf8Value(); - if (publicKey.length() != B64_WG_KEY_LENGTH) throw Napi::Error::New(env, "Set valid publicKey"); - } - - // Private key - const auto sprk = config.Get("privateKey"); - if (!(sprk.IsString())) throw Napi::Error::New(env, "privateKey is empty"); - privateKey = sprk.ToString().Utf8Value(); - if (privateKey.length() != B64_WG_KEY_LENGTH) throw Napi::Error::New(env, (std::string("Set valid privateKey ")).append(std::to_string(privateKey.length()))); - - // Port to listen Wireguard interface - const auto spor = config.Get("portListen"); - if (spor.IsNumber() && (spor.ToNumber().Int32Value() >= 0 && spor.ToNumber().Int32Value() <= 65535)) portListen = spor.ToNumber().Int32Value(); - - // Firewall mark - const auto sfw = config.Get("fwmark"); - if (sfw.IsNumber() && (sfw.ToNumber().Uint32Value() >= 0)) fwmark = sfw.ToNumber().Uint32Value(); - else fwmark = -1; - - const auto setAddress = config.Get("address"); - if (setAddress.IsArray()) { - const Napi::Array addrs = setAddress.As(); - for (unsigned int i = 0; i < addrs.Length(); i++) { - if (addrs.Get(i).IsString()) Address.push_back(addrs.Get(i).ToString().Utf8Value()); - } - } - - // Replace peers - const auto setReplace = config.Get("replacePeers"); - if (setReplace.IsBoolean()) replacePeers = setReplace.ToBoolean().Value(); - - // Peers - const auto speers = config.Get("peers"); - if (speers.IsObject()) { - const Napi::Object Peers = speers.ToObject(); - const Napi::Array Keys = Peers.GetPropertyNames(); - for (unsigned int peerIndex = 0; peerIndex < Keys.Length(); peerIndex++) { - const auto peerPubKey = Keys[peerIndex]; - if (peerPubKey.IsString() && Peers.Get(Keys[peerIndex]).IsObject()) { - std::string ppkey = peerPubKey.ToString().Utf8Value(); - if (ppkey.length() != B64_WG_KEY_LENGTH) throw Napi::Error::New(env, std::string("Set valid peer publicKey, value: ").append(ppkey)); - const Napi::Object peerConfigObject = Peers.Get(Keys[peerIndex]).ToObject(); - - Peer peerConfig = Peer(); - const auto removeMe = peerConfigObject.Get("removeMe"); - if (removeMe.IsBoolean() && removeMe.ToBoolean().Value()) peerConfig.removeMe = true; - else { - // Preshared key - const auto pprekey = peerConfigObject.Get("presharedKey"); - if (pprekey.IsString()) { - peerConfig.presharedKey = pprekey.ToString().Utf8Value(); - if (peerConfig.presharedKey.length() != B64_WG_KEY_LENGTH) throw Napi::Error::New(env, "Set valid peer presharedKey"); - } - - // Keep interval - const auto pKeepInterval = peerConfigObject.Get("keepInterval"); - if (pKeepInterval.IsNumber() && (pKeepInterval.ToNumber().Int32Value() > 0 && pKeepInterval.ToNumber().Int32Value() <= 65535)) peerConfig.keepInterval = pKeepInterval.ToNumber().Int32Value(); - - // Peer endpoint - const auto pEndpoint = peerConfigObject.Get("endpoint"); - if (pEndpoint.IsString()) peerConfig.endpoint = pEndpoint.ToString().Utf8Value(); - - // Allowed ip's array - const auto pAllowedIPs = peerConfigObject.Get("allowedIPs"); - if (pAllowedIPs.IsArray()) { - const auto AllowedIps = pAllowedIPs.As(); - for (uint32_t allIndex = 0; allIndex < AllowedIps.Length(); allIndex++) { - if (AllowedIps.Get(allIndex).IsString()) peerConfig.allowedIPs.push_back(AllowedIps.Get(allIndex).ToString().Utf8Value()); - } - } - } - - // Insert peer - peersVector[ppkey] = peerConfig; - } - } - } - } - - // Set platform Execute script - void Execute() override; -}; - -class getConfig : public Napi::AsyncWorker { - private: - // Wireguard interface name (required) - std::string wgName; - - // Wireguard private key (required) - std::string privateKey; - - // Wireguard interface publicKey - std::string publicKey; - - // Wireguard port listen - unsigned int portListen; - - // FirewallMark specifies a device's firewall mark - // else set to 0, the firewall mark will be cleared. - int fwmark = -1; - - // Interface address'es - std::vector Address; - - /* - Wireguard peers - Map: - */ - std::map peersVector; - public: - ~getConfig() {} - getConfig(const Napi::Env env, std::string name): AsyncWorker(env), wgName{name}, getPromise{env} {} - const Napi::Promise::Deferred getPromise; - - void OnError(const Napi::Error& e) override { - Napi::HandleScope scope(Env()); - getPromise.Reject(e.Value()); - } - - void OnOK() override { - Napi::HandleScope scope(Env()); - const Napi::Env env = Env(); - const auto config = Napi::Object::New(env); - - if (privateKey.length() == B64_WG_KEY_LENGTH) config.Set("privateKey", privateKey); - if (publicKey.length() == B64_WG_KEY_LENGTH) config.Set("publicKey", publicKey); - if (portListen >= 0 && portListen <= 65535) config.Set("portListen", portListen); - if (fwmark >= 0) config.Set("fwmark", fwmark); - if (Address.size() > 0) { - const auto Addrs = Napi::Array::New(env); - for (auto &addr : Address) Addrs.Set(Addrs.Length(), addr); - config.Set("address", Addrs); - } - - // Peer object - const auto PeersObject = Napi::Object::New(env); - for (auto &peer : peersVector) { - const auto PeerObject = Napi::Object::New(env); - auto peerConfig = peer.second; - - if (peerConfig.presharedKey.length() == B64_WG_KEY_LENGTH) PeerObject.Set("presharedKey", peerConfig.presharedKey); - if (peerConfig.keepInterval > 0 && peerConfig.keepInterval <= 65535) PeerObject.Set("keepInterval", peerConfig.keepInterval); - if (peerConfig.endpoint.length() > 0) PeerObject.Set("endpoint", peerConfig.endpoint); - if (peerConfig.rxBytes >= 0) PeerObject.Set("rxBytes", Napi::BigInt::New(env, (uint64_t)peerConfig.rxBytes)); - if (peerConfig.txBytes >= 0) PeerObject.Set("txBytes", Napi::BigInt::New(env, (uint64_t)peerConfig.txBytes)); - if (peerConfig.last_handshake >= 0) { - PeerObject.Set("lastHandshake", Napi::Date::New(env, peerConfig.last_handshake)); - PeerObject.Set("lastHandshakeBigint", peerConfig.last_handshake); // Debug to windows - } - if (peerConfig.allowedIPs.size() > 0) { - const auto allowedIPs = Napi::Array::New(env); - for (auto &ip : peerConfig.allowedIPs) allowedIPs.Set(allowedIPs.Length(), ip); - PeerObject.Set("allowedIPs", allowedIPs); - } - - // const std::string peerPubKey = peer.first; - PeersObject.Set(peer.first, PeerObject); - } - - // Set peers to object - config.Set("peers", PeersObject); - - // Resolve config json - getPromise.Resolve(config); - }; - - // Set platform Execute script - void Execute() override; -}; diff --git a/addons/tools/win/shared.cpp b/addons/tools/win/shared.cpp deleted file mode 100644 index e73fbb1..0000000 --- a/addons/tools/win/shared.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Function to check if the current user has administrator privileges -bool IsRunAsAdmin() -{ - BOOL fRet = FALSE; - HANDLE hToken = NULL; - if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { - TOKEN_ELEVATION Elevation; - DWORD cbSize = sizeof(TOKEN_ELEVATION); - if (GetTokenInformation(hToken, TokenElevation, &Elevation, sizeof(Elevation), &cbSize)) { - fRet = Elevation.TokenIsElevated; - } - } - if (hToken) CloseHandle(hToken); - return !!fRet; -} - -LPCWSTR toLpcwstr(std::string s) { - wchar_t* wString = new wchar_t[s.length()+1]; - MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, wString, s.length()+1); - return wString; -} - -int parse_dns_retries() { - unsigned long ret; - char *retries = getenv("WG_ENDPOINT_RESOLUTION_RETRIES"), *end; - - if (!retries) return 15; - if (!strcmp(retries, "infinity")) return -1; - - ret = strtoul(retries, &end, 10); - if (*end || ret > INT_MAX) { - fprintf(stderr, "Unable to parse WG_ENDPOINT_RESOLUTION_RETRIES: `%s'\n", retries); - exit(1); - } - return (int)ret; -} - -void insertEndpoint(SOCKADDR_INET *endpoint, std::string value) { - int ret, retries = parse_dns_retries(); - char *begin, *end; - auto mmutable = strdup(value.c_str()); - if (!mmutable) throw std::string("strdup"); - if (!value.size()) { - free(mmutable); - throw std::string("Unable to parse empty endpoint"); - } - if (mmutable[0] == '[') { - begin = &mmutable[1]; - end = strchr(mmutable, ']'); - if (!end) { - free(mmutable); - throw std::string("Unable to find matching brace of endpoint: ").append(value); - } - *end++ = '\0'; - if (*end++ != ':' || !*end) { - free(mmutable); - throw std::string("Unable to find port of endpoint: ").append(value); - } - } else { - begin = mmutable; - end = strrchr(mmutable, ':'); - if (!end || !*(end + 1)) { - free(mmutable); - throw std::string("Unable to find port of endpoint: ").append(value); - } - *end++ = '\0'; - } - - - ADDRINFOA *resolved; - // #define min(a, b) ((a) < (b) ? (a) : (b)) - for (unsigned int timeout = 1000000;; timeout = ((20000000) < (timeout * 6 / 5) ? (20000000) : (timeout * 6 / 5))) { - // ret = getaddrinfo(begin, end, &hints, &resolved); - ret = getaddrinfo(begin, end, NULL, &resolved); - if (!ret) break; - /* The set of return codes that are "permanent failures". All other possibilities are potentially transient. - * - * This is according to https://sourceware.org/glibc/wiki/NameResolver which states: - * "From the perspective of the application that calls getaddrinfo() it perhaps - * doesn't matter that much since EAI_FAIL, EAI_NONAME and EAI_NODATA are all - * permanent failure codes and the causes are all permanent failures in the - * sense that there is no point in retrying later." - * - * So this is what we do, except FreeBSD removed EAI_NODATA some time ago, so that's conditional. - */ - if (ret == EAI_NONAME || ret == EAI_FAIL || - #ifdef EAI_NODATA - ret == EAI_NODATA || - #endif - (retries >= 0 && !retries--)) { - free(mmutable); - throw std::string("Error code: ").append(std::to_string(ret)); - } - std::this_thread::sleep_for(std::chrono::microseconds(timeout)); - } - - if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(SOCKADDR_IN))) memcpy(&endpoint->Ipv4, resolved->ai_addr, resolved->ai_addrlen); - else if (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(SOCKADDR_IN6)) memcpy(&endpoint->Ipv6, resolved->ai_addr, resolved->ai_addrlen); - else { - freeaddrinfo(resolved); - throw std::string("Neither IPv4 nor IPv6 address found: ").append(value); - } - freeaddrinfo(resolved); - free(mmutable); -} - -std::string parseEndpoint(SOCKADDR_INET *input) { - if (!(input->si_family == AF_INET || input->si_family == AF_INET6)) return ""; - char saddr[INET6_ADDRSTRLEN]; - input->si_family == AF_INET ? inet_ntop(AF_INET, &input->Ipv4.sin_addr, saddr, INET_ADDRSTRLEN) : inet_ntop(AF_INET6, &input->Ipv6.sin6_addr, saddr, INET6_ADDRSTRLEN); - - if (input->si_family == AF_INET6) return std::string("[").append(saddr).append("]:").append(std::to_string(htons(input->Ipv6.sin6_port))); - return std::string(saddr).append(":").append(std::to_string(htons(input->Ipv4.sin_port))); -} - -std::string insertIpAddr(NET_LUID InterfaceLuid, std::string IPv4, std::string IPv6) { - NET_IFINDEX ind; - if (ConvertInterfaceLuidToIndex(&InterfaceLuid, &ind) != NO_ERROR) return "Cannot get interface index"; - - // IPv4 - if (IPv4.size() > 0) { - ULONG NTEContext = 0; - ULONG NTEInstance = 0; - UINT iaIPAddress; - inet_pton(AF_INET, IPv4.c_str(), &iaIPAddress); - auto status = AddIPAddress(iaIPAddress, NULL, ind, &NTEContext, &NTEInstance); - if (status != NO_ERROR) { - if (status == 5010) { - } else return std::string("Cannot set IPv4 interface, error code: ").append(std::to_string(status)); - } - } - - // IPv6 - if (IPv6.size() > 0) { - UINT iaIPAddress; - inet_pton(AF_INET6, IPv6.c_str(), &iaIPAddress); - std::cerr << "Current not support IPv6 to set in interface!" << std::endl; - } - return ""; -} - -std::vector getIpAddr(NET_LUID InterfaceLuid) { - NET_IFINDEX ind; - if (ConvertInterfaceLuidToIndex(&InterfaceLuid, &ind) != NO_ERROR) throw std::string("Cannot get interface index"); - std::vector ips; - - IP_ADAPTER_INFO *pAdapterInfo; - ULONG ulOutBufLen; - DWORD dwRetVal; - pAdapterInfo = (IP_ADAPTER_INFO *) malloc( sizeof(IP_ADAPTER_INFO) ); - ulOutBufLen = sizeof(IP_ADAPTER_INFO); - if (GetAdaptersInfo( pAdapterInfo, &ulOutBufLen) != ERROR_SUCCESS) { - free (pAdapterInfo); - pAdapterInfo = (IP_ADAPTER_INFO *) malloc ( ulOutBufLen ); - } - if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) != ERROR_SUCCESS) throw std::string("GetAdaptersInfo call failed with ").append(std::to_string(dwRetVal)); - PIP_ADAPTER_INFO pAdapter = pAdapterInfo; - while (pAdapter) { - if (pAdapter->Index == ind) ips.push_back(std::string(pAdapter->IpAddressList.IpAddress.String).append("/32")); - pAdapter = pAdapter->Next; - } - if (pAdapterInfo) free(pAdapterInfo); - - return ips; -} diff --git a/binding.yaml b/binding.yaml index 79c9913..e38ad17 100644 --- a/binding.yaml +++ b/binding.yaml @@ -1,80 +1,56 @@ name: wginterface -flags: - - "!-fno-exceptions" - - "-fpermissive" - - "-fexceptions" - - "-w" - - "-fpermissive" -flagsCC: - - "!-fno-exceptions" - - "-fpermissive" - - "-fexceptions" - - "-w" - - "-fpermissive" defines: - - "NAPI_DISABLE_CPP_EXCEPTIONS" - - "NODE_VERSION=4" + - "NODE_VERSION=8" + - "NAPI_CPP_EXCEPTIONS" includes: - - addons/genKey - - addons/tools - node_modules/node-addon-api + - ./addon sources: - - "addons/genKey/wgkeys.cpp" - - "addons/tools/wginterface.cpp" - - "addons/tools/wginterface-dummy.cpp" + - "addon/main.cpp" + - "addon/genKey/wgkeys.cpp" + - "addon/userspace/wginterface.cpp" +prebuild: + - shell: bash + cwd: ./addon/userspace/go + env: + CGO_ENABLED: "1" + LDFLAGS: "-w" + run: | + go build -trimpath -v -o ../wg-go.o -buildmode c-archive . + mv -fv ../wg-go.o "${BUILDDIR}" target: - macos: - defines: - - USERSPACE_GO linux: sources: - - "addons/tools/linux/wireguard.c" - - "addons/tools/wginterface-linux.cpp" - - "!addons/tools/wginterface-dummy.cpp" - defines: - - "LISTDEV" - - "GETCONFIG" - - "SETCONFIG" - - "DELIFACE" - target: - x86_64: - release: true - flags: - - "-fPIC" - flagsCC: - - "-fPIC" - aarch64: - release: true - flags: - - "-fPIC" - flagsCC: - - "-fPIC" - windows: + - "!addon/userspace/wginterface.cpp" + - "addon/linux/wginterface.cpp" + - "addon/linux/wireguard.c" flags: - - "-undefined" - - "dynamic_lookup" - target: - x86_64: - release: true - aarch64: - release: true + - "!-fno-exceptions" + - "-fpermissive" + - "-fexceptions" + - "-w" + - "-fpermissive" + - "-fPIC" + windows: sources: - - "addons/tools/wginterface-win.cpp" - - "!addons/tools/wginterface-dummy.cpp" - includes: - - "addons/tools/win" - defines: - - "ONSTARTADDON" - - "LISTDEV" - - "GETCONFIG" - - "SETCONFIG" - - "DELIFACE" - - "_HAS_EXCEPTIONS=1" + - "!addon/userspace/wginterface.cpp" + - "addon/win/wginterface.cpp" libraries: - - "bcrypt.lib" - - "crypt32.lib" - - "iphlpapi.lib" - - "kernel32.lib" - - "ntdll.lib" - - "ws2_32.lib" - - "setupapi.lib" \ No newline at end of file + - wbemuuid.lib + - bcrypt.lib + - crypt32.lib + - iphlpapi.lib + - kernel32.lib + - ntdll.lib + - ws2_32.lib + - setupapi.lib + defines: + - "_HAS_EXCEPTIONS=1" + - "ONSTARTADDON" + macos: + flags: + - "!-fno-exceptions" + - "-fpermissive" + - "-fexceptions" + - "-w" + - "-fpermissive" diff --git a/package.json b/package.json index c5d5554..b2ddac7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "license": "GPL-3.0-or-later", "repository": { "type": "git", - "url": "git+https://sirherobrine23.org/Wireguard/Wireguard-tools.js.git" + "url": "https://sirherobrine23.org/Wireguard/Wireguard-tools.js.git" }, "keywords": [ "wireguard", @@ -34,18 +34,18 @@ }, "scripts": { "install": "rebory prebuild", - "dev": "rebory build -DP", - "test": "rebory build -D && node --no-warnings --loader ts-node/esm src/index_test.js", - "prepack": "tsc --build --clean && tsc --build", + "dev": "rebory build", + "test": "rebory build && node --no-warnings --loader ts-node/esm src/index_test.js", + "prepack": "tsc --build --clean && tsc --build && rebory build --release", "postpack": "tsc --build --clean" }, "devDependencies": { - "@types/node": "^20.11.19", + "@types/node": "^20.11.26", "ts-node": "^10.9.2", - "typescript": "^5.3.3" + "typescript": "^5.4.2" }, "dependencies": { - "node-addon-api": "^7.1.0", - "rebory": "^0.1.12" + "node-addon-api": "^8.0.0", + "rebory": "^0.2.5" } } diff --git a/src/build/config.gypi b/src/build/config.gypi deleted file mode 100644 index 1291fb4..0000000 --- a/src/build/config.gypi +++ /dev/null @@ -1,414 +0,0 @@ -# Do not edit. File was generated by node-gyp's "configure" step -{ - "target_defaults": { - "cflags": [], - "default_configuration": "Release", - "defines": [], - "include_dirs": [], - "libraries": [], - "msbuild_toolset": "v143", - "msvs_windows_target_platform_version": "10.0.22621.0" - }, - "variables": { - "asan": 0, - "coverage": "false", - "dcheck_always_on": 0, - "debug_nghttp2": "false", - "debug_node": "false", - "enable_lto": "false", - "enable_pgo_generate": "false", - "enable_pgo_use": "false", - "error_on_warn": "false", - "force_dynamic_crt": 0, - "host_arch": "x64", - "icu_data_in": "..\\..\\deps\\icu-tmp\\icudt73l.dat", - "icu_endianness": "l", - "icu_gyp_path": "tools/icu/icu-generic.gyp", - "icu_path": "deps/icu-small", - "icu_small": "false", - "icu_ver_major": "73", - "is_debug": 0, - "libdir": "lib", - "llvm_version": "0.0", - "napi_build_version": "9", - "nasm_version": "2.16", - "node_builtin_shareable_builtins": [ - "deps/cjs-module-lexer/lexer.js", - "deps/cjs-module-lexer/dist/lexer.js", - "deps/undici/undici.js" - ], - "node_byteorder": "little", - "node_debug_lib": "false", - "node_enable_d8": "false", - "node_enable_v8_vtunejit": "false", - "node_fipsinstall": "false", - "node_install_corepack": "true", - "node_install_npm": "true", - "node_library_files": [ - "lib/_http_agent.js", - "lib/_http_client.js", - "lib/_http_common.js", - "lib/_http_incoming.js", - "lib/_http_outgoing.js", - "lib/_http_server.js", - "lib/_stream_duplex.js", - "lib/_stream_passthrough.js", - "lib/_stream_readable.js", - "lib/_stream_transform.js", - "lib/_stream_wrap.js", - "lib/_stream_writable.js", - "lib/_tls_common.js", - "lib/_tls_wrap.js", - "lib/assert.js", - "lib/assert/strict.js", - "lib/async_hooks.js", - "lib/buffer.js", - "lib/child_process.js", - "lib/cluster.js", - "lib/console.js", - "lib/constants.js", - "lib/crypto.js", - "lib/dgram.js", - "lib/diagnostics_channel.js", - "lib/dns.js", - "lib/dns/promises.js", - "lib/domain.js", - "lib/events.js", - "lib/fs.js", - "lib/fs/promises.js", - "lib/http.js", - "lib/http2.js", - "lib/https.js", - "lib/inspector.js", - "lib/inspector/promises.js", - "lib/internal/abort_controller.js", - "lib/internal/assert.js", - "lib/internal/assert/assertion_error.js", - "lib/internal/assert/calltracker.js", - "lib/internal/async_hooks.js", - "lib/internal/blob.js", - "lib/internal/blocklist.js", - "lib/internal/bootstrap/node.js", - "lib/internal/bootstrap/realm.js", - "lib/internal/bootstrap/switches/does_not_own_process_state.js", - "lib/internal/bootstrap/switches/does_own_process_state.js", - "lib/internal/bootstrap/switches/is_main_thread.js", - "lib/internal/bootstrap/switches/is_not_main_thread.js", - "lib/internal/bootstrap/web/exposed-wildcard.js", - "lib/internal/bootstrap/web/exposed-window-or-worker.js", - "lib/internal/buffer.js", - "lib/internal/child_process.js", - "lib/internal/child_process/serialization.js", - "lib/internal/cli_table.js", - "lib/internal/cluster/child.js", - "lib/internal/cluster/primary.js", - "lib/internal/cluster/round_robin_handle.js", - "lib/internal/cluster/shared_handle.js", - "lib/internal/cluster/utils.js", - "lib/internal/cluster/worker.js", - "lib/internal/console/constructor.js", - "lib/internal/console/global.js", - "lib/internal/constants.js", - "lib/internal/crypto/aes.js", - "lib/internal/crypto/certificate.js", - "lib/internal/crypto/cfrg.js", - "lib/internal/crypto/cipher.js", - "lib/internal/crypto/diffiehellman.js", - "lib/internal/crypto/ec.js", - "lib/internal/crypto/hash.js", - "lib/internal/crypto/hashnames.js", - "lib/internal/crypto/hkdf.js", - "lib/internal/crypto/keygen.js", - "lib/internal/crypto/keys.js", - "lib/internal/crypto/mac.js", - "lib/internal/crypto/pbkdf2.js", - "lib/internal/crypto/random.js", - "lib/internal/crypto/rsa.js", - "lib/internal/crypto/scrypt.js", - "lib/internal/crypto/sig.js", - "lib/internal/crypto/util.js", - "lib/internal/crypto/webcrypto.js", - "lib/internal/crypto/webidl.js", - "lib/internal/crypto/x509.js", - "lib/internal/debugger/inspect.js", - "lib/internal/debugger/inspect_client.js", - "lib/internal/debugger/inspect_repl.js", - "lib/internal/dgram.js", - "lib/internal/dns/callback_resolver.js", - "lib/internal/dns/promises.js", - "lib/internal/dns/utils.js", - "lib/internal/encoding.js", - "lib/internal/error_serdes.js", - "lib/internal/errors.js", - "lib/internal/event_target.js", - "lib/internal/events/symbols.js", - "lib/internal/file.js", - "lib/internal/fixed_queue.js", - "lib/internal/freelist.js", - "lib/internal/freeze_intrinsics.js", - "lib/internal/fs/cp/cp-sync.js", - "lib/internal/fs/cp/cp.js", - "lib/internal/fs/dir.js", - "lib/internal/fs/promises.js", - "lib/internal/fs/read/context.js", - "lib/internal/fs/read/utf8.js", - "lib/internal/fs/recursive_watch.js", - "lib/internal/fs/rimraf.js", - "lib/internal/fs/streams.js", - "lib/internal/fs/sync_write_stream.js", - "lib/internal/fs/utils.js", - "lib/internal/fs/watchers.js", - "lib/internal/heap_utils.js", - "lib/internal/histogram.js", - "lib/internal/http.js", - "lib/internal/http2/compat.js", - "lib/internal/http2/core.js", - "lib/internal/http2/util.js", - "lib/internal/idna.js", - "lib/internal/inspector_async_hook.js", - "lib/internal/js_stream_socket.js", - "lib/internal/legacy/processbinding.js", - "lib/internal/linkedlist.js", - "lib/internal/main/check_syntax.js", - "lib/internal/main/embedding.js", - "lib/internal/main/eval_stdin.js", - "lib/internal/main/eval_string.js", - "lib/internal/main/inspect.js", - "lib/internal/main/mksnapshot.js", - "lib/internal/main/print_help.js", - "lib/internal/main/prof_process.js", - "lib/internal/main/repl.js", - "lib/internal/main/run_main_module.js", - "lib/internal/main/test_runner.js", - "lib/internal/main/watch_mode.js", - "lib/internal/main/worker_thread.js", - "lib/internal/mime.js", - "lib/internal/modules/cjs/loader.js", - "lib/internal/modules/esm/assert.js", - "lib/internal/modules/esm/create_dynamic_module.js", - "lib/internal/modules/esm/fetch_module.js", - "lib/internal/modules/esm/formats.js", - "lib/internal/modules/esm/get_format.js", - "lib/internal/modules/esm/handle_process_exit.js", - "lib/internal/modules/esm/hooks.js", - "lib/internal/modules/esm/initialize_import_meta.js", - "lib/internal/modules/esm/load.js", - "lib/internal/modules/esm/loader.js", - "lib/internal/modules/esm/module_job.js", - "lib/internal/modules/esm/module_map.js", - "lib/internal/modules/esm/package_config.js", - "lib/internal/modules/esm/resolve.js", - "lib/internal/modules/esm/shared_constants.js", - "lib/internal/modules/esm/translators.js", - "lib/internal/modules/esm/utils.js", - "lib/internal/modules/esm/worker.js", - "lib/internal/modules/helpers.js", - "lib/internal/modules/package_json_reader.js", - "lib/internal/modules/run_main.js", - "lib/internal/net.js", - "lib/internal/options.js", - "lib/internal/per_context/domexception.js", - "lib/internal/per_context/messageport.js", - "lib/internal/per_context/primordials.js", - "lib/internal/perf/event_loop_delay.js", - "lib/internal/perf/event_loop_utilization.js", - "lib/internal/perf/nodetiming.js", - "lib/internal/perf/observe.js", - "lib/internal/perf/performance.js", - "lib/internal/perf/performance_entry.js", - "lib/internal/perf/resource_timing.js", - "lib/internal/perf/timerify.js", - "lib/internal/perf/usertiming.js", - "lib/internal/perf/utils.js", - "lib/internal/policy/manifest.js", - "lib/internal/policy/sri.js", - "lib/internal/priority_queue.js", - "lib/internal/process/esm_loader.js", - "lib/internal/process/execution.js", - "lib/internal/process/per_thread.js", - "lib/internal/process/permission.js", - "lib/internal/process/policy.js", - "lib/internal/process/pre_execution.js", - "lib/internal/process/promises.js", - "lib/internal/process/report.js", - "lib/internal/process/signal.js", - "lib/internal/process/task_queues.js", - "lib/internal/process/warning.js", - "lib/internal/process/worker_thread_only.js", - "lib/internal/promise_hooks.js", - "lib/internal/querystring.js", - "lib/internal/readline/callbacks.js", - "lib/internal/readline/emitKeypressEvents.js", - "lib/internal/readline/interface.js", - "lib/internal/readline/promises.js", - "lib/internal/readline/utils.js", - "lib/internal/repl.js", - "lib/internal/repl/await.js", - "lib/internal/repl/history.js", - "lib/internal/repl/utils.js", - "lib/internal/socket_list.js", - "lib/internal/socketaddress.js", - "lib/internal/source_map/prepare_stack_trace.js", - "lib/internal/source_map/source_map.js", - "lib/internal/source_map/source_map_cache.js", - "lib/internal/stream_base_commons.js", - "lib/internal/streams/add-abort-signal.js", - "lib/internal/streams/buffer_list.js", - "lib/internal/streams/compose.js", - "lib/internal/streams/destroy.js", - "lib/internal/streams/duplex.js", - "lib/internal/streams/duplexify.js", - "lib/internal/streams/end-of-stream.js", - "lib/internal/streams/from.js", - "lib/internal/streams/lazy_transform.js", - "lib/internal/streams/legacy.js", - "lib/internal/streams/operators.js", - "lib/internal/streams/passthrough.js", - "lib/internal/streams/pipeline.js", - "lib/internal/streams/readable.js", - "lib/internal/streams/state.js", - "lib/internal/streams/transform.js", - "lib/internal/streams/utils.js", - "lib/internal/streams/writable.js", - "lib/internal/structured_clone.js", - "lib/internal/test/binding.js", - "lib/internal/test/transfer.js", - "lib/internal/test_runner/coverage.js", - "lib/internal/test_runner/harness.js", - "lib/internal/test_runner/mock/mock.js", - "lib/internal/test_runner/mock/mock_timers.js", - "lib/internal/test_runner/reporter/dot.js", - "lib/internal/test_runner/reporter/spec.js", - "lib/internal/test_runner/reporter/tap.js", - "lib/internal/test_runner/reporter/v8-serializer.js", - "lib/internal/test_runner/runner.js", - "lib/internal/test_runner/test.js", - "lib/internal/test_runner/tests_stream.js", - "lib/internal/test_runner/utils.js", - "lib/internal/timers.js", - "lib/internal/tls/secure-context.js", - "lib/internal/tls/secure-pair.js", - "lib/internal/trace_events_async_hooks.js", - "lib/internal/tty.js", - "lib/internal/url.js", - "lib/internal/util.js", - "lib/internal/util/colors.js", - "lib/internal/util/comparisons.js", - "lib/internal/util/debuglog.js", - "lib/internal/util/embedding.js", - "lib/internal/util/inspect.js", - "lib/internal/util/inspector.js", - "lib/internal/util/iterable_weak_map.js", - "lib/internal/util/parse_args/parse_args.js", - "lib/internal/util/parse_args/utils.js", - "lib/internal/util/types.js", - "lib/internal/v8/startup_snapshot.js", - "lib/internal/v8_prof_polyfill.js", - "lib/internal/v8_prof_processor.js", - "lib/internal/validators.js", - "lib/internal/vm.js", - "lib/internal/vm/module.js", - "lib/internal/wasm_web_api.js", - "lib/internal/watch_mode/files_watcher.js", - "lib/internal/watchdog.js", - "lib/internal/webidl.js", - "lib/internal/webstreams/adapters.js", - "lib/internal/webstreams/compression.js", - "lib/internal/webstreams/encoding.js", - "lib/internal/webstreams/queuingstrategies.js", - "lib/internal/webstreams/readablestream.js", - "lib/internal/webstreams/transfer.js", - "lib/internal/webstreams/transformstream.js", - "lib/internal/webstreams/util.js", - "lib/internal/webstreams/writablestream.js", - "lib/internal/worker.js", - "lib/internal/worker/io.js", - "lib/internal/worker/js_transferable.js", - "lib/module.js", - "lib/net.js", - "lib/os.js", - "lib/path.js", - "lib/path/posix.js", - "lib/path/win32.js", - "lib/perf_hooks.js", - "lib/process.js", - "lib/punycode.js", - "lib/querystring.js", - "lib/readline.js", - "lib/readline/promises.js", - "lib/repl.js", - "lib/stream.js", - "lib/stream/consumers.js", - "lib/stream/promises.js", - "lib/stream/web.js", - "lib/string_decoder.js", - "lib/sys.js", - "lib/test.js", - "lib/test/reporters.js", - "lib/timers.js", - "lib/timers/promises.js", - "lib/tls.js", - "lib/trace_events.js", - "lib/tty.js", - "lib/url.js", - "lib/util.js", - "lib/util/types.js", - "lib/v8.js", - "lib/vm.js", - "lib/wasi.js", - "lib/worker_threads.js", - "lib/zlib.js" - ], - "node_module_version": 115, - "node_no_browser_globals": "false", - "node_prefix": "\\usr\\local", - "node_release_urlbase": "https://nodejs.org/download/release/", - "node_shared": "false", - "node_shared_brotli": "false", - "node_shared_cares": "false", - "node_shared_http_parser": "false", - "node_shared_libuv": "false", - "node_shared_nghttp2": "false", - "node_shared_nghttp3": "false", - "node_shared_ngtcp2": "false", - "node_shared_openssl": "false", - "node_shared_zlib": "false", - "node_tag": "", - "node_target_type": "executable", - "node_use_bundled_v8": "true", - "node_use_node_code_cache": "true", - "node_use_node_snapshot": "true", - "node_use_openssl": "true", - "node_use_v8_platform": "true", - "node_with_ltcg": "true", - "node_without_node_options": "false", - "openssl_is_fips": "false", - "openssl_quic": "true", - "ossfuzz": "false", - "shlib_suffix": "so.115", - "single_executable_application": "true", - "target_arch": "x64", - "v8_enable_31bit_smis_on_64bit_arch": 0, - "v8_enable_gdbjit": 0, - "v8_enable_hugepage": 0, - "v8_enable_i18n_support": 1, - "v8_enable_inspector": 1, - "v8_enable_javascript_promise_hooks": 1, - "v8_enable_lite_mode": 0, - "v8_enable_object_print": 1, - "v8_enable_pointer_compression": 0, - "v8_enable_shared_ro_heap": 1, - "v8_enable_short_builtin_calls": 1, - "v8_enable_webassembly": 1, - "v8_no_strict_aliasing": 1, - "v8_optimized_debug": 1, - "v8_promise_internal_field_count": 1, - "v8_random_seed": 0, - "v8_trace_maps": 0, - "v8_use_siphash": 1, - "want_separate_host_toolset": 0, - "nodedir": "C:\\Users\\mathe\\AppData\\Local\\node-gyp\\Cache\\20.6.1", - "standalone_static_library": 1, - "msbuild_path": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe" - } -} diff --git a/src/index.ts b/src/index.ts index b2a4e80..bd34e77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ export * as key from "./key.js"; -export * as wgQuick from "./quick.js"; +export * as quickConfig from "./quick.js"; export * from "./wginterface.js"; -export * as wginterface from "./wginterface.js"; -export * as default from "./wginterface.js"; +export * as wginterface from "./wginterface.js"; \ No newline at end of file diff --git a/src/key.ts b/src/key.ts index d425e71..0698e85 100644 --- a/src/key.ts +++ b/src/key.ts @@ -1,5 +1,7 @@ import crypto from "node:crypto"; +export const KeyLength = 32, Base64Length = 44; + type BinArray = Float64Array|Uint8Array|number[]; function gf(init?: number[]) { @@ -94,39 +96,31 @@ function clamp(z: BinArray) { z[0] &= 248; } -/** - * Generate preshared key blocking loop event - * - * @deprecated - use presharedKey - */ -export function presharedKeySync() { - var privateKey = new Uint8Array(32); - crypto.randomFillSync(privateKey); - return keyToBase64(privateKey); +function Base64ToKey(keyInput: string): Uint8Array { + if (keyInput.length !== Base64Length) throw new Error("Invalid key length", { cause: { Required: Base64Length, Input: keyInput.length } }); + return new Uint8Array(Buffer.from(keyInput, "base64")); +} + +function keyToBase64(key: Uint8Array): string { + if (key.length !== KeyLength) throw new Error("Invalid key length", { cause: { Required: KeyLength, Input: key.length } }); + return Buffer.from(key).toString("base64"); +} + +export function keyToHex(key: string): string { + return Buffer.from(key, "base64").toString("hex"); } /** * Generate preshared key */ export async function presharedKey(): Promise { - var privateKey = new Uint8Array(32); + var privateKey = new Uint8Array(KeyLength); return new Promise((done, reject) => crypto.randomFill(privateKey, (err) => { if (err) return reject(err); done(keyToBase64(privateKey)); })); } -/** - * Create private key - * - * @deprecated - Use privateKey - */ -export function privateKeySync() { - var privateKey = Base64ToKey(presharedKeySync()); - clamp(privateKey); - return keyToBase64(privateKey); -} - /** * Create private key */ @@ -136,14 +130,6 @@ export async function privateKey() { return keyToBase64(privateKey); } -export function keyToBase64(key: Uint8Array): string { - return Buffer.from(key).toString("base64"); -} - -export function Base64ToKey(keyInput: string): Uint8Array { - return new Uint8Array(Buffer.from(keyInput, "base64")); -} - /** * Get Public key from Private key * @@ -151,7 +137,7 @@ export function Base64ToKey(keyInput: string): Uint8Array { */ export function publicKey(privKey: string) { var privateKey: Uint8Array = Base64ToKey(privKey); - var r: number, z = new Uint8Array(32); + var r: number, z = new Uint8Array(KeyLength); var a = gf([1]), b = gf([9]), c = gf(), diff --git a/src/wginterface.ts b/src/wginterface.ts index 3e31ce6..5d562e7 100644 --- a/src/wginterface.ts +++ b/src/wginterface.ts @@ -1,46 +1,10 @@ -import { promises as fs } from "fs"; -import { isIPv4, createConnection as netConnection } from "net"; -import path from "path"; -import readline from "readline"; -import { finished } from "stream/promises"; -import rebory from "rebory"; -import { fileURLToPath } from "url"; +import path from "node:path"; +import { loadAddon } from "rebory"; +import { key } from "./index.js"; +import { isIP } from "node:net"; +const __dirname = import.meta.dirname || path.dirname((await import("node:url")).fileURLToPath(import.meta.url)); -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const addon = rebory.loadAddon(path.join(__dirname, "../binding.yaml")).wginterface.loadRelease<{ - listDevices?: () => Promise<{from: "userspace"|"kernel", name: string, path?: string}[]>; - deleteInterface?: (wgName: string) => Promise; - setConfig?: (wgName: string, config: WgConfigSet) => Promise; - getConfig?: (wgName: string) => Promise; - - createTun?: () => Promise; - deleteTun?: () => void; - checkTun?: () => Promise; - getTun?: () => Promise; - - /** Wireguard addon constants */ - constants: { - driveVersion: string; - base64Length: number; - keyLength: number; - nameLength: number; - }; -}>({ - WIN32DLLPATH: path.resolve(__dirname, "../addons/tools/win/wireguard-nt/bin", ((process.arch === "x64" && "amd64") || (process.arch === "ia32" && "i386"))||process.arch, "wireguard.dll") -}); - -export const { - constants -} = addon; - -/** default location to run socket's */ -const defaultPath = (process.env.WIRWGUARD_GO_RUN||"").length > 0 ? path.resolve(process.cwd(), process.env.WIRWGUARD_GO_RUN) : process.platform === "win32" ? "\\\\.\\pipe\\WireGuard" : "/var/run/wireguard"; - -async function exists(path: string) { - return fs.open(path).then(o => o && (o.close().then(() => true, () => true))||true, () => false); -} - -export interface Peer { +interface Peer { /** Preshared key to peer */ presharedKey?: string; @@ -54,23 +18,26 @@ export interface Peer { allowedIPs?: string[]; }; -export interface PeerSet extends Peer { - /** Mark this peer to be removed, any changes remove this option */ +export interface SetPeer extends Peer { + /** Remove this peer */ removeMe?: boolean; } -export interface PeerGet extends Peer { +export interface GetPeer extends Peer { /** ReceiveBytes indicates the number of bytes received from this peer. */ - rxBytes?: number; + rxBytes: bigint; /** TransmitBytes indicates the number of bytes transmitted to this peer. */ - txBytes?: number; + txBytes: bigint; /** Last peer Handshake */ - lastHandshake?: Date; + lastHandshake: Date; } -export interface WgConfigBase { +interface Config { + /** Wireguard interface name */ + name: string; + /** privateKey specifies a private key configuration */ privateKey: string; @@ -86,164 +53,341 @@ export interface WgConfigBase { /** Interface IP address'es */ address?: string[]; - /** Interface peers */ + /** Interface Peers */ peers: Record; -} +}; -export interface WgConfigGet extends WgConfigBase {} -export interface WgConfigSet extends WgConfigBase { +export interface GetConfig extends Config { }; +export interface SetConfig extends Config { /** this option will remove all peers if `true` and add new peers */ replacePeers?: boolean; -} +}; -export type WgGlobalConfig = WgConfigSet & WgConfigGet; +export const addon = (await loadAddon(path.resolve(__dirname, "../binding.yaml"))).wginterface.load_addon<{ + /** Current Wireguard drive version */ + driveVersion?: string; -/** - * Get Wireguard devices and locations - */ -export async function listDevices() { - let devices: {from: "userspace"|"kernel", name: string, path?: string}[] = []; - if (typeof addon.listDevices === "function") devices = devices.concat(await addon.listDevices()); - if (await exists(defaultPath)) (await fs.readdir(defaultPath)).forEach(file => devices.push({ from: "userspace", name: file.endsWith(".sock") ? file.slice(0, -5) : file, path: path.join("/var/run/wireguard", file) })); - return devices; -} + /** + * Delete interface if exists + * @param name - Interface name + */ + deleteInterface(name: string): Promise; -/** - * Delete wireguard interface if present - * @param wgName - Interface name - * @returns - */ -export async function deleteInterface(wgName: string): Promise { - if (typeof addon.deleteInterface === "function") return addon.deleteInterface(wgName); - const dev = (await listDevices()).find(s => s.name === wgName); - if (dev && dev.path) return fs.rm(dev.path, { force: true }); -} + /** + * Get Wireguard interfaces list + * + * if running in userspace return socket (UAPI) path's + */ + listDevices(): Promise; -/** - * Add the settings to the Wireguard interface, if it does not exist and the interface will be created automatically. - * - * To update the interface settings, first get the interface settings to update! - * - * @param wgName - Interface name - * @param config - Interface config - */ -export async function setConfig(wgName: string, config: WgConfigGet): Promise; -/** - * Add the settings to the Wireguard interface, if it does not exist and the interface will be created automatically. - * - * To update the interface settings, first get the interface settings to update! - * - * @param wgName - Interface name - * @param config - Interface config - */ -export async function setConfig(wgName: string, config: WgConfigSet): Promise { - if (wgName.length > constants.nameLength) throw new Error("Interface name more then allowed", { cause: constants.nameLength }); - if (typeof addon.setConfig === "function") return addon.setConfig(wgName, config); - const client = netConnection(path.join(defaultPath, (wgName).concat(".sock"))); - const writel = (...data: any[]) => client.write(data.map(String).join("").concat("\n")); - // Init set config in interface - writel("set=1"); + /** + * Get current config from Wireguard interface + * @param name - Interface name + */ + getConfig(name: string): Promise; - // Port listening - if (config.portListen !== undefined && Math.floor(config.portListen) >= 0) writel(("listen_port="), ((Math.floor(config.portListen)))); + /** + * Set new config to Wireguard interface or create new interface if not exists + * @param config - Interface config + */ + setConfig(config: SetConfig): Promise; +}>({ + WIN32DLLPATH: path.resolve(__dirname, "../addon/win", (process.arch === "x64" && "amd64") || (process.arch === "ia32" && "x86") || process.arch, "wireguard.dll") +}); - // fwmark - if (Math.floor(config.fwmark) >= 0) writel(("fwmark="), ((Math.floor(config.fwmark)))); +export const { + driveVersion, + listDevices, + getConfig, + setConfig, + deleteInterface +} = addon; - // Replace peer's - if (config.replacePeers) writel("replace_peers=true"); +export class WireGuardPeer { + constructor(public publicKey: string, private __Wg: Wireguard) { } - // Keys - if (typeof config.privateKey === "string" && config.privateKey.length > 0) writel(("private_key="), (Buffer.from(config.privateKey, "base64").toString("hex"))); - - // Mount peer - for (const publicKey of Object.keys(config.peers||{})) { - const { presharedKey, endpoint, keepInterval, removeMe, allowedIPs = [] } = config.peers[publicKey]; - - // Public key - writel(("public_key="), (Buffer.from(publicKey, "base64").toString("hex"))); - if (removeMe) { - writel("remove=true"); // Remove peer - continue; - } - - if (typeof endpoint === "string" && endpoint.length > 0) writel(("endpoint="), (endpoint)); - if (typeof presharedKey === "string" && presharedKey.length > 3) writel(("preshared_key="), (Buffer.from(presharedKey, "base64").toString("hex"))); - if (typeof keepInterval === "number" && Math.floor(keepInterval) > 0) writel(("persistent_keepalive_interval="), (String(Math.floor(keepInterval)))); - if (allowedIPs.length > 0) { - writel("replace_allowed_ips=true"); - const fixed = allowedIPs.map(i => i.indexOf("/") === -1 ? i.concat("/", (isIPv4(i) ? "32" : "128")) : i) - for (const IIP of fixed) writel(("allowed_ip="), (IIP)); - } + async getStats() { + const { rxBytes, txBytes, lastHandshake } = await getConfig(this.__Wg.name).then((config) => config.peers[this.publicKey]); + return { + rxBytes, + txBytes, + lastHandshake + }; } - let payload = ""; - client.once("data", function processBuff(buff) { - payload = payload.concat(buff.toString("utf8")); - if (payload[payload.length - 1] === "\n" && payload[payload.length - 2] === "\n") { - client.end(); // Close conenction - return; - } - client.once("data", processBuff); - }); - client.write("\n"); - await finished(client, { error: true }); - const payloadKeys = payload.split("\n").filter(i => i.length > 3).map(line => { const iit = line.indexOf("="); return [ line.slice(0, iit), line.slice(iit+1) ]; }) - if (payloadKeys.at(-1)[1] !== "0") { - const err = new Error("Invalid send config, check log"); - throw err; + addNewAddress(address: string) { + if (isIP(address.split("/")[0]) === 0) throw new Error("Invalid IP address"); + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + const _addr = new Set(this.__Wg._peers.get(this.publicKey).allowedIPs); + _addr.add(address.split("/")[0]); + this.__Wg._peers.get(this.publicKey).allowedIPs = Array.from(_addr); + return this; + } + + removeAddress(address: string) { + if (isIP(address.split("/")[0]) === 0) throw new Error("Invalid IP address"); + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + const _addr = new Set(this.__Wg._peers.get(this.publicKey).allowedIPs); + _addr.delete(address.split("/")[0]); + this.__Wg._peers.get(this.publicKey).allowedIPs = Array.from(_addr); + return this; + } + + setKeepInterval(keepInterval: number) { + if (typeof keepInterval !== "number" || keepInterval < 0) throw new Error("Invalid keepInterval"); + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + if (keepInterval > 0) this.__Wg._peers.get(this.publicKey).keepInterval = keepInterval; + else delete this.__Wg._peers.get(this.publicKey).keepInterval; + return this; + } + + setEndpoint(endpoint: string) { + if (typeof endpoint !== "string") throw new Error("Invalid endpoint"); + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + if (endpoint.length > 0) this.__Wg._peers.get(this.publicKey).endpoint = endpoint; + else delete this.__Wg._peers.get(this.publicKey).endpoint; + return this; + } + + /** + * Sets the preshared key for the peer. + * @param presharedKey - The preshared key to set. If not provided, a new preshared key will be generated. + * @returns The updated WireGuard interface object. + * @throws {Error} If the provided preshared key is invalid. + */ + setPresharedKey(): Promise & this; + /** + * Sets the preshared key for the peer. + * @param presharedKey - The preshared key to set. If not provided, a new preshared key will be generated. + * @returns The updated WireGuard interface object. + * @throws {Error} If the provided preshared key is invalid. + */ + setPresharedKey(presharedKey: string): this; + /** + * Sets the preshared key for the peer. + * @param presharedKey - The preshared key to set. If not provided, a new preshared key will be generated. + * @returns The updated WireGuard interface object. + * @throws {Error} If the provided preshared key is invalid. + */ + setPresharedKey(presharedKey?: string) { + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + if (!presharedKey) return Object.assign(key.presharedKey().then((presharedKey) => this.__Wg._peers.get(this.publicKey).presharedKey = presharedKey), this); + if (typeof presharedKey !== "string" || presharedKey.length !== key.Base64Length) throw new Error("Invalid presharedKey"); + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + this.__Wg._peers.get(this.publicKey).presharedKey = presharedKey; + return this; + } + + /** + * Removes the peer from the WireGuard interface. + * @returns The updated WireGuard interface. + */ + remove() { + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + this.__Wg._peers.get(this.publicKey)["removeMe"] = true; + return this; + } + + /** + * Converts the `WireGuard Peer` object to a JSON representation. + * @returns The JSON representation of the `WireGuard Peer` object. + */ + toJSON(): [string, SetPeer] { + if (!this.__Wg._peers) this.__Wg._peers = new Map(); + const { keepInterval, endpoint, presharedKey, allowedIPs } = this.__Wg._peers.get(this.publicKey); + const peer: SetPeer = Object.create({}); + if (presharedKey) peer.presharedKey = presharedKey; + if (keepInterval) peer.keepInterval = keepInterval; + if (endpoint) peer.endpoint = endpoint; + if (allowedIPs) peer.allowedIPs = allowedIPs; + return [this.publicKey, peer]; } } /** - * Get wireguard interface config - * @param wgName - Interface name - * @returns + * Maneger Wireguard interface and peers simple and fast */ -export async function getConfig(wgName: string): Promise { - if (typeof addon.getConfig === "function") return addon.getConfig(wgName); - const info = (await listDevices()).find(int => int.name === wgName); - if (!info) throw new Error("Create interface, not exists"); - const client = netConnection(path.join(defaultPath, wgName.concat(".sock"))); - const config: WgConfigGet = Object(); - let latestPeer: string, previewKey: string; - - const tetrisBreak = readline.createInterface(client); - tetrisBreak.on("line", function lineProcess(line) { - if (line === "") tetrisBreak.removeListener("line", lineProcess).close(); - const findout = line.indexOf("="), keyName = line.slice(0, findout), value = line.slice(findout+1); - if (findout <= 0) return; - if (keyName === "errno" && value !== "0") throw new Error(("wireguard-go error, code: ").concat(value)); - - // Drop - if ((["last_handshake_time_nsec", "protocol_version", "errno"]).includes(keyName)) return; - else if (keyName === "private_key") config.privateKey = Buffer.from(value, "hex").toString("base64"); - else if (keyName === "listen_port") config.portListen = Number(value); - else if (keyName === "endpoint") ((config.peers||(config.peers = {}))[latestPeer]).endpoint = value; - else if (keyName === "persistent_keepalive_interval") ((config.peers||(config.peers = {}))[latestPeer]).keepInterval = Number(value); - else if (keyName === "rx_bytes") ((config.peers||(config.peers = {}))[latestPeer]).rxBytes = Number(value); - else if (keyName === "tx_bytes") ((config.peers||(config.peers = {}))[latestPeer]).txBytes = Number(value); - else if (keyName === "last_handshake_time_sec") ((config.peers||(config.peers = {}))[latestPeer]).lastHandshake = new Date(Number(value) * 1000); - else if (keyName === "allowed_ip") { - if (!value) return; - ((config.peers||(config.peers = {}))[latestPeer]).allowedIPs = (((config.peers||(config.peers = {}))[latestPeer]).allowedIPs||[]).concat(value); - } else if (keyName === "preshared_key") { - if (value === "0000000000000000000000000000000000000000000000000000000000000000") return; - ((config.peers||(config.peers = {}))[latestPeer]).presharedKey = Buffer.from(value, "hex").toString("base64"); - } else if (keyName === "public_key") { - const keyDecode = Buffer.from(value, "hex").toString("base64"); - if (previewKey !== "public_key") (config.peers||(config.peers = {}))[latestPeer] = {}; - else { - config.publicKey = latestPeer; - (config.peers||(config.peers = {}))[keyDecode] = (config.peers||(config.peers = {}))[latestPeer]; - delete (config.peers||(config.peers = {}))[latestPeer]; - latestPeer = keyDecode; - } +export class Wireguard { + constructor(config?: SetConfig | GetConfig | Config) { + // super({}); + if (!config) return; + if (typeof config === "object") { + if (config instanceof Wireguard) return config; } - previewKey = keyName; - }); - client.write("get=1\n\n"); - await new Promise((done, reject) => tetrisBreak.on("error", reject).once("close", done)); - await finished(client.end()); - return config; -} \ No newline at end of file + } + + private _name: string; + get name() { + return this._name; + } + + /** + * Set Wireguard interface name + * @param name - Interface name + * @returns Wireguard + */ + set name(name: string) { + if (typeof name !== "string" || name.length === 0) throw new Error("Invalid name"); + this._name = name; + } + + private _portListen: number; + /** + * Sets the port to listen on. + * @param port - The port number to listen on. + * @returns The current instance of the `Wireguard` class. + * @throws {Error} If the provided port is not a number or is less than 0. + */ + setPortListen(port: number) { + if (typeof port !== "number" || port < 0) throw new Error("Invalid port"); + this._portListen = port; + return this; + } + + private _fwmark: number; + + /** + * Sets the fwmark value for the WireGuard interface. + * + * @param fwmark - The fwmark value to set. + * @returns The current instance of the `Wireguard` class. + * @throws {Error} If the `fwmark` value is not a number or is less than 0. + */ + setFwmark(fwmark: number) { + if (typeof fwmark !== "number" || fwmark < 0) throw new Error("Invalid fwmark"); + this._fwmark = fwmark; + return this; + } + + private _privateKey: string; + + /** + * Get interface public key + */ + public get publicKey() { + return key.publicKey(this._privateKey); + } + + /** + * Generate new private key and set to Wireguard interface + */ + setPrivateKey(): Promise & this; + /** + * Set private key to Wireguard interface + * @param privateKey - Private key + * @returns Wireguard + */ + setPrivateKey(privateKey: string): this; + setPrivateKey(privateKey?: string): this { + if (!privateKey) return Object.assign(key.privateKey().then((privateKey) => this._privateKey = privateKey), this); + else this._privateKey = privateKey; + return this; + } + + private _address: string[]; + + addNewAddress(address: string) { + if (isIP(address.split("/")[0]) === 0) throw new Error("Invalid IP address"); + const _addr = new Set(this._address); + _addr.add(address.split("/")[0]); + this._address = Array.from(_addr); + return this; + } + + removeAddress(address: string) { + if (isIP(address.split("/")[0]) === 0) throw new Error("Invalid IP address"); + const _addr = new Set(this._address); + _addr.delete(address.split("/")[0]); + this._address = Array.from(_addr); + return this; + } + + _peers: Map; + + /** + * Adds a new peer to the Wireguard interface. + * + * @param publicKey - The public key of the peer. + * @param peer - other configuration options for the peer. + * @throws Error if the peer is invalid. + */ + addNewPeer(publicKey: string, peer: Peer) { + if (!this._peers) this._peers = new Map(); + if (!((typeof publicKey === "string" && publicKey.length === key.Base64Length) && typeof peer === "object")) throw new Error("Invalid peer"); + let { allowedIPs, endpoint, keepInterval, presharedKey } = peer; + this._peers.set(publicKey, {}); + if ((typeof presharedKey === "string" && presharedKey.length === key.Base64Length)) this._peers.get(publicKey).presharedKey = presharedKey; + if (typeof keepInterval === "number") this._peers.get(publicKey).keepInterval = keepInterval; + if (typeof endpoint === "string") this._peers.get(publicKey).endpoint = endpoint; + if (Array.isArray(allowedIPs)) this._peers.get(publicKey).allowedIPs = allowedIPs.filter((ip) => isIP(ip.split("/")[0]) !== 0); + return new WireGuardPeer(publicKey, this); + } + + /** + * Removes a peer from the WireGuard interface. + * @param publicKey - The public key of the peer to remove. + * @returns The updated WireGuard interface. + */ + removePeer(publicKey: string) { + if (this._peers) this._peers.delete(publicKey); + return this; + } + + + + /** + * Converts the `Wireguard Interface` object to a JSON representation. + * @returns The JSON representation of the `Wireguard Interface` object. + */ + toJSON(): SetConfig { + const config: SetConfig = Object.create({}); + config.name = this._name; + config.privateKey = this._privateKey; + if (this._portListen) config.portListen = this._portListen; + if (this._fwmark) config.fwmark = this._fwmark; + if (this._address) config.address = this._address; + if (this._peers) config.peers = Array.from(this._peers||[]).map(([pubKey]) => new WireGuardPeer(pubKey, this).toJSON()).reduce((obj, [pubKey, peer]) => (obj[pubKey] = peer, obj), {}); + return config; + } + + /** + * Set new config to Wireguard interface or create new interface if not exists + * @returns Promise + */ + async deploy() { + return setConfig({ + ...(this.toJSON()), + replacePeers: true, + }); + } + + /** + * Deletes the WireGuard interface. + * @returns A promise that resolves when the interface is successfully deleted. + */ + async delete() { + return deleteInterface(this._name); + } + + /** + * Retrieves the configuration for the Wireguard interface. + */ + async getConfig() { + const { peers, privateKey, address, fwmark, portListen } = await getConfig(this._name); + this._privateKey = privateKey; + this._portListen = portListen; + this._address = address; + this._fwmark = fwmark; + + this._peers = new Map(Object.entries(peers)); + for (const [publicKey, { allowedIPs, endpoint, keepInterval, presharedKey }] of this._peers) { + this._peers.set(publicKey, { allowedIPs, endpoint, keepInterval, presharedKey }); + if (keepInterval === 0) delete this._peers.get(publicKey).keepInterval; + if (!presharedKey) delete this._peers.get(publicKey).presharedKey; + if (!endpoint) delete this._peers.get(publicKey).endpoint; + if (!allowedIPs) delete this._peers.get(publicKey).allowedIPs; + else if (allowedIPs.length === 0) delete this._peers.get(publicKey).allowedIPs; + } + } +} +export default Wireguard; \ No newline at end of file diff --git a/src/wginterface_test.ts b/src/wginterface_test.ts index bcdfaeb..5b3efca 100644 --- a/src/wginterface_test.ts +++ b/src/wginterface_test.ts @@ -1,54 +1,51 @@ import test from "node:test"; -import { setConfig, deleteInterface, WgConfigSet, getConfig } from "./wginterface.js"; -import { publicKey } from "./key.js"; -import { userInfo } from "os"; +import { Wireguard, getConfig, setConfig } from "./wginterface.js"; +import { presharedKey, privateKey, publicKey } from "./key.js"; +import assert from "node:assert"; -if (process.platform === "win32" || process.platform === "linux" && (userInfo().uid === 0)) { - test("Wireguard configuration", async t => { - // Config base - const peer1Key = 'EKgSatFzZtsv1qFJ6gE8HqfuA+tXzW+7vDeVc7Xaa2E=', peer2Key = '4BSvgiM9j5jjuR0Vg3gbqTFD5+CyuOU2K2kJE5+cakQ=', - config: WgConfigSet = { - privateKey: "4GTKsUfzodunTXaHtY/u+JhQN1D2CP1Sc+4D1VmpylY=", - address: [ - "10.66.124.1/32" - ], - peers: {} - }; +await test("Wireguard interface", async t => { + const config = new Wireguard; + config.name = "wg23"; + if (process.platform === "darwin") config.name = "utun23"; - config.peers[publicKey(peer1Key)] = { - allowedIPs: [ - "10.66.124.2" - ] - } + config.setPrivateKey(await privateKey()); + config.addNewAddress("10.66.66.1/32"); + config.addNewAddress("fd42:42:42::1/128"); - await t.test("Set config in interface", async () => { - await setConfig("wg23", config); - }); - - await t.test("Get config in interface", async () => { - const __config = await getConfig("wg23"); - if (!__config.peers[publicKey(peer1Key)]) throw new Error("Not exist peer 1!"); - }); - - config.peers[publicKey(peer1Key)].removeMe = true; - config.peers[publicKey(peer2Key)] = { - allowedIPs: [ - "10.66.124.3" - ] - } - - await t.test("Set config in interface", async () => { - await setConfig("wg23", config); - }); - - await t.test("Get config in interface", async () => { - const __config = await getConfig("wg23"); - if (__config.peers[publicKey(peer1Key)]) throw new Error("Invalid config get!"); - if (!__config.peers[publicKey(peer2Key)]) throw new Error("Not exist peer 2!"); - }); - - await t.test("Delete interface", async () => { - await deleteInterface("wg23"); - }); + const peer1 = await privateKey(); + config.addNewPeer(publicKey(peer1), { + keepInterval: 15, + presharedKey: await presharedKey(), + allowedIPs: [ + "10.66.66.2/32" + ] }); -} \ No newline at end of file + + const peer2 = await privateKey(); + config.addNewPeer(publicKey(peer2), { + keepInterval: 0, + allowedIPs: [ + "10.66.66.3/32" + ] + }); + + const jsonConfig = config.toJSON(); + + let skip: string; + await t.test("Create and Set config in interface", async () => setConfig(jsonConfig).catch(err => { skip = "Cannot set wireguard config"; return Promise.reject(err); })); + await t.test("Get config from interface", { skip }, async () => { + const config = await getConfig(jsonConfig.name); + // console.dir(config, { depth: null }); + + if (!config.peers[publicKey(peer1)]) throw new Error("Peer not exists in interface"); + if (!config.peers[publicKey(peer2)]) throw new Error("Peer not exists in interface"); + + assert.equal(config.peers[publicKey(peer1)].keepInterval, jsonConfig.peers[publicKey(peer1)].keepInterval); + assert.equal(config.peers[publicKey(peer1)].presharedKey, jsonConfig.peers[publicKey(peer1)].presharedKey); + + assert.deepEqual(config.peers[publicKey(peer1)].allowedIPs, jsonConfig.peers[publicKey(peer1)].allowedIPs); + assert.deepEqual(config.peers[publicKey(peer2)].allowedIPs, jsonConfig.peers[publicKey(peer2)].allowedIPs); + }); + + await t.test("Delete interface if exists", { skip }, async () => config.delete()); +}); \ No newline at end of file