diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..a9cde29 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,89 @@ +name: Test +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + linux_test: + runs-on: ubuntu-latest + strategy: + matrix: + node_version: [ 16.x, 18.x, 19.x, 20.x, 21.x ] + steps: + - uses: actions/checkout@v4 + name: Checkout + + - uses: actions/setup-node@v4 + name: Setup Node.js + with: + node-version: ${{ matrix.node_version }} + + - name: Install dependencies + run: | + echo "Host arch: $(uname -m)" + export DEBIAN_FRONTEND=noninteractive + sudo apt update + packages=( "binutils-multiarch" ); + if [[ $(uname -m) == "x86_64" ]];then + packages+=( "gcc-*aarch64-linux-gnu" "gcc-*aarch64-linux-gnu-base" "g++-*aarch64-linux-gnu" "libc6-arm64-cross" ) + else + packages+=( "libc6-amd64-cross" "g++-*x86-64-linux-gnu" "g++-*x86-64-linux-gnux32" "gcc-*x86-64-linux-gnu" "gcc-*x86-64-linux-gnux32" ) + if [[ $(uname -m) != "aarch64" ]];then + packages+=( "gcc-*aarch64-linux-gnu" "gcc-*aarch64-linux-gnu-base" "g++-*aarch64-linux-gnu" "libc6-arm64-cross" ) + fi + fi + sudo apt install -y ${packages[@]} + npm install --no-save --ignore-scripts + + - name: Build addon + run: npm run prebuildify -- -v + + - name: Test + run: sudo -E "$(command -v node)" --require ts-node/register --loader ts-node/esm ./src/index_test.ts + + - name: Upload generate interface + uses: actions/upload-artifact@v3 + with: + name: addrs_${{ runner.os }}_${{ matrix.node_version }} + path: "*.addrs.json" + + - name: Upload prebuilds interface + uses: actions/upload-artifact@v3 + with: + retention-days: 7 + name: prebuilds_${{ runner.os }} + path: "prebuilds/**" + + pack_package: + needs: linux_test + runs-on: ubuntu-latest + name: Pack npm package + env: + PACKAGE_VERSION: ${{ github.ref }} + steps: + - uses: actions/checkout@v4 + name: Code checkout + + - uses: actions/setup-node@v4 + name: Setup node.js + with: + node-version: 20.x + registry-url: https://registry.npmjs.org/ + + - name: Download all artefacts + uses: actions/download-artifact@v3 + with: + path: ./prebuilds + + - run: npm install --no-save --ignore-scripts + - run: npm pack + + - name: Upload npm package + uses: actions/upload-artifact@v3 + with: + name: Package_Pack + path: "*.tgz" diff --git a/.github/workflows/testProject.yml b/.github/workflows/test.yml similarity index 85% rename from .github/workflows/testProject.yml rename to .github/workflows/test.yml index 160fb9a..dc70508 100644 --- a/.github/workflows/testProject.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node_version: [ 16.x, 17.x, 18.x, 19.x, 20.x ] + node_version: [ 16.x, 18.x, 19.x, 20.x, 21.x ] steps: - uses: actions/checkout@v4 name: Checkout @@ -29,8 +29,11 @@ jobs: sudo apt install -y binutils-multiarch gcc-*aarch64-linux-gnu gcc-*aarch64-linux-gnu-base g++-*aarch64-linux-gnu libc6-arm64-cross npm install --no-save --ignore-scripts + - name: Build addon + run: npm run prebuildify -- -v + - name: Test - run: npm run prebuildify -- -v && sudo -E ./node_modules/.bin/mocha ./src + run: sudo -E node --require ts-node/register --loader ts-node/esm ./src/index_test.ts - name: Upload generate interface uses: actions/upload-artifact@v3 @@ -49,7 +52,7 @@ jobs: runs-on: macos-latest strategy: matrix: - node_version: [ 16.x, 17.x, 18.x, 19.x, 20.x ] + node_version: [ 16.x, 18.x, 19.x, 20.x, 21.x ] steps: - uses: actions/checkout@v4 name: Checkout @@ -74,8 +77,11 @@ jobs: - name: Install dependencies run: npm install --no-save --ignore-scripts + - name: Build addon + run: npm run prebuildify -- -v + - name: Test - run: npm run prebuildify -- -v && sudo -E ./node_modules/.bin/mocha ./src + run: sudo -E node --require ts-node/register --loader ts-node/esm ./src/index_test.ts - name: Upload generate interface uses: actions/upload-artifact@v3 @@ -94,7 +100,7 @@ jobs: runs-on: windows-latest strategy: matrix: - node_version: [ 16.x, 17.x, 18.x, 19.x, 20.x ] + node_version: [ 16.x, 18.x, 19.x, 20.x, 21.x ] steps: - uses: actions/checkout@v4 name: Checkout @@ -107,8 +113,11 @@ jobs: - name: Install dependencies run: npm install --no-save --ignore-scripts + - name: Build addon + run: npm run prebuildify -- -v + - name: Test - run: npm run prebuildify -- -v && ./node_modules/.bin/mocha ./src + run: node --require ts-node/register --loader ts-node/esm ./src/index_test.ts - name: Upload generate interface uses: actions/upload-artifact@v3 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e4bd5f..b9a3b76 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,10 +1,25 @@ image: node:lts test: - only: - - main stage: test + image: node:latest script: - - npm i -g typescript ts-node - - npm install --no-save && npm i -g mocha - - npm run build - - mocha 'tests/**/*.ts' + - | + echo "Host arch: $(uname -m)" + export DEBIAN_FRONTEND=noninteractive + sudo apt update + packages=( "binutils-multiarch" ); + if [[ $(uname -m) == "x86_64" ]];then + packages+=( "gcc-*aarch64-linux-gnu" "gcc-*aarch64-linux-gnu-base" "g++-*aarch64-linux-gnu" "libc6-arm64-cross" ) + else + packages+=( "libc6-amd64-cross" "g++-*x86-64-linux-gnu" "g++-*x86-64-linux-gnux32" "gcc-*x86-64-linux-gnu" "gcc-*x86-64-linux-gnux32" ) + if [[ $(uname -m) != "aarch64" ]];then + packages+=( "gcc-*aarch64-linux-gnu" "gcc-*aarch64-linux-gnu-base" "g++-*aarch64-linux-gnu" "libc6-arm64-cross" ) + fi + fi + sudo apt install -y ${packages[@]} + - npm install --no-save --ignore-scripts + - npm run prebuildify -- -v + - | + sudo="" + if command -v sudo; then sudo="sudo" fi + $sudo node --require ts-node/register --loader ts-node/esm ./src/index_test.ts diff --git a/.mocharc.yml b/.mocharc.yml deleted file mode 100644 index a2032ef..0000000 --- a/.mocharc.yml +++ /dev/null @@ -1,8 +0,0 @@ -exit: true -colors: true -full-trace: true -recursive: true -parallel: false -timeout: 0 -spec: [ "testPackage.test.cjs" ] -require: [ts-node/esm, ts-node/register] \ No newline at end of file diff --git a/.npmignore b/.npmignore index fb950ce..acabee7 100644 --- a/.npmignore +++ b/.npmignore @@ -3,6 +3,7 @@ node_modules/ /*.tgz # Typescript +src/**/*_test.* src/**/*.ts !src/**/*.d.ts diff --git a/README.md b/README.md index 09f26c8..db1c7d1 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,16 @@ other tools are wrappers over `wg`, `wireguard-tools.js` is not like that, it is - Userspace [(wireguard-go)](https://git.zx2c4.com/wireguard-go/about/) support. - Maneger wireguard interface (linux and windows create if not exist's). -- Generate `pre-shared`, `private` and `public` keys. +- 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 [`docs`](docs/README.md) folder. +- More info and example check [`wiki`](https://sirherobrine23.org/Wireguard/Wireguard-tools.js/wiki). > **Note** > > we have pre-copiled files for: > - `Windows`: x64, arm64 -> - `MacOS`: x64/intel, arm64 -> - `Linux`: x64/amd64, arm64 +> - `MacOS`: x64/intel, arm64/Apple silicon +> - `Linux`: x64/amd64, arm64/aarch64 > > else arch and system require copiler supported by `node-gyp` installed to compile Node addon. > diff --git a/addons/tools/wginterface.hh b/addons/tools/wginterface.hh index feec57e..3fc2c1e 100644 --- a/addons/tools/wginterface.hh +++ b/addons/tools/wginterface.hh @@ -262,7 +262,7 @@ class getConfig : public Napi::AsyncWorker { std::string publicKey; // Wireguard port listen - uint32_t portListen; + unsigned int portListen; // FirewallMark specifies a device's firewall mark // else set to 0, the firewall mark will be cleared. @@ -305,24 +305,28 @@ class getConfig : public Napi::AsyncWorker { const auto PeersObject = Napi::Object::New(env); for (auto &peer : peersVector) { const auto PeerObject = Napi::Object::New(env); - const std::string peerPubKey = peer.first; 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.last_handshake >= 0) PeerObject.Set("lastHandshake", Napi::Date::New(env, peerConfig.last_handshake)); - // if (peerConfig.last_handshake >= 0) PeerObject.Set("lastHandshake", peerConfig.last_handshake); - if (peerConfig.rxBytes >= 0) PeerObject.Set("rxBytes", peerConfig.rxBytes); - if (peerConfig.txBytes >= 0) PeerObject.Set("txBytes", peerConfig.txBytes); + 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); } - PeersObject.Set(peerPubKey, PeerObject); + // const std::string peerPubKey = peer.first; + PeersObject.Set(peer.first, PeerObject); } + + // Set peers to object config.Set("peers", PeersObject); // Resolve config json diff --git a/index.mjs b/index.mjs index b065e2f..552e969 100644 --- a/index.mjs +++ b/index.mjs @@ -1,3 +1,14 @@ -import __wg from "./src/index.js"; -export const { key, wgQuick, wginterface, constants, deleteInterface, getConfig, listDevices, setConfig } = __wg; -export default __wg["default"]||__wg; \ No newline at end of file +import * as __wg from "./src/index.js"; +export const { + constants, + key, + key_experimental, + wgQuick, + wginterface, + getConfig, + setConfig, + listDevices, + deleteInterface, +} = __wg; + +export default __wg.default; \ No newline at end of file diff --git a/package.json b/package.json index 73bd948..5c2f4e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wireguard-tools.js", "version": "1.8.1", - "description": "Control your wireguard interface from node.js, not a wireguard-tools wrapper!", + "description": "Control your wireguard interface from node.js, not a wireguard-tools/wg wrapper!", "private": false, "type": "commonjs", "main": "./src/index.js", @@ -14,12 +14,12 @@ "types": "./src/index.d.ts" } }, - "homepage": "https://github.com/Sirherobrine23/Wireguard-tools.js#readme", + "homepage": "https://sirherobrine23.org/Wireguard/Wireguard-tools.js#readme", "author": "Matheus Sampaio Queiroga (https://sirherobrine23.org/)", "license": "GPL-3.0-or-later", "repository": { "type": "git", - "url": "git+https://github.com/Sirherobrine23/Wireguard-tools.js.git" + "url": "git+https://sirherobrine23.org/Wireguard/Wireguard-tools.js.git" }, "keywords": [ "wireguard", @@ -29,7 +29,7 @@ "wireguard-utils" ], "bugs": { - "url": "https://github.com/Sirherobrine23/Wireguard-tools.js/issues/new" + "url": "https://sirherobrine23.org/Wireguard/Wireguard-tools.js/issues/new" }, "sponsor": { "url": "https://github.com/sponsors/Sirherobrine23" @@ -42,20 +42,19 @@ }, "scripts": { "install": "node libs/build.mjs", - "test": "node libs/build.mjs build --clean && mocha ./testPackage.test.cjs", + "test": "node libs/build.mjs build --clean && node --require ts-node/register --loader ts-node/esm --test src/**/*_test.ts", "dev": "node libs/build.mjs build", "prebuildify": "node libs/build.mjs build --auto", "prepack": "tsc --build --clean && tsc --build && node libs/build.mjs", "postpack": "tsc --build --clean" }, "devDependencies": { - "@types/node": "^20.8.3", - "mocha": "^10.2.0", - "ts-node": "^10.9.1", - "typescript": "^5.2.2" + "@types/node": "^20.11.0", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" }, "dependencies": { "node-addon-api": "^7.0.0", - "node-gyp": "^10.0.0" + "node-gyp": "^10.0.1" } -} +} \ No newline at end of file diff --git a/src/experimental/key.ts b/src/experimental/key.ts new file mode 100644 index 0000000..8ca7825 --- /dev/null +++ b/src/experimental/key.ts @@ -0,0 +1,195 @@ +import crypto from "node:crypto"; + +type BinArray = Float64Array|Uint8Array|number[]; + +function gf(init?: number[]) { + var r = new Float64Array(16); + if (init) { + for (var i = 0; i < init.length; ++i) + r[i] = init[i]; + } + return r; +} + +function pack(o: BinArray, n: BinArray) { + var b, m = gf(), t = gf(); + for (var i = 0; i < 16; ++i) + t[i] = n[i]; + carry(t); + carry(t); + carry(t); + for (var j = 0; j < 2; ++j) { + m[0] = t[0] - 0xffed; + for (var i = 1; i < 15; ++i) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); + m[i - 1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); + b = (m[15] >> 16) & 1; + m[14] &= 0xffff; + cswap(t, m, 1 - b); + } + for (var i = 0; i < 16; ++i) { + o[2 * i] = t[i] & 0xff; + o[2 * i + 1] = t[i] >> 8; + } +} + +function carry(o: BinArray) { + // var c; + for (var i = 0; i < 16; ++i) { + o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536); + o[i] &= 0xffff; + } +} + +function cswap(p: BinArray, q: BinArray, b: number) { + var t, c = ~(b - 1); + for (var i = 0; i < 16; ++i) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } +} + +function add(o: BinArray, a: BinArray, b: BinArray) { + for (var i = 0; i < 16; ++i) + o[i] = (a[i] + b[i]) | 0; +} + +function subtract(o: BinArray, a: BinArray, b: BinArray) { + for (var i = 0; i < 16; ++i) + o[i] = (a[i] - b[i]) | 0; +} + +function multmod(o: BinArray, a: BinArray, b: BinArray) { + var t = new Float64Array(31); + for (var i = 0; i < 16; ++i) { + for (var j = 0; j < 16; ++j) + t[i + j] += a[i] * b[j]; + } + for (var i = 0; i < 15; ++i) + t[i] += 38 * t[i + 16]; + for (var i = 0; i < 16; ++i) + o[i] = t[i]; + carry(o); + carry(o); +} + +function invert(o: BinArray, i: BinArray) { + var c = gf(); + for (var a = 0; a < 16; ++a) + c[a] = i[a]; + for (var a = 253; a >= 0; --a) { + multmod(c, c, c); + if (a !== 2 && a !== 4) + multmod(c, c, i); + } + for (var a = 0; a < 16; ++a) + o[a] = c[a]; +} + +function clamp(z: BinArray) { + z[31] = (z[31] & 127) | 64; + 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); +} + +/** + * Generate preshared key + */ +export async function presharedKey(): Promise { + var privateKey = new Uint8Array(32); + 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 + */ +export async function privateKey() { + var privateKey = Base64ToKey(await presharedKey()); + clamp(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 + * + * @param privateKey - Private key + */ +export function publicKey(privKey: string) { + var privateKey: Uint8Array = Base64ToKey(privKey); + var r: number, z = new Uint8Array(32); + var a = gf([1]), + b = gf([9]), + c = gf(), + d = gf([1]), + e = gf(), + f = gf(), + _121665 = gf([0xdb41, 1]), + _9 = gf([9]); + for (var i = 0; i < 32; ++i) + z[i] = privateKey[i]; + clamp(z); + for (var i = 254; i >= 0; --i) { + r = (z[i >>> 3] >>> (i & 7)) & 1; + cswap(a, b, r); + cswap(c, d, r); + add(e, a, c); + subtract(a, a, c); + add(c, b, d); + subtract(b, b, d); + multmod(d, e, e); + multmod(f, a, a); + multmod(a, c, a); + multmod(c, b, e); + add(e, a, c); + subtract(a, a, c); + multmod(b, a, a); + subtract(c, d, f); + multmod(a, c, _121665); + add(a, a, d); + multmod(c, c, a); + multmod(a, d, f); + multmod(d, b, _9); + multmod(b, e, e); + cswap(a, b, r); + cswap(c, d, r); + } + invert(c, c); + multmod(a, a, c); + pack(z, a); + return keyToBase64(z); +} \ No newline at end of file diff --git a/src/experimental/key_test.ts b/src/experimental/key_test.ts new file mode 100644 index 0000000..401d376 --- /dev/null +++ b/src/experimental/key_test.ts @@ -0,0 +1,20 @@ +import assert from "node:assert"; +import test from "node:test"; +import { presharedKey, privateKey, publicKey } from "./key"; + +test("Experimental: Generate key in javascript", async (t) => { + const PrivateKey = "yI7j4HI5kBKp+uDIsiKTGYdZooeyzF9i49yKRbmq1n8="; + t.test("Get public key", () => assert.strictEqual(publicKey(PrivateKey), "utkYO/qP/pxLaRCoPsZnpPx5G9hz/9DnBc3OTmk8uX0=")); + t.test("Generate Preshared key", async () => { await presharedKey() }); + t.test("Generate Private key", async () => { await privateKey() }); + + const max = 100; + await t.test(`Generate key ${max}`, async () => { + await Promise.all(Array(max).fill(null).map(async () => { + const _presharedKey = await presharedKey(); + const _privateKey = await privateKey(); + const _publicKey = publicKey(_privateKey); + return [_privateKey, { preshared: _presharedKey, pub: _publicKey }]; + })); + }); +}); diff --git a/src/index.ts b/src/index.ts index ece17fd..60c02e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ -import * as key from "./key"; -import * as wgQuick from "./quick"; -import * as wginterface from "./wginterface"; - +export * as key_experimental from "./experimental/key"; +export * as key from "./key"; +export * as wgQuick from "./quick"; export * from "./wginterface"; -export { key, wgQuick, wginterface }; -export default Object.assign({}, wginterface, { key, wgQuick, wginterface }); \ No newline at end of file +export * as wginterface from "./wginterface"; +export * as default from "./wginterface"; diff --git a/src/index_test.ts b/src/index_test.ts new file mode 100644 index 0000000..b74825a --- /dev/null +++ b/src/index_test.ts @@ -0,0 +1,3 @@ +import "./quick_test"; +import "./key_test"; +import "./experimental/key_test"; \ No newline at end of file diff --git a/src/key_test.ts b/src/key_test.ts new file mode 100644 index 0000000..32c87bf --- /dev/null +++ b/src/key_test.ts @@ -0,0 +1,20 @@ +import assert from "node:assert"; +import test from "node:test"; +import { presharedKey, privateKey, publicKey } from "./key"; + +test("Generate key", async (t) => { + const PrivateKey = "yI7j4HI5kBKp+uDIsiKTGYdZooeyzF9i49yKRbmq1n8="; + t.test("Get public key", async () => assert.strictEqual(await publicKey(PrivateKey), "utkYO/qP/pxLaRCoPsZnpPx5G9hz/9DnBc3OTmk8uX0=")); + t.test("Generate Preshared key", async () => { await presharedKey() }); + t.test("Generate Private key", async () => { await privateKey() }); + + const max = 100; + await t.test(`Generate key ${max}`, async () => { + await Promise.all(Array(max).fill(null).map(async () => { + const _presharedKey = await presharedKey(); + const _privateKey = await privateKey(); + const _publicKey = publicKey(_privateKey); + return [_privateKey, { preshared: _presharedKey, pub: _publicKey }]; + })); + }); +}); diff --git a/src/quick.ts b/src/quick.ts index a0fa850..2535a7e 100644 --- a/src/quick.ts +++ b/src/quick.ts @@ -1,9 +1,9 @@ import { isIP } from "net"; -import { WgConfig, constants } from "./wginterface"; +import { WgConfigSet, constants } from "./wginterface"; import { genKey, publicKey, keyObjectWithPreshared } from "./key"; import { randomInt } from "crypto"; -export interface QuickConfig extends WgConfig { +export interface QuickConfig extends WgConfigSet { DNS?: string[]; } diff --git a/src/quick_test.ts b/src/quick_test.ts new file mode 100644 index 0000000..45030a3 --- /dev/null +++ b/src/quick_test.ts @@ -0,0 +1,26 @@ +import { parse, stringify, QuickConfig } from "./quick"; +import test from "node:test"; +import assert from "node:assert"; + +const configJson: QuickConfig = { + privateKey: "uFVB0+R+IrQRFwKCiCWXFLFZsOS0tQPL4O1FYE3X6lU=", + peers: { + // 6MyCbRlWEJ9+UHRQlnSXWuWsCgboUwOLJL9loVR+/VY=, + "Xp/PbG0IApQvAaNoBWG36vt+PGPC4d9jHtfC/VDXs1o=": { + allowedIPs: [ + "10.6.6.3/32" + ] + } + } +}; +const configText: string = ""; + +test("Wireguard quick config", async t => { + await Promise.all([ + t.test("Stringify", () => {stringify(configJson);}), + t.test("Parse", () => { + parse + assert.equal(configText, configText); + }), + ]); +}); \ No newline at end of file diff --git a/src/wginterface.ts b/src/wginterface.ts index 00bd95c..02ade67 100644 --- a/src/wginterface.ts +++ b/src/wginterface.ts @@ -1,7 +1,7 @@ +import { promises as fs } from "fs"; +import { isIPv4, createConnection as netConnection } from "net"; import path from "path"; import readline from "readline"; -import { promises as fs } from "fs" -import { isIPv4, createConnection as netConnection } from "net"; import { finished } from "stream/promises"; if (process.platform === "win32") global.WIREGUARD_DLL_PATH = path.join(__dirname, "../addons/tools/win/wireguard-nt/bin", process.arch === "x64" ? "amd64" : process.arch, "wireguard.dll"); const addon = require("../libs/prebuildifyLoad.cjs")("wginterface", path.resolve(__dirname, "../")); @@ -15,7 +15,37 @@ async function exists(path: string) { return fs.open(path).then(o => o && (o.close().then(() => true, () => true))||true, () => false); } -export type WgConfig = { +export interface Peer { + /** Preshared key to peer */ + presharedKey?: string; + + /** keepInterval specifies the persistent keepalive interval for this peer */ + keepInterval?: number; + + /** Remote address or hostname to Wireguard connect or endpoint is the most recent source address used for communication by peer. */ + endpoint?: string; + + /** AllowedIPs specifies a list of allowed IP addresses in CIDR notation (`0.0.0.0/0`, `::/0`) */ + allowedIPs?: string[]; +}; + +export interface PeerSet extends Peer { + /** Mark this peer to be removed, any changes remove this option */ + removeMe?: boolean; +} + +export interface PeerGet extends Peer { + /** ReceiveBytes indicates the number of bytes received from this peer. */ + rxBytes?: number; + + /** TransmitBytes indicates the number of bytes transmitted to this peer. */ + txBytes?: number; + + /** Last peer Handshake */ + lastHandshake?: Date; +} + +export interface WgConfigBase { /** privateKey specifies a private key configuration */ privateKey?: string; /** publicKey specifies a public key configuration */ @@ -26,36 +56,16 @@ export type WgConfig = { fwmark?: number; /** Interface IP address'es */ Address?: string[]; - /** this option will remove all peers if `true` and add new peers */ - replacePeers?: boolean; - peers: { - [peerPublicKey: string]: { - /** Mark this peer to be removed, any changes remove this option */ - removeMe?: boolean; - - /** Preshared key to peer */ - presharedKey?: string; - /** keepInterval specifies the persistent keepalive interval for this peer */ - keepInterval?: number; - - /** Remote address or hostname to Wireguard connect or endpoint is the most recent source address used for communication by peer. */ - endpoint?: string; - - /** ReceiveBytes indicates the number of bytes received from this peer. */ - rxBytes?: number; - - /** TransmitBytes indicates the number of bytes transmitted to this peer. */ - txBytes?: number; - - /** Last peer Handshake */ - lastHandshake?: Date; + /** Interface peers */ + peers: Record; +} - /** AllowedIPs specifies a list of allowed IP addresses in CIDR notation (`0.0.0.0/0`, `::/0`) */ - allowedIPs?: string[]; - }; - } -}; +export interface WgConfigGet extends WgConfigBase {} +export interface WgConfigSet extends WgConfigBase { + /** this option will remove all peers if `true` and add new peers */ + replacePeers?: boolean; +} /** * Get Wireguard devices and locations @@ -67,17 +77,33 @@ export async function listDevices() { return devices; } +/** + * 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); - return fs.rm(path.join(defaultPath, (wgName).concat(".sock")), { force: true }); + const dev = (await listDevices()).find(s => s.name === wgName); + if (dev && dev.path) return fs.rm(dev.path, { force: true }); } /** * Set Wireguard config in interface + * + * in the Linux and Windows create if not exist interface + * * @param wgName - Interface name * @param config - Interface config */ -export async function setConfig(wgName: string, config: WgConfig) { +export async function setConfig(wgName: string, config: WgConfigSet): Promise { + if (process.platform === "darwin") { + if (!(wgName.match(/^tun([0-9]+)$/))) throw new Error("Invalid name, example to valid: tun0"); + // Replace to tun name + // const devNames = Object.keys(networkInterfaces()).filter(s => s.toLowerCase().startsWith("tun")); + // for (let i = 0n; i < BigInt(Number.MAX_SAFE_INTEGER); i++) if (devNames.indexOf((wgName = ("tun").concat(i.toString())))) break; + } + 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")); @@ -140,12 +166,12 @@ export async function setConfig(wgName: string, config: WgConfig) { * @param wgName - Interface name * @returns */ -export async function getConfig(wgName: string) { +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: WgConfig = Object(); + const config: WgConfigGet = Object(); let latestPeer: string, previewKey: string; const tetrisBreak = readline.createInterface(client); diff --git a/testPackage.test.cjs b/testPackage.test.cjs deleted file mode 100644 index 11efd39..0000000 --- a/testPackage.test.cjs +++ /dev/null @@ -1,35 +0,0 @@ -const wg = require("./src/index.ts"); - -describe("Keys", function() { - it("Preshared key", async () => wg.key.presharedKey()); - it("Private key", async () => wg.key.privateKey()); - it("Public key", async () => wg.key.publicKey(await wg.key.privateKey())); - it("Key pack 1", async () => wg.key.genKey()); - it("Key pack 2", async () => wg.key.genKey(true)); -}); - -const interfaceName = String(((process.env.WG_INETRFACE||"").length > 0) ? process.env.WG_INETRFACE : (process.platform === "darwin" ? ("utun").concat(String(randomInt(20, 1023))) : "wgtest")); - -describe(("Wireguard interface (").concat(interfaceName, ")"), function() { - this.timeout(Infinity); - /** @type { wg.WgConfig } */ - const interfaceConfig = {}; - it("Generate config", async () => { - interfaceConfig.Address = [ "10.0.0.1/24" ]; - interfaceConfig.privateKey = await wg.key.privateKey(); - interfaceConfig.portListen = 8030; - interfaceConfig.peers = {}; - await Promise.all(Array(10).fill(null).map(async (_, peerGenIndex) => { - const peerKey = await wg.key.genKey(true); - interfaceConfig.peers[peerKey.publicKey] = { - presharedKey: peerKey.presharedKey, - allowedIPs: [ `10.0.0.${peerGenIndex+2}/24` ], - keepInterval: 5 - }; - })); - // console.dir(interfaceConfig, { depth: null }); - }); - it("Set config", async () => wg.setConfig(interfaceName, interfaceConfig)); - it("Get config", async () => wg.getConfig(interfaceName)); - it("Delete interface", async () => wg.deleteInterface(interfaceName)); -}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 85e398d..2a34f73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,14 +13,11 @@ "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "allowJs": true, - "composite": true, "lib": [ "ESNext" ] }, "exclude": [ - "**/*.test.*", - "**/test*/**", "**/libs/**", "**/docs/**", "node_modules/",