From d8c7be18188315d26bfd3e7bab6a32ef27097953 Mon Sep 17 00:00:00 2001 From: Matheus Sampaio Queiroga Date: Sun, 7 Jul 2024 22:04:03 -0300 Subject: [PATCH 1/5] Droping rebory and migrate to cmake-js - Updated test actions Signed-off-by: Matheus Sampaio Queiroga --- .github/workflows/test.yaml | 13 +- CMakeLists.txt | 86 ++++++++++ README.md | 36 +++- addon/linux/wginterface.cpp | 4 +- addon/main.cpp | 12 -- addon/userspace/go/main.go | 1 - addon/wg.hh | 20 --- binding.yaml | 59 ------- package.json | 16 +- src/addons.ts | 55 ++++++ src/wginterface.ts | 324 ++---------------------------------- src/wginterface_test.ts | 78 ++++----- 12 files changed, 238 insertions(+), 466 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 binding.yaml create mode 100644 src/addons.ts diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ccda88e..7ff70e2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [ 16.x, 18.x, 20.x, latest ] + node-version: [ 18.x, 20.x, 21.x, latest ] steps: - name: Disable sudo PATH replace run: | @@ -32,18 +32,13 @@ jobs: - 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" + go-version: ">=1.22" - name: Install build dependencies - run: sudo apt update && sudo apt install -y build-essential + run: sudo apt update && sudo apt install -y build-essential cmake - 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 + run: npm run build && sudo node --no-warnings --loader ts-node/esm src/index_test.js diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cd2faa2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0042 NEW) + +project (wg) + +add_compile_definitions(NAPI_VERSION=8 NAPI_CPP_EXCEPTIONS) +# set_target_properties(PROPERTIES CXX_STANDARD 17) +# set_target_properties(PROPERTIES C_STANDARD 17) +set(CMAKE_C_STANDARD 17) +set(CMAKE_CXX_STANDARD 17) + +include_directories(${CMAKE_JS_INC}) +include_directories("node_modules/node-addon-api") +include_directories("addon") +include_directories("addon/genKey") + +if(UNIX) + add_definitions(-fpermissive -fexceptions -w -fpermissive -fPIC) +endif() + +file(GLOB GSOURCE_FILES + "addon/main.cpp" + "addon/genKey/wgkeys.cpp" +) + +if(MSVC) + file(GLOB SOURCE_FILES "addon/win/wginterface.cpp") + include_directories("addon/win") + add_compile_definitions(_HAS_EXCEPTIONS=1 ONSTARTADDON) + target_link_libraries(${PROJECT_NAME} + "wbemuuid.lib" + "bcrypt.lib" + "crypt32.lib" + "iphlpapi.lib" + "kernel32.lib" + "ntdll.lib" + "ws2_32.lib" + "setupapi.lib" + ) +elseif(UNIX AND NOT APPLE OR ANDROID) + include_directories("addon/linux") + file(GLOB SOURCE_FILES + "addon/linux/wireguard.c" + "addon/linux/wginterface.cpp" + ) +else() + message(STATUS "Buiding go Userspace") + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/wg-go.o) + file(REMOVE_RECURSE ${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/wg-go.o) + endif() + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/wg-go.h) + file(REMOVE_RECURSE ${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/wg-go.h) + endif() + set(ENV{CGO_ENABLED} 1) + set(ENV{LDFLAGS} -w) + # Remove CXX and CC envs to CGO + set(ENV{DCXX} ENV{CXX}) + set(ENV{DCC} ENV{CC}) + set(ENV{CXX}) + set(ENV{CC}) + execute_process( + COMMAND go build -trimpath -v -o ../wg-go.o -buildmode c-archive . + # COMMAND env + RESULT_VARIABLE GOCODE + OUTPUT_VARIABLE GOBUILDLOG + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/go + ) + set(ENV{CXX} ENV{DCXX}) + set(ENV{CC} ENV{DCC}) + if(NOT GOCODE EQUAL "0") + message(FATAL_ERROR "cannot build go userspace code exit ${GOCODE}\n${GOBUILDLOG}") + endif() + include_directories("addon/userspace") + set(USERSPACEOBJ ${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/wg-go.o) + file(GLOB SOURCE_FILES "addon/userspace/wginterface.cpp") +endif() + +add_library(${PROJECT_NAME} SHARED ${GSOURCE_FILES} ${SOURCE_FILES} ${CMAKE_JS_SRC}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${USERSPACEOBJ} ${CMAKE_JS_LIB}) + +if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET) + # Generate node.lib + execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) +endif() \ No newline at end of file diff --git a/README.md b/README.md index e36f4c1..d65c5ff 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,42 @@ Manage your Wireguard interfaces directly from Node.js without any wrappers over `wg` or `wg-quick` -```js +> [!WARNING] +> Require cmake and tools (GCC/GCC++, clang or Visual Studio) to build this addon +> +> New versions does't include prebuilt binaries +```js +import { setConfig, getConfig, key, Config } from "../index.js" + +const tunName = process.platform === "darwin" ? "utun10" : "wg3" // Tunnel name, in MacOS/Darwin require start with utun prefix +let currentConfig: Config +try { + currentConfig = await getConfig(tunName) // Check if exists tun +} catch { + // Create new wireguard tun + currentConfig = { + name: tunName, + privateKey: await key.privateKey(), + portListen: 5820, + address: [ + "10.66.66.1/24" + ], + peers: {} + } +} + +// Add new Peer +const peerPrivate = await key.privateKey() +currentConfig.peers[key.publicKey(peerPrivate)] = { + presharedKey: await key.presharedKey(), + allowedIPs: [ + "10.66.66.2/24" + ] +} + +// Deploy new Config +await setConfig(currentConfig) ``` ## Licences diff --git a/addon/linux/wginterface.cpp b/addon/linux/wginterface.cpp index 384adc7..dd67830 100644 --- a/addon/linux/wginterface.cpp +++ b/addon/linux/wginterface.cpp @@ -30,7 +30,7 @@ std::string getWireguardVersion() { #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"); @@ -100,9 +100,9 @@ void WireguardConfig::getWireguardConfig() { 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.keepInterval = peer->persistent_keepalive_interval; PeerConfig.lastHandshake = peer->last_handshake_time.tv_sec*1000; PeerConfig.rxBytes = peer->rx_bytes; PeerConfig.txBytes = peer->tx_bytes; diff --git a/addon/main.cpp b/addon/main.cpp index 107051f..e13c0c1 100644 --- a/addon/main.cpp +++ b/addon/main.cpp @@ -36,18 +36,6 @@ Napi::Object StartAddon(const Napi::Env env, const Napi::Object exports) { } })); - 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(); diff --git a/addon/userspace/go/main.go b/addon/userspace/go/main.go index 632288c..3b571b1 100644 --- a/addon/userspace/go/main.go +++ b/addon/userspace/go/main.go @@ -16,7 +16,6 @@ import ( "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/ipc" "golang.zx2c4.com/wireguard/tun" - ) const levelLog = device.LogLevelError diff --git a/addon/wg.hh b/addon/wg.hh index 3a5f84b..7639521 100644 --- a/addon/wg.hh +++ b/addon/wg.hh @@ -40,26 +40,6 @@ class DeleteInterface : public Promised { } }; -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() { diff --git a/binding.yaml b/binding.yaml deleted file mode 100644 index 48dce19..0000000 --- a/binding.yaml +++ /dev/null @@ -1,59 +0,0 @@ -name: wginterface -defines: - - "NODE_VERSION=8" - - "NAPI_CPP_EXCEPTIONS" -includes: - - node_modules/node-addon-api - - ./addon -sources: - - "addon/main.cpp" - - "addon/genKey/wgkeys.cpp" - - "addon/userspace/wginterface.cpp" -prebuild: - - shell: bash - cwd: ./addon/userspace/go - ifOs: - - "!win32" - - "!linux" - 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: - linux: - sources: - - "!addon/userspace/wginterface.cpp" - - "addon/linux/wginterface.cpp" - - "addon/linux/wireguard.c" - flags: - - "!-fno-exceptions" - - "-fpermissive" - - "-fexceptions" - - "-w" - - "-fpermissive" - - "-fPIC" - win32: - sources: - - "!addon/userspace/wginterface.cpp" - - "addon/win/wginterface.cpp" - libraries: - - wbemuuid.lib - - bcrypt.lib - - crypt32.lib - - iphlpapi.lib - - kernel32.lib - - ntdll.lib - - ws2_32.lib - - setupapi.lib - defines: - - "_HAS_EXCEPTIONS=1" - - "ONSTARTADDON" - darwin: - flags: - - "!-fno-exceptions" - - "-fpermissive" - - "-fexceptions" - - "-w" - - "-fpermissive" diff --git a/package.json b/package.json index e928491..52df7b8 100644 --- a/package.json +++ b/package.json @@ -33,19 +33,19 @@ "node": ">=16.0.0" }, "scripts": { - "install": "rebory prebuild", - "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", + "install": "cmake-js compile", + "build": "cmake-js rebuild", + "test": "cmake-js compile && node --no-warnings --loader ts-node/esm src/index_test.js", + "prepack": "tsc --build --clean && tsc --build", "postpack": "tsc --build --clean" }, "devDependencies": { - "@types/node": "^20.11.26", + "@types/node": "^20.14.10", "ts-node": "^10.9.2", - "typescript": "^5.4.2" + "typescript": "^5.5.3" }, "dependencies": { - "node-addon-api": "^8.0.0", - "rebory": "^0.2.10" + "cmake-js": "^7.3.0", + "node-addon-api": "^8.0.0" } } diff --git a/src/addons.ts b/src/addons.ts new file mode 100644 index 0000000..e64a63a --- /dev/null +++ b/src/addons.ts @@ -0,0 +1,55 @@ +import path from "node:path"; +import fs from "node:fs/promises"; + +const __dirname = import.meta.dirname || path.dirname((await import("node:url")).fileURLToPath(import.meta.url)); +export const projectRoot: string = !process["resourcesPath"] ? path.resolve(__dirname, "..") : process["resourcesPath"]; + +declare global { + namespace NodeJS { + export interface DlopenModule { + exports: any; + } + + interface Process { + /** + * The `process.dlopen()` method allows dynamically loading shared objects. It is primarily used by `require()` to load C++ Addons, and should not be used directly, except in special cases. In other words, `require()` should be preferred over `process.dlopen()` unless there are specific reasons such as custom dlopen flags or loading from ES modules. + * + * An important requirement when calling `process.dlopen()` is that the `module` instance must be passed. Functions exported by the C++ Addon are then accessible via `module.exports`. + * @param module - module to export + * @param filename - Addon path + * @param flags - The flags argument is an integer that allows to specify dlopen behavior. See the [os.constants.dlopen](https://nodejs.org/docs/latest/api/os.html#dlopen-constants) documentation for details. + * @default flags `os.constants.dlopen.RTLD_LAZY` + * @since v9.0.0 + */ + dlopen(module: DlopenModule, filename: string, flags: number): void; + /** + * The `process.dlopen()` method allows dynamically loading shared objects. It is primarily used by `require()` to load C++ Addons, and should not be used directly, except in special cases. In other words, `require()` should be preferred over `process.dlopen()` unless there are specific reasons such as custom dlopen flags or loading from ES modules. + * + * An important requirement when calling `process.dlopen()` is that the `module` instance must be passed. Functions exported by the C++ Addon are then accessible via `module.exports`. + * @param module - module to export + * @param filename - Addon path + * @since v0.1.16 + */ + dlopen(module: DlopenModule, filename: string): void; + } + } +} + +async function exists(filePath: string) { + return fs.access(path.resolve(filePath)).then(() => true, () => false); +} + +export async function LoadAddon(addonFile: string, exports?: Record): Promise { + let _addonFile: string = null + if (await exists(addonFile)) _addonFile = addonFile; + else if (await exists(path.resolve(projectRoot, addonFile))) _addonFile = path.resolve(projectRoot, addonFile) + else if (await exists(path.resolve(projectRoot, addonFile+".node"))) _addonFile = path.resolve(projectRoot, addonFile+".node") + else if (await exists(path.resolve(projectRoot, "build/Release", addonFile))) _addonFile = path.resolve(projectRoot, "build/Release", addonFile) + else if (await exists(path.resolve(projectRoot, "build/Release", addonFile+".node"))) _addonFile = path.resolve(projectRoot, "build/Release", addonFile+".node") + else if (await exists(path.resolve(projectRoot, "build/Debug", addonFile))) _addonFile = path.resolve(projectRoot, "build/Debug", addonFile) + else if (await exists(path.resolve(projectRoot, "build/Debug", addonFile+".node"))) _addonFile = path.resolve(projectRoot, "build/Debug", addonFile+".node") + if (!_addonFile) throw new Error("Cannot load required addon") + let ext: NodeJS.DlopenModule = {exports: Object.assign({}, exports)} + process.dlopen(ext, _addonFile) + return ext.exports +} \ No newline at end of file diff --git a/src/wginterface.ts b/src/wginterface.ts index 5d562e7..5027254 100644 --- a/src/wginterface.ts +++ b/src/wginterface.ts @@ -1,8 +1,5 @@ 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)); +import { LoadAddon, projectRoot } from "./addons.js"; interface Peer { /** Preshared key to peer */ @@ -34,7 +31,7 @@ export interface GetPeer extends Peer { lastHandshake: Date; } -interface Config { +export interface Config { /** Wireguard interface name */ name: string; @@ -63,9 +60,15 @@ export interface SetConfig extends Config { replacePeers?: boolean; }; -export const addon = (await loadAddon(path.resolve(__dirname, "../binding.yaml"))).wginterface.load_addon<{ - /** Current Wireguard drive version */ - driveVersion?: string; +/** + * Exported wireguard-tools.js addon + */ +export const addon = await LoadAddon<{ + /** External functions or drive info */ + constants: { + /** Current Wireguard drive version */ + driveVersion?: string; + } /** * Delete interface if exists @@ -73,13 +76,6 @@ export const addon = (await loadAddon(path.resolve(__dirname, "../binding.yaml") */ deleteInterface(name: string): Promise; - /** - * Get Wireguard interfaces list - * - * if running in userspace return socket (UAPI) path's - */ - listDevices(): Promise; - /** * Get current config from Wireguard interface * @param name - Interface name @@ -91,303 +87,13 @@ export const addon = (await loadAddon(path.resolve(__dirname, "../binding.yaml") * @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") +}>("wg", { + WIN32DLLPATH: path.resolve(projectRoot, "addon/win", (process.arch === "x64" && "amd64") || (process.arch === "ia32" && "x86") || process.arch, "wireguard.dll") }); export const { - driveVersion, - listDevices, + constants: {driveVersion = "Unknown"}, getConfig, setConfig, deleteInterface -} = addon; - -export class WireGuardPeer { - constructor(public publicKey: string, private __Wg: Wireguard) { } - - async getStats() { - const { rxBytes, txBytes, lastHandshake } = await getConfig(this.__Wg.name).then((config) => config.peers[this.publicKey]); - return { - rxBytes, - txBytes, - lastHandshake - }; - } - - 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]; - } -} - -/** - * Maneger Wireguard interface and peers simple and fast - */ -export class Wireguard { - constructor(config?: SetConfig | GetConfig | Config) { - // super({}); - if (!config) return; - if (typeof config === "object") { - if (config instanceof Wireguard) return config; - } - } - - 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 +} = addon; \ No newline at end of file diff --git a/src/wginterface_test.ts b/src/wginterface_test.ts index 5b3efca..bcc5a35 100644 --- a/src/wginterface_test.ts +++ b/src/wginterface_test.ts @@ -1,51 +1,39 @@ -import test from "node:test"; -import { Wireguard, getConfig, setConfig } from "./wginterface.js"; -import { presharedKey, privateKey, publicKey } from "./key.js"; +import test from "node:test" +import { SetConfig, GetConfig, Config, setConfig, getConfig, deleteInterface } from "./wginterface.js" +import { privateKey, publicKey, presharedKey } from "./key.js" +import { format } from "node:util"; import assert from "node:assert"; await test("Wireguard interface", async t => { - const config = new Wireguard; - config.name = "wg23"; - if (process.platform === "darwin") config.name = "utun23"; + const newConfig: Config = { + name: process.platform === "darwin" ? "utun23" : "wg10", + privateKey: await privateKey(), + portListen: 8260, + address: [ + "10.66.66.1/24" + ], + peers: {} + } + for (let i = 0; i != 10; i++) { + newConfig.peers[publicKey(await privateKey())] = { + presharedKey: await presharedKey(), + keepInterval: 25, + allowedIPs: [ + format("10.66.66.%d/24", i+2) + ], + } + } - config.setPrivateKey(await privateKey()); - config.addNewAddress("10.66.66.1/32"); - config.addNewAddress("fd42:42:42::1/128"); - - const peer1 = await privateKey(); - config.addNewPeer(publicKey(peer1), { - keepInterval: 15, - presharedKey: await presharedKey(), - allowedIPs: [ - "10.66.66.2/32" - ] + await t.test("Set config", async () => setConfig(newConfig)); + await t.test("Get config and check", async () => { + const currentConfig = await getConfig(newConfig.name) + assert.equal(currentConfig.privateKey, newConfig.privateKey) + for (const pubKey in newConfig.peers) { + console.log("Current: %O\nIn set: %O", currentConfig.peers[pubKey], newConfig.peers[pubKey]) + if (!currentConfig.peers[pubKey]) throw new Error("one peer not exists in currentConfig") + else if (currentConfig.peers[pubKey].presharedKey != newConfig.peers[pubKey].presharedKey) throw new Error("presharedKey is mismatch") + else if (currentConfig.peers[pubKey].keepInterval != newConfig.peers[pubKey].keepInterval) throw new Error("keepInterval is mismatch") + } }); - - 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()); + await t.test("Deleting interface", async () => deleteInterface(newConfig.name)); }); \ No newline at end of file -- 2.45.2 From d0ed246bcadca5c03f72571e38d05d957a6f4c1e Mon Sep 17 00:00:00 2001 From: Matheus Sampaio Queiroga Date: Sun, 7 Jul 2024 23:26:59 -0300 Subject: [PATCH 2/5] Update cmake builder --- CMakeLists.txt | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd2faa2..719ee3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,22 +11,34 @@ set(CMAKE_C_STANDARD 17) set(CMAKE_CXX_STANDARD 17) include_directories(${CMAKE_JS_INC}) -include_directories("node_modules/node-addon-api") -include_directories("addon") -include_directories("addon/genKey") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/addon") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/addon/genKey") +# Include N-API wrappers +# $ node -p "require('node-addon-api').include" +# "/home/will/projects/financialcpp/financialcpp/node_modules/node-addon-api" +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR +) + +# strip `"` and `\n` from the output above +string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) +string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + +include_directories(PRIVATE ${NODE_ADDON_API_DIR}) if(UNIX) add_definitions(-fpermissive -fexceptions -w -fpermissive -fPIC) endif() file(GLOB GSOURCE_FILES - "addon/main.cpp" - "addon/genKey/wgkeys.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/addon/main.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/addon/genKey/wgkeys.cpp" ) if(MSVC) - file(GLOB SOURCE_FILES "addon/win/wginterface.cpp") - include_directories("addon/win") + file(GLOB SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/addon/win/wginterface.cpp") + include_directories("${CMAKE_CURRENT_SOURCE_DIR}/addon/win") add_compile_definitions(_HAS_EXCEPTIONS=1 ONSTARTADDON) target_link_libraries(${PROJECT_NAME} "wbemuuid.lib" @@ -39,10 +51,10 @@ if(MSVC) "setupapi.lib" ) elseif(UNIX AND NOT APPLE OR ANDROID) - include_directories("addon/linux") + include_directories("${CMAKE_CURRENT_SOURCE_DIR}/addon/linux") file(GLOB SOURCE_FILES - "addon/linux/wireguard.c" - "addon/linux/wginterface.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/addon/linux/wireguard.c" + "${CMAKE_CURRENT_SOURCE_DIR}/addon/linux/wginterface.cpp" ) else() message(STATUS "Buiding go Userspace") @@ -71,9 +83,9 @@ else() if(NOT GOCODE EQUAL "0") message(FATAL_ERROR "cannot build go userspace code exit ${GOCODE}\n${GOBUILDLOG}") endif() - include_directories("addon/userspace") + include_directories("${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace") set(USERSPACEOBJ ${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/wg-go.o) - file(GLOB SOURCE_FILES "addon/userspace/wginterface.cpp") + file(GLOB SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/addon/userspace/wginterface.cpp") endif() add_library(${PROJECT_NAME} SHARED ${GSOURCE_FILES} ${SOURCE_FILES} ${CMAKE_JS_SRC}) -- 2.45.2 From 99b2320b622ea151bc1cb51b9f139de99c8f56d8 Mon Sep 17 00:00:00 2001 From: Matheus Sampaio Queiroga Date: Sun, 7 Jul 2024 23:33:30 -0300 Subject: [PATCH 3/5] Minor fix and update npm ignore --- .npmignore | 5 ++++- src/quick_test.ts | 1 + src/wginterface_test.ts | 2 +- tsconfig.json | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.npmignore b/.npmignore index fe51b68..a20a18d 100644 --- a/.npmignore +++ b/.npmignore @@ -2,6 +2,9 @@ node_modules/ /*.tgz +# Builder dir +build/ + # Typescript src/**/*_test.* src/**/*.ts @@ -18,4 +21,4 @@ src/**/*.ts # Project .vscode-ctags* -*.addrs.json \ No newline at end of file +*.addrs.json diff --git a/src/quick_test.ts b/src/quick_test.ts index 2952c18..1a97f49 100644 --- a/src/quick_test.ts +++ b/src/quick_test.ts @@ -24,6 +24,7 @@ PublicKey = 15PMkuIeQNM9AlknHb+c10y8e3fzOihZJpuD23y+d0c= AllowedIPs = 10.88.198.220/32, 192.168.15.112/32, 2002:0A58:C6DC::/128, 2002:C0A8:0F70::/128`; const StaticConfigJson: QuickConfig = { + name: "wg0", privateKey: "2Ll/2LCXDlLVZcBCBZ6QeXB4qEF+bTzmuOBxnpu57WY=", portListen: 38451, PostUp: [ diff --git a/src/wginterface_test.ts b/src/wginterface_test.ts index bcc5a35..dc3459c 100644 --- a/src/wginterface_test.ts +++ b/src/wginterface_test.ts @@ -1,5 +1,5 @@ import test from "node:test" -import { SetConfig, GetConfig, Config, setConfig, getConfig, deleteInterface } from "./wginterface.js" +import { Config, setConfig, getConfig, deleteInterface } from "./wginterface.js" import { privateKey, publicKey, presharedKey } from "./key.js" import { format } from "node:util"; import assert from "node:assert"; diff --git a/tsconfig.json b/tsconfig.json index 9fc6b3f..b4b92ea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,7 @@ "exclude": [ "**/libs/**", "**/docs/**", + "build/**", "node_modules/", "index.mjs" ], @@ -28,4 +29,4 @@ "esm": true, "transpileOnly": true } -} \ No newline at end of file +} -- 2.45.2 From 9d15ec2756b1091bbfcb96675592a755ce1080fb Mon Sep 17 00:00:00 2001 From: Matheus Sampaio Queiroga Date: Mon, 8 Jul 2024 11:56:06 -0300 Subject: [PATCH 4/5] Fix quick test - Problems know: Userspace have segment fault, finding problems --- src/addons.ts | 8 ++--- src/index.ts | 2 +- src/key.ts | 4 --- src/maneger.ts | 79 ++++++++++++++++++++++++++++++++++++++++++++++ src/quick.ts | 2 +- src/quick_test.ts | 1 - src/wginterface.ts | 2 +- 7 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 src/maneger.ts diff --git a/src/addons.ts b/src/addons.ts index e64a63a..251d3ed 100644 --- a/src/addons.ts +++ b/src/addons.ts @@ -6,7 +6,7 @@ export const projectRoot: string = !process["resourcesPath"] ? path.resolve(__di declare global { namespace NodeJS { - export interface DlopenModule { + export interface Moduledlopen { exports: any; } @@ -21,7 +21,7 @@ declare global { * @default flags `os.constants.dlopen.RTLD_LAZY` * @since v9.0.0 */ - dlopen(module: DlopenModule, filename: string, flags: number): void; + dlopen(module: Moduledlopen, filename: string, flags: number): void; /** * The `process.dlopen()` method allows dynamically loading shared objects. It is primarily used by `require()` to load C++ Addons, and should not be used directly, except in special cases. In other words, `require()` should be preferred over `process.dlopen()` unless there are specific reasons such as custom dlopen flags or loading from ES modules. * @@ -30,7 +30,7 @@ declare global { * @param filename - Addon path * @since v0.1.16 */ - dlopen(module: DlopenModule, filename: string): void; + dlopen(module: Moduledlopen, filename: string): void; } } } @@ -49,7 +49,7 @@ export async function LoadAddon(addonFile: string, exports?: Record { + private _privateKey: string + + /** Current privateKey */ + get privateKey(): string { + return this._privateKey + } + + /** Get publicKey from privateKey */ + get publicKey(): string { + if (!this._privateKey) throw new Error("Set private key to get publicKey") + return publicKey(this._privateKey) + } + + /** @deprecated set privateKey only */ + set publicKey(_key: string) { + throw new Error("Set privateKey only") + } + + /** Set interface privateKey */ + set privateKey(key: string) { + this._privateKey = key + } + + /** Generate privateKey to interface */ + async generatePrivateKey() { + this._privateKey = await privateKey() + } + + private _portListen: number + get portListen() { return this._portListen; } + set portListen(port: number) { + if (port < 0 || port > 65534) throw new Error("Invalid port to listening"); + this._portListen = port + } + + /** Get config */ + toJSON(wgName: string): wginterface.Config { + const config: wginterface.Config = { + name: wgName, + portListen: this._portListen, + privateKey: this.privateKey, + peers: {}, + } + for (const [publicKey, peerSettings] of this.entries()) { + config.peers[publicKey] = { + keepInterval: peerSettings.keepInterval, + presharedKey: peerSettings.presharedKey, + endpoint: peerSettings.endpoint, + allowedIPs: peerSettings.allowedIPs + } + } + return config; + } + + /** Get quick config from current config */ + toString(extraConfig?: Omit) { + return quick.stringify(Object.assign({}, extraConfig, this.toJSON("wg0"))) + } + + /** Deploy config to wireguard interface */ + async deployConfig(wgName: string) { + return wginterface.setConfig(this.toJSON(wgName)) + } + + /** Add peer to Map and check config */ + async addPeer(publicKey: string, peerConfig: wginterface.Peer) { + if (peerConfig.allowedIPs?.length > 0) { + for (const ipAddress of peerConfig.allowedIPs) if (isIP(ipAddress.split("/")[0]) === 0) throw new Error("Invalid ip address in allowedIPs") + } + peerConfig.keepInterval + this.set(publicKey, peerConfig) + } +} \ No newline at end of file diff --git a/src/quick.ts b/src/quick.ts index 1465a5b..c75754b 100644 --- a/src/quick.ts +++ b/src/quick.ts @@ -2,7 +2,7 @@ import { isIP } from "net"; import { format } from "util"; import { SetConfig } from "./wginterface.js"; -export interface QuickConfig extends SetConfig, Partial> { +export interface QuickConfig extends Omit, Partial> { DNS?: string[]; Table?: number; MTU?: number; diff --git a/src/quick_test.ts b/src/quick_test.ts index 1a97f49..2952c18 100644 --- a/src/quick_test.ts +++ b/src/quick_test.ts @@ -24,7 +24,6 @@ PublicKey = 15PMkuIeQNM9AlknHb+c10y8e3fzOihZJpuD23y+d0c= AllowedIPs = 10.88.198.220/32, 192.168.15.112/32, 2002:0A58:C6DC::/128, 2002:C0A8:0F70::/128`; const StaticConfigJson: QuickConfig = { - name: "wg0", privateKey: "2Ll/2LCXDlLVZcBCBZ6QeXB4qEF+bTzmuOBxnpu57WY=", portListen: 38451, PostUp: [ diff --git a/src/wginterface.ts b/src/wginterface.ts index 5027254..16b4348 100644 --- a/src/wginterface.ts +++ b/src/wginterface.ts @@ -1,7 +1,7 @@ import path from "node:path"; import { LoadAddon, projectRoot } from "./addons.js"; -interface Peer { +export interface Peer { /** Preshared key to peer */ presharedKey?: string; -- 2.45.2 From da3cc1583406541aef3231dc6355cce05cf5bc32 Mon Sep 17 00:00:00 2001 From: Matheus Sampaio Queiroga Date: Mon, 8 Jul 2024 23:07:51 -0300 Subject: [PATCH 5/5] Refactored go module - Userspace now avaible to current process not externals - Pretty code - Fix hex key decode --- .npmignore | 5 +- .vscode/settings.json | 4 +- .zed/settings.json | 38 +++++ addon/genKey/wgkeys.cpp | 18 ++- addon/genKey/wgkeys.hh | 6 +- addon/userspace/go/go.sum | 6 - addon/userspace/go/main.go | 247 +++++++++----------------------- addon/userspace/ipc.cpp | 54 ------- addon/userspace/wginterface.cpp | 238 ++++++++++++++---------------- compile_flags.txt | 3 + src/wginterface_test.ts | 24 ++-- 11 files changed, 249 insertions(+), 394 deletions(-) create mode 100644 .zed/settings.json delete mode 100644 addon/userspace/ipc.cpp create mode 100644 compile_flags.txt diff --git a/.npmignore b/.npmignore index a20a18d..00b1a16 100644 --- a/.npmignore +++ b/.npmignore @@ -10,10 +10,11 @@ src/**/*_test.* src/**/*.ts !src/**/*.d.ts -# vscode +# IDEs .devcontainer/ -.vscode/ .vscode-ctags +.vscode/ +.zed/ # Github and Git .github/ diff --git a/.vscode/settings.json b/.vscode/settings.json index c84b4a9..50210cc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -116,6 +116,8 @@ "xtr1common": "cpp", "xtree": "cpp", "xutility": "cpp", - "fstream": "cpp" + "fstream": "cpp", + "deque": "cpp", + "stack": "cpp" } } \ No newline at end of file diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..8d58387 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,38 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings +{ + "remove_trailing_whitespace_on_save": true, + "ensure_final_newline_on_save": true, + "formatter": "auto", + "format_on_save": "off", + "tab_size": 2, + "languages": { + "TypeScript": { + "hard_tabs": false + "format_on_save": true, + "code_actions_on_format": { + "source.organizeImports": true, + "source.removeUnusedImports": true + } + }, + "JavaScript": { + "hard_tabs": false + "format_on_save": true, + "code_actions_on_format": { + "source.organizeImports": true, + "source.removeUnusedImports": true + } + }, + "Go": { + "format_on_save": true + }, + "C++": { + "hard_tabs": false + }, + "C": { + "hard_tabs": false + } + } +} diff --git a/addon/genKey/wgkeys.cpp b/addon/genKey/wgkeys.cpp index de20d61..fbb3e15 100644 --- a/addon/genKey/wgkeys.cpp +++ b/addon/genKey/wgkeys.cpp @@ -1,7 +1,9 @@ #include "wgkeys.hh" #include #include +#include #include +#include #include #ifdef __linux__ @@ -263,7 +265,7 @@ void wgKeys::generatePublic(wg_key public_key, const wg_key private_key) { memzero_explicit(f, sizeof(f)); } -std::string wgKeys::generatePublic(const std::string private_key) { +std::string wgKeys::generatePublic(const std::string &private_key) { wg_key public_key; wg_key private_key_; stringToKey(private_key_, private_key); @@ -282,7 +284,7 @@ bool key_is_zero(const uint8_t key[32]) { } void wgKeys::stringToKey(wg_key key, std::string keyBase64) { - auto base64 = keyBase64.c_str(); + const char* base64 = keyBase64.c_str(); if (keyBase64.length() != B64_WG_KEY_LENGTH || base64[B64_WG_KEY_LENGTH - 1] != '=') throw std::string("invalid key, length: ") @@ -315,8 +317,7 @@ 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]); + 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] = '='; @@ -325,7 +326,7 @@ std::string wgKeys::toString(const wg_key key) { return std::string(base64); } -std::string wgKeys::toHex(const std::string keyBase64) { +std::string wgKeys::toHex(const std::string &keyBase64) { wg_key key; wgKeys::stringToKey(key, keyBase64); char hex[65]; @@ -334,8 +335,11 @@ std::string wgKeys::toHex(const std::string keyBase64) { return std::string(hex); } -std::string wgKeys::HextoBase64(const std::string keyHex) { +std::string wgKeys::HextoBase64(const std::string &s_hex) { wg_key key; - for (int i = 0; i < 32; ++i) sscanf(keyHex.c_str() + i * 2, "%02x", &key[i]); + for(unsigned i = 0, uchr ; i < s_hex.length() ; i += 2) { + sscanf( s_hex.c_str()+ i, "%2x", &uchr); // conversion + key[i/2] = uchr; // save as char + } return wgKeys::toString(key); } \ No newline at end of file diff --git a/addon/genKey/wgkeys.hh b/addon/genKey/wgkeys.hh index 7422c83..e592a5d 100644 --- a/addon/genKey/wgkeys.hh +++ b/addon/genKey/wgkeys.hh @@ -14,10 +14,10 @@ namespace wgKeys { std::string toString(const wg_key key); // Convert base64 to hex key - std::string toHex(const std::string keyBase64); + std::string toHex(const std::string &keyBase64); // Convert hex to base64 - std::string HextoBase64(const std::string keyHex); + std::string HextoBase64(const std::string &keyHex); // Convert base64 to wg_key void stringToKey(wg_key key, std::string keyBase64); @@ -31,7 +31,7 @@ namespace wgKeys { // 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); + std::string generatePublic(const std::string &private_key); } #endif \ No newline at end of file diff --git a/addon/userspace/go/go.sum b/addon/userspace/go/go.sum index 2820e27..4db6943 100644 --- a/addon/userspace/go/go.sum +++ b/addon/userspace/go/go.sum @@ -1,15 +1,9 @@ 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/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= diff --git a/addon/userspace/go/main.go b/addon/userspace/go/main.go index 3b571b1..66ad85a 100644 --- a/addon/userspace/go/main.go +++ b/addon/userspace/go/main.go @@ -4,49 +4,24 @@ import "C" import ( "fmt" - "os" - "os/signal" - "runtime" "runtime/debug" "strings" - "syscall" - _ "unsafe" "golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/device" - "golang.zx2c4.com/wireguard/ipc" "golang.zx2c4.com/wireguard/tun" ) -const levelLog = device.LogLevelError - -//go:linkname socketDirectory golang.xz2c4.com/wireguard/ipc.socketDirectory -var socketDirectory = "/var/run/wireguard" - -func init() { - if runtime.GOOS == "windows" { - socketDirectory = `\\.\pipe\ProtectedPrefix\Administrators\WireGuard` - } -} - -// 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() - }() + +// Default log level to print in terminal +const levelLog = device.LogLevelSilent + +func getCharErr(err error) *C.char { + if err == nil { + return C.CString("") + } + return C.CString(err.Error()) } // Get wireguard-go version @@ -65,158 +40,68 @@ func wgVersion() *C.char { 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("") -} +// End process function callbacks +var Tuns = make(map[string]tun.Device) +var Devices = make(map[string]*device.Device) // 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("") - } +//export listInternalTuns +func listInternalTuns() *C.char { var uapis []string - for _, file := range Files { - if file.IsDir() { - continue - } - uapis = append(uapis, strings.Join(([]string{socketDirectory, file.Name()}), "/")) + for tun := range Tuns { + uapis = append(uapis, tun) } return C.CString(strings.Join(uapis, "\x00") + "\x00") } + +// Delete interface +// +//export stopWg +func stopWg(wgName *C.char) (bool, *C.char) { + tunName := C.GoString(wgName) + if dev, ok := Devices[tunName]; ok { + dev.Close() + if tun, ok := Tuns[tunName]; ok { + if err := tun.Close(); err != nil { + return false, getCharErr(err) + } + } + return true, nil + } + return false, nil +} + +// Set config in tun, if not exist create and add to Tuns +// +//export setWg +func setWg(wgName, wgConfig *C.char) *C.char { + tunName, configString := C.GoString(wgName), C.GoString(wgConfig) + _, okTuns := Tuns[tunName] + _, okDev := Devices[tunName] + if !(okTuns || okDev) { + logger := device.NewLogger(levelLog, fmt.Sprintf("(%s) ", tunName)) + // open TUN device (or use supplied fd) + tdev, err := tun.CreateTUN(tunName, device.DefaultMTU) + Tuns[tunName] = tdev + if err != nil { + return getCharErr(err) + } + Devices[tunName] = device.NewDevice(tdev, conn.NewDefaultBind(), logger) + } + dev := Devices[tunName] + return getCharErr(dev.IpcSet(configString)) +} + +// Get config from Tuns device +// +//export getWg +func getWg(wgName *C.char) (*C.char, *C.char) { + tunName := C.GoString(wgName) + if dev, ok := Devices[tunName]; ok { + config, err := dev.IpcGet() + return C.CString(config), getCharErr(err) + } + return nil, getCharErr(fmt.Errorf("device not exists")) +} diff --git a/addon/userspace/ipc.cpp b/addon/userspace/ipc.cpp deleted file mode 100644 index e65de77..0000000 --- a/addon/userspace/ipc.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#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 index b8ccf08..ce3a19c 100644 --- a/addon/userspace/wginterface.cpp +++ b/addon/userspace/wginterface.cpp @@ -1,8 +1,11 @@ #include "wginterface.hh" #include "userspace/wg-go.h" -#include "userspace/ipc.cpp" #include "genKey/wgkeys.hh" +#include #include +#include +#include +#include #include #include #include @@ -16,161 +19,134 @@ #include #include +void IpManeger::SetInInterface(std::string interfaceName) {} +void IpManeger::GetInInterface(std::string interfaceName) {} + // Ignore if required to windows -std::string driveLoad(std::map load) {} +std::string driveLoad(std::map load) { return ""; } std::string getWireguardVersion() { return std::string(wgVersion()); } void WireguardDevices::getInterfaces() { - size_t len; char *device_name, *devicesList = listUapis(); + size_t len; char *device_name, *devicesList = listInternalTuns(); 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; + auto status = stopWg((char*)wgName.c_str()); + if (!!status.r1) throw std::string(status.r1); } 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; - } + std::string userspaceConfig; - 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"); + if (this->privateKey.length() == Base64WgKeyLength) userspaceConfig = userspaceConfig.append("private_key=").append(wgKeys::toHex(this->privateKey)).append("\n"); + if (this->portListen >= 0) userspaceConfig = userspaceConfig.append("listen_port=").append(std::to_string(this->portListen)).append("\n"); + if (this->fwmark >= 0) userspaceConfig = userspaceConfig.append("fwmark=").append(std::to_string(this->fwmark)).append("\n"); + if (this->replacePeers) userspaceConfig = userspaceConfig.append("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; + userspaceConfig = userspaceConfig.append("public_key=").append(wgKeys::toHex(peer.first)).append("\n"); + auto config = peer.second; + if (config.removeMe) { + userspaceConfig = userspaceConfig.append("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); + if (config.presharedKey.length() == Base64WgKeyLength) userspaceConfig = userspaceConfig.append("preshared_key=").append(wgKeys::toHex(config.presharedKey)).append("\n"); + if (config.keepInterval > 0) userspaceConfig = userspaceConfig.append("persistent_keepalive_interval=").append(std::to_string(config.keepInterval)).append("\n"); + if (config.endpoint.length() > 0) userspaceConfig = userspaceConfig.append("endpoint=").append(config.endpoint).append("\n"); + if (config.allowedIPs.size() > 0) { + userspaceConfig = userspaceConfig.append("replace_allowed_ips=true\n"); + for (auto s : config.allowedIPs.getIpParsed()) userspaceConfig = userspaceConfig.append("allowed_ip=").append(s.Address).append("/").append(std::to_string(s.Mask)).append("\n"); } } - 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)); + std::string err = setWg((char*)this->name.c_str(), (char*)userspaceConfig.append("\n").c_str()); + if (!err.empty()) throw err; } -void IpManeger::SetInInterface(std::string interfaceName) {} -void IpManeger::GetInInterface(std::string interfaceName) {} \ No newline at end of file +std::vector splitLines(const std::string& str) { + std::vector result; + std::stringstream ss(str); + std::string line; + // Loop until the end of the string + while (std::getline(ss, line)) { + if (!line.empty()) { + result.push_back(line); + } + } + return result; +} + +void WireguardConfig::getWireguardConfig() { + auto status = getWg((char*)this->name.c_str()); + if (strlen(status.r1) > 0) throw std::string(status.r1, strlen(status.r1)); + std::string wgConfig(status.r0, strlen(status.r0)); + std::string pubKey; + int ret = 0; + for (auto line : splitLines(wgConfig)) { + std::string key(line.substr(0, line.find("="))), value(line.substr(line.find("=")+1)); + if (key == "private_key") { + this->privateKey = wgKeys::HextoBase64(value); + } else if (key == "listen_port") { + this->portListen = (unsigned short)std::strtoul(value.c_str(), NULL, 0); + } else if (key == "public_key") { + // Insert to peer key + pubKey = wgKeys::HextoBase64(value); + this->Peers[pubKey] = Peer{}; + } else if (key == "preshared_key" && pubKey.length() > 0) { + this->Peers[pubKey].presharedKey = wgKeys::HextoBase64(value); + } else if (key == "allowed_ip" && pubKey.length() > 0) { + this->Peers[pubKey].allowedIPs.push_back(value); + } else if (key == "persistent_keepalive_interval" && pubKey.length() > 0) { + this->Peers[pubKey].keepInterval = std::stoi(value); + } else if (key == "last_handshake_time_sec" && pubKey.length() > 0) { + } else if (key == "last_handshake_time_nsec" && pubKey.length() > 0) { + this->Peers[pubKey].lastHandshake = ({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = std::strtoull(value.c_str(), &end, 10); if (*end || num > 0x7fffffffffffffffULL) break; num; }); + } else if (key == "rx_bytes" && pubKey.length() > 0) { + this->Peers[pubKey].rxBytes = std::stoull(value); + } else if (key == "tx_bytes" && pubKey.length() > 0) { + this->Peers[pubKey].txBytes = std::stoull(value); + } else if (key == "endpoint" && pubKey.length() > 0) { + char *cvalue = strdup(value.c_str()); + char *begin, *end; + struct addrinfo *resolved; + struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_DGRAM, .ai_protocol = IPPROTO_UDP }; + if (!value.length()) break; + if (value[0] == '[') { + begin = &value[1]; + end = strchr(cvalue, ']'); + if (!end) break; + *end++ = '\0'; + if (*end++ != ':' || !*end) break; + } else { + begin = cvalue; + end = strrchr(cvalue, ':'); + 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[pubKey].endpoint = value; + else { + freeaddrinfo(resolved); + break; + } + freeaddrinfo(resolved); + } else if (key == "errno") { + ret = -({ unsigned long long num; char *end; if (!char_is_digit(value[0])) break; num = std::strtoull(value.c_str(), &end, 10); if (*end || num > 0x7fffffffU) break; num; }); + break; + } else if (key == "protocol_version") { + } + } + if (ret < 0) throw std::string("Failed to get wireguard config"); +} diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..b47ad71 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,3 @@ +-Iaddon/ +-Iaddon/genKey +-std=c++17 \ No newline at end of file diff --git a/src/wginterface_test.ts b/src/wginterface_test.ts index dc3459c..82fef91 100644 --- a/src/wginterface_test.ts +++ b/src/wginterface_test.ts @@ -1,8 +1,8 @@ -import test from "node:test" -import { Config, setConfig, getConfig, deleteInterface } from "./wginterface.js" -import { privateKey, publicKey, presharedKey } from "./key.js" -import { format } from "node:util"; import assert from "node:assert"; +import test from "node:test"; +import { format } from "node:util"; +import { presharedKey, privateKey, publicKey } from "./key.js"; +import { Config, deleteInterface, getConfig, setConfig } from "./wginterface.js"; await test("Wireguard interface", async t => { const newConfig: Config = { @@ -19,21 +19,27 @@ await test("Wireguard interface", async t => { presharedKey: await presharedKey(), keepInterval: 25, allowedIPs: [ - format("10.66.66.%d/24", i+2) + format("10.66.66.%d/32", i+2) ], } } + function checkExists(arg0: T, arg1: T) { + for (const a1 of arg0) { + if (!Array.from(arg1).includes(a1)) throw new Error(format("%O not includes in %O", a1, arg1)) + } + } + await t.test("Set config", async () => setConfig(newConfig)); await t.test("Get config and check", async () => { const currentConfig = await getConfig(newConfig.name) assert.equal(currentConfig.privateKey, newConfig.privateKey) for (const pubKey in newConfig.peers) { - console.log("Current: %O\nIn set: %O", currentConfig.peers[pubKey], newConfig.peers[pubKey]) if (!currentConfig.peers[pubKey]) throw new Error("one peer not exists in currentConfig") - else if (currentConfig.peers[pubKey].presharedKey != newConfig.peers[pubKey].presharedKey) throw new Error("presharedKey is mismatch") - else if (currentConfig.peers[pubKey].keepInterval != newConfig.peers[pubKey].keepInterval) throw new Error("keepInterval is mismatch") + else if (currentConfig.peers[pubKey].presharedKey != newConfig.peers[pubKey].presharedKey) throw new Error("presharedKey is mismatch"); + else if (currentConfig.peers[pubKey].keepInterval != newConfig.peers[pubKey].keepInterval) throw new Error("keepInterval is mismatch"); + checkExists(newConfig.peers[pubKey].allowedIPs, currentConfig.peers[pubKey].allowedIPs); } }); await t.test("Deleting interface", async () => deleteInterface(newConfig.name)); -}); \ No newline at end of file +}); -- 2.45.2