@sourceregistry/node-wireguard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +105 -0
- package/bin/x86_64-linux-gnu/node-wireguard.node +0 -0
- package/binding.gyp +53 -0
- package/lib/binding.d.ts +5 -0
- package/lib/binding.js +45 -0
- package/lib/index.d.ts +56 -0
- package/lib/index.js +115 -0
- package/lib/types/AllowedIP.d.ts +5 -0
- package/lib/types/AllowedIP.js +2 -0
- package/lib/types/Config.d.ts +17 -0
- package/lib/types/Config.js +2 -0
- package/lib/types/Device.d.ts +23 -0
- package/lib/types/Device.js +2 -0
- package/lib/types/Key.d.ts +5 -0
- package/lib/types/Key.js +2 -0
- package/lib/types/Peer.d.ts +22 -0
- package/lib/types/Peer.js +2 -0
- package/lib/types/PeerConfig.d.ts +24 -0
- package/lib/types/PeerConfig.js +2 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/index.js +22 -0
- package/package.json +80 -0
- package/src/WireGuardClient.cpp +490 -0
- package/src/WireGuardClient.h +32 -0
- package/src/WireGuardTypes.h +68 -0
- package/src/crypto/Key.cpp +77 -0
- package/src/crypto/Key.h +30 -0
- package/src/helpers/Array.h +26 -0
- package/src/helpers/AsyncPromise.h +91 -0
- package/src/helpers/IfName.cpp +27 -0
- package/src/helpers/IfName.h +17 -0
- package/src/netlink/NlAttr.cpp +398 -0
- package/src/netlink/NlAttr.h +40 -0
- package/src/netlink/NlSocket.cpp +143 -0
- package/src/netlink/NlSocket.h +44 -0
- package/src/netlink/RtLink.cpp +217 -0
- package/src/netlink/RtLink.h +34 -0
- package/src/netlink/wireguard_uapi.h +68 -0
- package/src/node-wireguard.cpp +48 -0
- package/src/uapi/UapiCodec.cpp +185 -0
- package/src/uapi/UapiCodec.h +22 -0
- package/src/uapi/UapiSocket.cpp +116 -0
- package/src/uapi/UapiSocket.h +27 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "netlink/NlSocket.h"
|
|
4
|
+
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <napi.h>
|
|
7
|
+
|
|
8
|
+
// N-API class mirroring wgctrl-go's *wgctrl.Client, extended with interface
|
|
9
|
+
// lifecycle (createDevice/deleteDevice) since this addon — unlike wgctrl-go —
|
|
10
|
+
// also owns creating/destroying the WireGuard link itself (see plan decision:
|
|
11
|
+
// "full lifecycle"). One instance owns one genetlink socket for its lifetime.
|
|
12
|
+
class WireGuardClient : public Napi::ObjectWrap<WireGuardClient> {
|
|
13
|
+
public:
|
|
14
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
15
|
+
explicit WireGuardClient(const Napi::CallbackInfo &info);
|
|
16
|
+
|
|
17
|
+
private:
|
|
18
|
+
Napi::Value CreateDevice(const Napi::CallbackInfo &info);
|
|
19
|
+
Napi::Value DeleteDevice(const Napi::CallbackInfo &info);
|
|
20
|
+
Napi::Value Devices(const Napi::CallbackInfo &info);
|
|
21
|
+
Napi::Value Device(const Napi::CallbackInfo &info);
|
|
22
|
+
Napi::Value ConfigureDevice(const Napi::CallbackInfo &info);
|
|
23
|
+
Napi::Value SetUp(const Napi::CallbackInfo &info);
|
|
24
|
+
Napi::Value SetDown(const Napi::CallbackInfo &info);
|
|
25
|
+
Napi::Value SetAddress(const Napi::CallbackInfo &info);
|
|
26
|
+
Napi::Value DeleteAddress(const Napi::CallbackInfo &info);
|
|
27
|
+
Napi::Value Close(const Napi::CallbackInfo &info);
|
|
28
|
+
|
|
29
|
+
// Shared (not owned exclusively) so an in-flight AsyncWorker keeps the
|
|
30
|
+
// socket alive even if the JS wrapper is closed/GC'd before it finishes.
|
|
31
|
+
std::shared_ptr<netlink::NlSocket> sock_;
|
|
32
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <array>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <optional>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
namespace wg {
|
|
10
|
+
|
|
11
|
+
constexpr size_t kKeyLen = 32;
|
|
12
|
+
using Key = std::array<uint8_t, kKeyLen>;
|
|
13
|
+
|
|
14
|
+
struct AllowedIP {
|
|
15
|
+
std::string ip; // textual IPv4/IPv6 address
|
|
16
|
+
uint8_t family; // AF_INET or AF_INET6
|
|
17
|
+
uint8_t cidr;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Read-only peer status, as returned by WG_CMD_GET_DEVICE (mirrors wgtypes.Peer).
|
|
21
|
+
struct Peer {
|
|
22
|
+
Key publicKey{};
|
|
23
|
+
std::optional<Key> presharedKey;
|
|
24
|
+
std::optional<std::string> endpoint; // "host:port"
|
|
25
|
+
uint16_t persistentKeepaliveInterval = 0; // seconds
|
|
26
|
+
int64_t lastHandshakeTimeSec = 0; // unix seconds, 0 = never
|
|
27
|
+
uint64_t receiveBytes = 0;
|
|
28
|
+
uint64_t transmitBytes = 0;
|
|
29
|
+
std::vector<AllowedIP> allowedIPs;
|
|
30
|
+
uint32_t protocolVersion = 0;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Read-only device snapshot, as returned by WG_CMD_GET_DEVICE or the UAPI
|
|
34
|
+
// `get=1` request (mirrors wgtypes.Device, plus `userspace` to record which
|
|
35
|
+
// backend produced it - see DeviceToJs in WireGuardClient.cpp).
|
|
36
|
+
struct Device {
|
|
37
|
+
std::string name;
|
|
38
|
+
bool userspace = false; // true if fetched via the UAPI socket backend, not kernel netlink
|
|
39
|
+
std::optional<Key> privateKey;
|
|
40
|
+
std::optional<Key> publicKey;
|
|
41
|
+
uint16_t listenPort = 0;
|
|
42
|
+
uint32_t firewallMark = 0;
|
|
43
|
+
std::vector<Peer> peers;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Peer configuration input for WG_CMD_SET_DEVICE (mirrors wgtypes.PeerConfig).
|
|
47
|
+
// optional<T> absent == "leave unchanged"; present (even zero-valued) == "apply this value".
|
|
48
|
+
struct PeerConfig {
|
|
49
|
+
Key publicKey{};
|
|
50
|
+
bool remove = false;
|
|
51
|
+
bool updateOnly = false;
|
|
52
|
+
std::optional<Key> presharedKey;
|
|
53
|
+
std::optional<std::string> endpoint; // "host:port"
|
|
54
|
+
std::optional<uint16_t> persistentKeepaliveInterval; // seconds; 0 clears
|
|
55
|
+
bool replaceAllowedIPs = false;
|
|
56
|
+
std::vector<AllowedIP> allowedIPs;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Device configuration input for WG_CMD_SET_DEVICE (mirrors wgtypes.Config).
|
|
60
|
+
struct Config {
|
|
61
|
+
std::optional<Key> privateKey; // all-zero key clears the private key
|
|
62
|
+
std::optional<uint16_t> listenPort;
|
|
63
|
+
std::optional<uint32_t> firewallMark; // 0 clears
|
|
64
|
+
bool replacePeers = false;
|
|
65
|
+
std::vector<PeerConfig> peers;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
} // namespace wg
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#include "Key.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <sodium.h>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
namespace crypto {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
void ClampScalar(wg::Key &key) {
|
|
12
|
+
key[0] &= 248;
|
|
13
|
+
key[31] &= 127;
|
|
14
|
+
key[31] |= 64;
|
|
15
|
+
}
|
|
16
|
+
} // namespace
|
|
17
|
+
|
|
18
|
+
wg::Key GeneratePrivateKey() {
|
|
19
|
+
wg::Key key{};
|
|
20
|
+
randombytes_buf(key.data(), wg::kKeyLen);
|
|
21
|
+
ClampScalar(key);
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
wg::Key GeneratePresharedKey() {
|
|
26
|
+
wg::Key key{};
|
|
27
|
+
randombytes_buf(key.data(), wg::kKeyLen);
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
wg::Key PublicKeyFromPrivate(const wg::Key &privateKey) {
|
|
32
|
+
wg::Key pub{};
|
|
33
|
+
if (crypto_scalarmult_base(pub.data(), privateKey.data()) != 0) {
|
|
34
|
+
throw std::runtime_error("crypto_scalarmult_base failed");
|
|
35
|
+
}
|
|
36
|
+
return pub;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
std::string KeyToBase64(const wg::Key &key) {
|
|
40
|
+
// sodium_base64_ENCODED_LEN includes the null terminator.
|
|
41
|
+
std::vector<char> out(sodium_base64_ENCODED_LEN(wg::kKeyLen, sodium_base64_VARIANT_ORIGINAL));
|
|
42
|
+
sodium_bin2base64(out.data(), out.size(), key.data(), wg::kKeyLen, sodium_base64_VARIANT_ORIGINAL);
|
|
43
|
+
return std::string(out.data());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
wg::Key KeyFromBase64(const std::string &b64) {
|
|
47
|
+
wg::Key key{};
|
|
48
|
+
size_t decodedLen = 0;
|
|
49
|
+
if (sodium_base642bin(key.data(), wg::kKeyLen, b64.c_str(), b64.size(), nullptr, &decodedLen,
|
|
50
|
+
nullptr, sodium_base64_VARIANT_ORIGINAL) != 0 ||
|
|
51
|
+
decodedLen != wg::kKeyLen) {
|
|
52
|
+
throw std::invalid_argument("invalid base64-encoded WireGuard key: " + b64);
|
|
53
|
+
}
|
|
54
|
+
return key;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
std::string KeyToHex(const wg::Key &key) {
|
|
58
|
+
std::vector<char> out(wg::kKeyLen * 2 + 1); // 2 hex chars/byte + '\0', per sodium_bin2hex's hex_maxlen contract
|
|
59
|
+
sodium_bin2hex(out.data(), out.size(), key.data(), wg::kKeyLen);
|
|
60
|
+
return std::string(out.data());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
wg::Key KeyFromHex(const std::string &hex) {
|
|
64
|
+
wg::Key key{};
|
|
65
|
+
size_t decodedLen = 0;
|
|
66
|
+
if (sodium_hex2bin(key.data(), wg::kKeyLen, hex.c_str(), hex.size(), nullptr, &decodedLen, nullptr) != 0 ||
|
|
67
|
+
decodedLen != wg::kKeyLen) {
|
|
68
|
+
throw std::invalid_argument("invalid hex-encoded WireGuard key: " + hex);
|
|
69
|
+
}
|
|
70
|
+
return key;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
bool IsZeroKey(const wg::Key &key) {
|
|
74
|
+
return std::all_of(key.begin(), key.end(), [](uint8_t b) { return b == 0; });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} // namespace crypto
|
package/src/crypto/Key.h
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../WireGuardTypes.h"
|
|
4
|
+
|
|
5
|
+
namespace crypto {
|
|
6
|
+
|
|
7
|
+
// Generates a 32-byte Curve25519 private key, clamped per RFC7748 (same
|
|
8
|
+
// clamping `wg genkey` applies), from libsodium's CSPRNG.
|
|
9
|
+
wg::Key GeneratePrivateKey();
|
|
10
|
+
|
|
11
|
+
// Generates an opaque 32-byte preshared key (raw random bytes, not a scalar).
|
|
12
|
+
wg::Key GeneratePresharedKey();
|
|
13
|
+
|
|
14
|
+
// Computes the X25519 public key for a (clamped) private key.
|
|
15
|
+
wg::Key PublicKeyFromPrivate(const wg::Key &privateKey);
|
|
16
|
+
|
|
17
|
+
// Base64 (standard, padded) encode/decode, matching wgtypes.Key.String()/ParseKey().
|
|
18
|
+
std::string KeyToBase64(const wg::Key &key);
|
|
19
|
+
wg::Key KeyFromBase64(const std::string &b64); // throws std::invalid_argument if not 32 bytes decoded
|
|
20
|
+
|
|
21
|
+
// Lowercase hex encode/decode - the key encoding the cross-platform userspace
|
|
22
|
+
// UAPI protocol uses (https://www.wireguard.com/xplatform/), distinct from the
|
|
23
|
+
// base64 form used by the kernel netlink path / `wg` CLI.
|
|
24
|
+
std::string KeyToHex(const wg::Key &key);
|
|
25
|
+
wg::Key KeyFromHex(const std::string &hex); // throws std::invalid_argument if not 32 bytes decoded
|
|
26
|
+
|
|
27
|
+
// True if every byte is zero - the UAPI/netlink convention for "field unset".
|
|
28
|
+
bool IsZeroKey(const wg::Key &key);
|
|
29
|
+
|
|
30
|
+
} // namespace crypto
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <napi.h>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
namespace helpers {
|
|
8
|
+
|
|
9
|
+
inline Napi::Array StringVectorToJS(Napi::Env env, const std::vector<std::string> &items) {
|
|
10
|
+
Napi::Array arr = Napi::Array::New(env, items.size());
|
|
11
|
+
for (size_t i = 0; i < items.size(); i++) {
|
|
12
|
+
arr.Set(i, Napi::String::New(env, items[i]));
|
|
13
|
+
}
|
|
14
|
+
return arr;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
inline std::vector<std::string> JSArrayToStringVector(const Napi::Array &arr) {
|
|
18
|
+
std::vector<std::string> out;
|
|
19
|
+
out.reserve(arr.Length());
|
|
20
|
+
for (uint32_t i = 0; i < arr.Length(); i++) {
|
|
21
|
+
out.push_back(arr.Get(i).As<Napi::String>().Utf8Value());
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace helpers
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <napi.h>
|
|
4
|
+
#include <cerrno>
|
|
5
|
+
#include <functional>
|
|
6
|
+
#include <stdexcept>
|
|
7
|
+
#include <string>
|
|
8
|
+
|
|
9
|
+
namespace helpers {
|
|
10
|
+
|
|
11
|
+
// Thrown from inside a PromiseWorker's execute function when a syscall fails;
|
|
12
|
+
// carries the errno so the JS side gets a matching err.code (e.g. 'ENODEV', 'EEXIST').
|
|
13
|
+
class SystemError : public std::runtime_error {
|
|
14
|
+
public:
|
|
15
|
+
SystemError(int errnoCode, const std::string &message)
|
|
16
|
+
: std::runtime_error(message), errnoCode_(errnoCode) {}
|
|
17
|
+
|
|
18
|
+
int Code() const { return errnoCode_; }
|
|
19
|
+
|
|
20
|
+
private:
|
|
21
|
+
int errnoCode_;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Generic AsyncWorker -> Promise bridge. `execute` runs off the JS thread and
|
|
25
|
+
// either returns normally or throws (std::exception / SystemError); `resolve`
|
|
26
|
+
// runs back on the JS thread to build the resolved value from any captured state.
|
|
27
|
+
class PromiseWorker : public Napi::AsyncWorker {
|
|
28
|
+
public:
|
|
29
|
+
using ExecuteFn = std::function<void()>;
|
|
30
|
+
using ResolveFn = std::function<Napi::Value(Napi::Env)>;
|
|
31
|
+
|
|
32
|
+
PromiseWorker(Napi::Env env, ExecuteFn execute, ResolveFn resolve = nullptr)
|
|
33
|
+
: Napi::AsyncWorker(env),
|
|
34
|
+
deferred_(Napi::Promise::Deferred::New(env)),
|
|
35
|
+
execute_(std::move(execute)),
|
|
36
|
+
resolve_(std::move(resolve)) {}
|
|
37
|
+
|
|
38
|
+
Napi::Promise Promise() { return deferred_.Promise(); }
|
|
39
|
+
|
|
40
|
+
protected:
|
|
41
|
+
void Execute() override {
|
|
42
|
+
try {
|
|
43
|
+
execute_();
|
|
44
|
+
} catch (const SystemError &e) {
|
|
45
|
+
errnoCode_ = e.Code();
|
|
46
|
+
SetError(e.what());
|
|
47
|
+
} catch (const std::exception &e) {
|
|
48
|
+
SetError(e.what());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void OnOK() override {
|
|
53
|
+
Napi::Env env = Env();
|
|
54
|
+
Napi::HandleScope scope(env);
|
|
55
|
+
deferred_.Resolve(resolve_ ? resolve_(env) : env.Undefined());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
void OnError(const Napi::Error &e) override {
|
|
59
|
+
Napi::Env env = Env();
|
|
60
|
+
Napi::HandleScope scope(env);
|
|
61
|
+
Napi::Object errorObj = e.Value();
|
|
62
|
+
if (errnoCode_ != 0) {
|
|
63
|
+
errorObj.Set("code", Napi::String::New(env, ErrnoName(errnoCode_)));
|
|
64
|
+
errorObj.Set("errno", Napi::Number::New(env, errnoCode_));
|
|
65
|
+
}
|
|
66
|
+
deferred_.Reject(errorObj);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private:
|
|
70
|
+
// Maps the small set of errno values this addon actually raises (see
|
|
71
|
+
// WireGuardClient.cpp) to their symbolic names; falls back to "EUNKNOWN".
|
|
72
|
+
static const char *ErrnoName(int code) {
|
|
73
|
+
switch (code) {
|
|
74
|
+
case ENODEV: return "ENODEV";
|
|
75
|
+
case EEXIST: return "EEXIST";
|
|
76
|
+
case ENOENT: return "ENOENT";
|
|
77
|
+
case EPERM: return "EPERM";
|
|
78
|
+
case EACCES: return "EACCES";
|
|
79
|
+
case EINVAL: return "EINVAL";
|
|
80
|
+
case EBUSY: return "EBUSY";
|
|
81
|
+
default: return "EUNKNOWN";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Napi::Promise::Deferred deferred_;
|
|
86
|
+
ExecuteFn execute_;
|
|
87
|
+
ResolveFn resolve_;
|
|
88
|
+
int errnoCode_ = 0;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
} // namespace helpers
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#include "IfName.h"
|
|
2
|
+
|
|
3
|
+
#include <net/if.h>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
|
|
6
|
+
namespace helpers {
|
|
7
|
+
|
|
8
|
+
void ValidateIfName(const std::string &name) {
|
|
9
|
+
if (name.empty()) {
|
|
10
|
+
throw std::invalid_argument("interface name must not be empty");
|
|
11
|
+
}
|
|
12
|
+
if (name == "." || name == "..") {
|
|
13
|
+
throw std::invalid_argument("invalid interface name: " + name);
|
|
14
|
+
}
|
|
15
|
+
// IFNAMSIZ includes the terminating NUL the kernel appends.
|
|
16
|
+
if (name.size() > IFNAMSIZ - 1) {
|
|
17
|
+
throw std::invalid_argument("interface name too long (max " + std::to_string(IFNAMSIZ - 1) +
|
|
18
|
+
" bytes): " + name);
|
|
19
|
+
}
|
|
20
|
+
for (char c : name) {
|
|
21
|
+
if (c == '\0' || c == '/' || static_cast<unsigned char>(c) <= ' ') {
|
|
22
|
+
throw std::invalid_argument("invalid character in interface name: " + name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} // namespace helpers
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
|
|
5
|
+
namespace helpers {
|
|
6
|
+
|
|
7
|
+
// Validates `name` as a Linux network interface name: non-empty, no NUL or
|
|
8
|
+
// '/' characters, not "." or "..", and short enough to fit IFNAMSIZ
|
|
9
|
+
// (including the kernel's terminating NUL). Throws std::invalid_argument
|
|
10
|
+
// otherwise. Every native entry point that turns a JS-supplied string into
|
|
11
|
+
// an ifname (netlink attribute, rtnetlink lookup, or UAPI socket path) must
|
|
12
|
+
// call this first - mnl_attr_put_strz does not bounds-check, and an
|
|
13
|
+
// unvalidated name can also be used for path traversal against the UAPI
|
|
14
|
+
// socket directory.
|
|
15
|
+
void ValidateIfName(const std::string &name);
|
|
16
|
+
|
|
17
|
+
} // namespace helpers
|