@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,217 @@
|
|
|
1
|
+
#include "RtLink.h"
|
|
2
|
+
#include "../helpers/AsyncPromise.h"
|
|
3
|
+
#include "../helpers/IfName.h"
|
|
4
|
+
#include "NlAttr.h"
|
|
5
|
+
|
|
6
|
+
#include <arpa/inet.h>
|
|
7
|
+
#include <cerrno>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <dirent.h>
|
|
10
|
+
#include <net/if.h>
|
|
11
|
+
#include <netinet/in.h>
|
|
12
|
+
#include <sys/stat.h>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
extern "C" {
|
|
16
|
+
#include <libmnl/libmnl.h>
|
|
17
|
+
#include <linux/if_addr.h>
|
|
18
|
+
#include <linux/if_link.h>
|
|
19
|
+
#include <linux/rtnetlink.h>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
namespace netlink {
|
|
23
|
+
|
|
24
|
+
namespace {
|
|
25
|
+
|
|
26
|
+
const size_t kBufSize = MNL_SOCKET_BUFFER_SIZE;
|
|
27
|
+
|
|
28
|
+
// Opens a short-lived NETLINK_ROUTE socket, sends `nlh`, and waits for the ack.
|
|
29
|
+
// Throws helpers::SystemError on any netlink-reported or syscall errno.
|
|
30
|
+
void SendRtRequestAndWaitAck(struct nlmsghdr *nlh) {
|
|
31
|
+
struct mnl_socket *sock = mnl_socket_open(NETLINK_ROUTE);
|
|
32
|
+
if (!sock) {
|
|
33
|
+
throw helpers::SystemError(errno, std::string("mnl_socket_open: ") + std::strerror(errno));
|
|
34
|
+
}
|
|
35
|
+
if (mnl_socket_bind(sock, 0, MNL_SOCKET_AUTOPID) < 0) {
|
|
36
|
+
int err = errno;
|
|
37
|
+
mnl_socket_close(sock);
|
|
38
|
+
throw helpers::SystemError(err, std::string("mnl_socket_bind: ") + std::strerror(err));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
uint32_t portid = mnl_socket_get_portid(sock);
|
|
42
|
+
|
|
43
|
+
if (mnl_socket_sendto(sock, nlh, nlh->nlmsg_len) < 0) {
|
|
44
|
+
int err = errno;
|
|
45
|
+
mnl_socket_close(sock);
|
|
46
|
+
throw helpers::SystemError(err, std::string("mnl_socket_sendto: ") + std::strerror(err));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
std::vector<char> buf(kBufSize);
|
|
50
|
+
ssize_t ret = mnl_socket_recvfrom(sock, buf.data(), buf.size());
|
|
51
|
+
mnl_socket_close(sock);
|
|
52
|
+
|
|
53
|
+
if (ret < 0) {
|
|
54
|
+
throw helpers::SystemError(errno, std::string("mnl_socket_recvfrom: ") + std::strerror(errno));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
auto *replyHdr = reinterpret_cast<struct nlmsghdr *>(buf.data());
|
|
58
|
+
if (replyHdr->nlmsg_seq != nlh->nlmsg_seq || replyHdr->nlmsg_pid != portid) {
|
|
59
|
+
throw helpers::SystemError(EIO, "unexpected netlink reply (seq/pid mismatch)");
|
|
60
|
+
}
|
|
61
|
+
if (replyHdr->nlmsg_type == NLMSG_ERROR) {
|
|
62
|
+
auto *err = static_cast<struct nlmsgerr *>(mnl_nlmsg_get_payload(replyHdr));
|
|
63
|
+
if (err->error != 0) {
|
|
64
|
+
int code = -err->error;
|
|
65
|
+
throw helpers::SystemError(code, std::string("rtnetlink error: ") + std::strerror(code));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} // namespace
|
|
71
|
+
|
|
72
|
+
void CreateWireGuardLink(const std::string &name) {
|
|
73
|
+
helpers::ValidateIfName(name);
|
|
74
|
+
if (if_nametoindex(name.c_str()) != 0) {
|
|
75
|
+
throw helpers::SystemError(EEXIST, "interface already exists: " + name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
std::vector<char> buf(kBufSize);
|
|
79
|
+
auto *nlh = mnl_nlmsg_put_header(buf.data());
|
|
80
|
+
nlh->nlmsg_type = RTM_NEWLINK;
|
|
81
|
+
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
|
|
82
|
+
nlh->nlmsg_seq = static_cast<unsigned int>(time(nullptr));
|
|
83
|
+
|
|
84
|
+
auto *ifi = static_cast<struct ifinfomsg *>(mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifinfomsg)));
|
|
85
|
+
ifi->ifi_family = AF_UNSPEC;
|
|
86
|
+
ifi->ifi_change = 0xFFFFFFFF;
|
|
87
|
+
|
|
88
|
+
mnl_attr_put_strz(nlh, IFLA_IFNAME, name.c_str());
|
|
89
|
+
|
|
90
|
+
struct nlattr *linkinfo = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
|
|
91
|
+
mnl_attr_put_strz(nlh, IFLA_INFO_KIND, "wireguard");
|
|
92
|
+
mnl_attr_nest_end(nlh, linkinfo);
|
|
93
|
+
|
|
94
|
+
SendRtRequestAndWaitAck(nlh);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
void DeleteLink(const std::string &name) {
|
|
98
|
+
helpers::ValidateIfName(name);
|
|
99
|
+
unsigned int ifindex = if_nametoindex(name.c_str());
|
|
100
|
+
if (ifindex == 0) {
|
|
101
|
+
throw helpers::SystemError(ENODEV, "no such interface: " + name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
std::vector<char> buf(kBufSize);
|
|
105
|
+
auto *nlh = mnl_nlmsg_put_header(buf.data());
|
|
106
|
+
nlh->nlmsg_type = RTM_DELLINK;
|
|
107
|
+
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
108
|
+
nlh->nlmsg_seq = static_cast<unsigned int>(time(nullptr));
|
|
109
|
+
|
|
110
|
+
auto *ifi = static_cast<struct ifinfomsg *>(mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifinfomsg)));
|
|
111
|
+
ifi->ifi_family = AF_UNSPEC;
|
|
112
|
+
ifi->ifi_index = static_cast<int>(ifindex);
|
|
113
|
+
|
|
114
|
+
SendRtRequestAndWaitAck(nlh);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
void SetLinkUp(const std::string &name, bool up) {
|
|
118
|
+
helpers::ValidateIfName(name);
|
|
119
|
+
unsigned int ifindex = if_nametoindex(name.c_str());
|
|
120
|
+
if (ifindex == 0) {
|
|
121
|
+
throw helpers::SystemError(ENODEV, "no such interface: " + name);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
std::vector<char> buf(kBufSize);
|
|
125
|
+
auto *nlh = mnl_nlmsg_put_header(buf.data());
|
|
126
|
+
nlh->nlmsg_type = RTM_NEWLINK;
|
|
127
|
+
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
128
|
+
nlh->nlmsg_seq = static_cast<unsigned int>(time(nullptr));
|
|
129
|
+
|
|
130
|
+
auto *ifi = static_cast<struct ifinfomsg *>(mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifinfomsg)));
|
|
131
|
+
ifi->ifi_family = AF_UNSPEC;
|
|
132
|
+
ifi->ifi_index = static_cast<int>(ifindex);
|
|
133
|
+
ifi->ifi_change = IFF_UP;
|
|
134
|
+
ifi->ifi_flags = up ? IFF_UP : 0;
|
|
135
|
+
|
|
136
|
+
SendRtRequestAndWaitAck(nlh);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
namespace {
|
|
140
|
+
void BuildAddrRequest(std::vector<char> &buf, uint16_t msgType, uint16_t flags, unsigned int ifindex,
|
|
141
|
+
const wg::AllowedIP &addr) {
|
|
142
|
+
auto *nlh = mnl_nlmsg_put_header(buf.data());
|
|
143
|
+
nlh->nlmsg_type = msgType;
|
|
144
|
+
nlh->nlmsg_flags = flags;
|
|
145
|
+
nlh->nlmsg_seq = static_cast<unsigned int>(time(nullptr));
|
|
146
|
+
|
|
147
|
+
auto *ifa = static_cast<struct ifaddrmsg *>(mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg)));
|
|
148
|
+
ifa->ifa_family = addr.family;
|
|
149
|
+
ifa->ifa_prefixlen = addr.cidr;
|
|
150
|
+
ifa->ifa_flags = 0;
|
|
151
|
+
ifa->ifa_scope = 0;
|
|
152
|
+
ifa->ifa_index = ifindex;
|
|
153
|
+
|
|
154
|
+
if (addr.family == AF_INET6) {
|
|
155
|
+
in6_addr raw{};
|
|
156
|
+
inet_pton(AF_INET6, addr.ip.c_str(), &raw);
|
|
157
|
+
mnl_attr_put(nlh, IFA_LOCAL, sizeof(raw), &raw);
|
|
158
|
+
mnl_attr_put(nlh, IFA_ADDRESS, sizeof(raw), &raw);
|
|
159
|
+
} else {
|
|
160
|
+
in_addr raw{};
|
|
161
|
+
inet_pton(AF_INET, addr.ip.c_str(), &raw);
|
|
162
|
+
mnl_attr_put(nlh, IFA_LOCAL, sizeof(raw), &raw);
|
|
163
|
+
mnl_attr_put(nlh, IFA_ADDRESS, sizeof(raw), &raw);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} // namespace
|
|
167
|
+
|
|
168
|
+
void AddAddress(const std::string &name, const std::string &cidr) {
|
|
169
|
+
helpers::ValidateIfName(name);
|
|
170
|
+
unsigned int ifindex = if_nametoindex(name.c_str());
|
|
171
|
+
if (ifindex == 0) {
|
|
172
|
+
throw helpers::SystemError(ENODEV, "no such interface: " + name);
|
|
173
|
+
}
|
|
174
|
+
wg::AllowedIP addr = ParseCIDR(cidr);
|
|
175
|
+
|
|
176
|
+
std::vector<char> buf(kBufSize);
|
|
177
|
+
BuildAddrRequest(buf, RTM_NEWADDR, NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE, ifindex, addr);
|
|
178
|
+
SendRtRequestAndWaitAck(reinterpret_cast<struct nlmsghdr *>(buf.data()));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
void DeleteAddress(const std::string &name, const std::string &cidr) {
|
|
182
|
+
helpers::ValidateIfName(name);
|
|
183
|
+
unsigned int ifindex = if_nametoindex(name.c_str());
|
|
184
|
+
if (ifindex == 0) {
|
|
185
|
+
throw helpers::SystemError(ENODEV, "no such interface: " + name);
|
|
186
|
+
}
|
|
187
|
+
wg::AllowedIP addr = ParseCIDR(cidr);
|
|
188
|
+
|
|
189
|
+
std::vector<char> buf(kBufSize);
|
|
190
|
+
BuildAddrRequest(buf, RTM_DELADDR, NLM_F_REQUEST | NLM_F_ACK, ifindex, addr);
|
|
191
|
+
SendRtRequestAndWaitAck(reinterpret_cast<struct nlmsghdr *>(buf.data()));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
std::vector<std::string> ListWireGuardInterfaceNames() {
|
|
195
|
+
std::vector<std::string> names;
|
|
196
|
+
DIR *dir = opendir("/sys/class/net");
|
|
197
|
+
if (!dir) {
|
|
198
|
+
throw helpers::SystemError(errno, std::string("opendir(/sys/class/net): ") + std::strerror(errno));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
struct dirent *entry;
|
|
202
|
+
while ((entry = readdir(dir)) != nullptr) {
|
|
203
|
+
std::string name = entry->d_name;
|
|
204
|
+
if (name == "." || name == "..") {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
struct stat st{};
|
|
208
|
+
std::string wgMarkerPath = "/sys/class/net/" + name + "/wireguard";
|
|
209
|
+
if (stat(wgMarkerPath.c_str(), &st) == 0) {
|
|
210
|
+
names.push_back(name);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
closedir(dir);
|
|
214
|
+
return names;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
} // namespace netlink
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <vector>
|
|
5
|
+
|
|
6
|
+
namespace netlink {
|
|
7
|
+
|
|
8
|
+
// Lists interface names that are WireGuard devices, by checking for the
|
|
9
|
+
// presence of /sys/class/net/<name>/wireguard (exposed by the kernel module
|
|
10
|
+
// for any interface of kind "wireguard") — cheaper than an RTM_GETLINK dump
|
|
11
|
+
// plus IFLA_LINKINFO parsing for the same result.
|
|
12
|
+
std::vector<std::string> ListWireGuardInterfaceNames();
|
|
13
|
+
|
|
14
|
+
// Issues RTM_NEWLINK with IFLA_INFO_KIND="wireguard" to create a new WireGuard
|
|
15
|
+
// link. Throws helpers::SystemError(EEXIST) if `name` already exists, or other
|
|
16
|
+
// errno on failure (e.g. EPERM if not running as root / missing CAP_NET_ADMIN).
|
|
17
|
+
void CreateWireGuardLink(const std::string &name);
|
|
18
|
+
|
|
19
|
+
// Issues RTM_DELLINK for `name`. Throws helpers::SystemError(ENODEV) if it
|
|
20
|
+
// doesn't exist.
|
|
21
|
+
void DeleteLink(const std::string &name);
|
|
22
|
+
|
|
23
|
+
// Brings `name` administratively up (IFF_UP) or down via RTM_NEWLINK.
|
|
24
|
+
// Required for traffic to flow - createDevice() leaves the link down.
|
|
25
|
+
void SetLinkUp(const std::string &name, bool up);
|
|
26
|
+
|
|
27
|
+
// Assigns a local address ("10.0.0.2/24" or "fd00::2/64") to `name` via
|
|
28
|
+
// RTM_NEWADDR. Replaces any existing address with the same prefix.
|
|
29
|
+
void AddAddress(const std::string &name, const std::string &cidr);
|
|
30
|
+
|
|
31
|
+
// Removes a previously assigned address via RTM_DELADDR.
|
|
32
|
+
void DeleteAddress(const std::string &name, const std::string &cidr);
|
|
33
|
+
|
|
34
|
+
} // namespace netlink
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Mirrors the stable kernel UAPI <linux/wireguard.h> generic-netlink protocol
|
|
4
|
+
// (the same wire format wgctrl-go's wglinux backend speaks). Defined locally
|
|
5
|
+
// instead of #include <linux/wireguard.h> so the build doesn't depend on the
|
|
6
|
+
// build host having a sufficiently new kernel-headers package installed —
|
|
7
|
+
// these attribute IDs are part of WireGuard's stable ABI and do not change.
|
|
8
|
+
|
|
9
|
+
#define WG_GENL_NAME "wireguard"
|
|
10
|
+
#define WG_GENL_VERSION 1
|
|
11
|
+
#define WG_MULTICAST_GROUP_PEERS "peers"
|
|
12
|
+
#define WG_KEY_LEN 32
|
|
13
|
+
|
|
14
|
+
enum wg_cmd {
|
|
15
|
+
WG_CMD_GET_DEVICE,
|
|
16
|
+
WG_CMD_SET_DEVICE,
|
|
17
|
+
__WG_CMD_MAX
|
|
18
|
+
};
|
|
19
|
+
#define WG_CMD_MAX (__WG_CMD_MAX - 1)
|
|
20
|
+
|
|
21
|
+
enum wgdevice_flag {
|
|
22
|
+
WGDEVICE_F_REPLACE_PEERS = 1U << 0,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
enum wgdevice_attribute {
|
|
26
|
+
WGDEVICE_A_UNSPEC,
|
|
27
|
+
WGDEVICE_A_IFINDEX, // NLA_U32
|
|
28
|
+
WGDEVICE_A_IFNAME, // NLA_NUL_STRING, IFNAMSIZ - 1
|
|
29
|
+
WGDEVICE_A_PRIVATE_KEY, // NLA_EXACT_LEN, WG_KEY_LEN
|
|
30
|
+
WGDEVICE_A_PUBLIC_KEY, // NLA_EXACT_LEN, WG_KEY_LEN
|
|
31
|
+
WGDEVICE_A_FLAGS, // NLA_U32
|
|
32
|
+
WGDEVICE_A_LISTEN_PORT, // NLA_U16
|
|
33
|
+
WGDEVICE_A_FWMARK, // NLA_U32
|
|
34
|
+
WGDEVICE_A_PEERS, // NLA_NESTED
|
|
35
|
+
__WGDEVICE_A_LAST
|
|
36
|
+
};
|
|
37
|
+
#define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1)
|
|
38
|
+
|
|
39
|
+
enum wgpeer_flag {
|
|
40
|
+
WGPEER_F_REMOVE_ME = 1U << 0,
|
|
41
|
+
WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1,
|
|
42
|
+
WGPEER_F_UPDATE_ONLY = 1U << 2,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
enum wgpeer_attribute {
|
|
46
|
+
WGPEER_A_UNSPEC,
|
|
47
|
+
WGPEER_A_PUBLIC_KEY, // NLA_EXACT_LEN, WG_KEY_LEN
|
|
48
|
+
WGPEER_A_PRESHARED_KEY, // NLA_EXACT_LEN, WG_KEY_LEN
|
|
49
|
+
WGPEER_A_FLAGS, // NLA_U32
|
|
50
|
+
WGPEER_A_ENDPOINT, // NLA_MIN_LEN(sockaddr), sockaddr_in/in6
|
|
51
|
+
WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, // NLA_U16
|
|
52
|
+
WGPEER_A_LAST_HANDSHAKE_TIME, // NLA_EXACT_LEN, __kernel_timespec
|
|
53
|
+
WGPEER_A_RX_BYTES, // NLA_U64
|
|
54
|
+
WGPEER_A_TX_BYTES, // NLA_U64
|
|
55
|
+
WGPEER_A_ALLOWEDIPS, // NLA_NESTED
|
|
56
|
+
WGPEER_A_PROTOCOL_VERSION, // NLA_U32
|
|
57
|
+
__WGPEER_A_LAST
|
|
58
|
+
};
|
|
59
|
+
#define WGPEER_A_MAX (__WGPEER_A_LAST - 1)
|
|
60
|
+
|
|
61
|
+
enum wgallowedip_attribute {
|
|
62
|
+
WGALLOWEDIP_A_UNSPEC,
|
|
63
|
+
WGALLOWEDIP_A_FAMILY, // NLA_U16, AF_INET or AF_INET6
|
|
64
|
+
WGALLOWEDIP_A_IPADDR, // NLA_MIN_LEN(4), 4 or 16 bytes
|
|
65
|
+
WGALLOWEDIP_A_CIDR_MASK, // NLA_U8
|
|
66
|
+
__WGALLOWEDIP_A_LAST
|
|
67
|
+
};
|
|
68
|
+
#define WGALLOWEDIP_A_MAX (__WGALLOWEDIP_A_LAST - 1)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#include "WireGuardClient.h"
|
|
2
|
+
#include "crypto/Key.h"
|
|
3
|
+
|
|
4
|
+
#include <napi.h>
|
|
5
|
+
#include <sodium.h>
|
|
6
|
+
#include <stdexcept>
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
Napi::Value GeneratePrivateKey(const Napi::CallbackInfo &info) {
|
|
11
|
+
return Napi::String::New(info.Env(), crypto::KeyToBase64(crypto::GeneratePrivateKey()));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Napi::Value GeneratePresharedKey(const Napi::CallbackInfo &info) {
|
|
15
|
+
return Napi::String::New(info.Env(), crypto::KeyToBase64(crypto::GeneratePresharedKey()));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Napi::Value PublicKey(const Napi::CallbackInfo &info) {
|
|
19
|
+
Napi::Env env = info.Env();
|
|
20
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
21
|
+
Napi::TypeError::New(env, "publicKey(privateKey: string) expects a base64-encoded string").ThrowAsJavaScriptException();
|
|
22
|
+
return env.Undefined();
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
wg::Key priv = crypto::KeyFromBase64(info[0].As<Napi::String>().Utf8Value());
|
|
26
|
+
return Napi::String::New(env, crypto::KeyToBase64(crypto::PublicKeyFromPrivate(priv)));
|
|
27
|
+
} catch (const std::exception &e) {
|
|
28
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
29
|
+
return env.Undefined();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
} // namespace
|
|
34
|
+
|
|
35
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
36
|
+
if (sodium_init() < 0) {
|
|
37
|
+
Napi::Error::New(env, "libsodium initialization failed").ThrowAsJavaScriptException();
|
|
38
|
+
return exports;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
exports.Set("generatePrivateKey", Napi::Function::New(env, GeneratePrivateKey));
|
|
42
|
+
exports.Set("generatePresharedKey", Napi::Function::New(env, GeneratePresharedKey));
|
|
43
|
+
exports.Set("publicKey", Napi::Function::New(env, PublicKey));
|
|
44
|
+
|
|
45
|
+
return WireGuardClient::Init(env, exports);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
NODE_API_MODULE(node_wireguard, Init)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#include "UapiCodec.h"
|
|
2
|
+
#include "../crypto/Key.h"
|
|
3
|
+
#include "../helpers/AsyncPromise.h"
|
|
4
|
+
#include "../netlink/NlAttr.h" // reuse ParseCIDR/FormatCIDR - same "ip/mask" textual form as the netlink path
|
|
5
|
+
|
|
6
|
+
#include <sstream>
|
|
7
|
+
#include <stdexcept>
|
|
8
|
+
|
|
9
|
+
namespace uapi {
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
// Strict unsigned decimal parser: rejects empty/non-numeric input, trailing
|
|
14
|
+
// garbage, and anything outside [0, max] - std::stoul alone accepts trailing
|
|
15
|
+
// garbage and returns a 64-bit value that a bare static_cast<uint16_t/32_t>
|
|
16
|
+
// would silently truncate instead of reject.
|
|
17
|
+
unsigned long ParseUnsignedInRange(const std::string &value, unsigned long max, const char *field) {
|
|
18
|
+
if (value.empty()) {
|
|
19
|
+
throw std::invalid_argument(std::string(field) + ": empty value");
|
|
20
|
+
}
|
|
21
|
+
size_t consumed = 0;
|
|
22
|
+
unsigned long ul;
|
|
23
|
+
try {
|
|
24
|
+
ul = std::stoul(value, &consumed);
|
|
25
|
+
} catch (const std::exception &) {
|
|
26
|
+
throw std::invalid_argument(std::string(field) + ": invalid value: " + value);
|
|
27
|
+
}
|
|
28
|
+
if (consumed != value.size() || ul > max) {
|
|
29
|
+
throw std::invalid_argument(std::string(field) + ": out of range: " + value);
|
|
30
|
+
}
|
|
31
|
+
return ul;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
uint16_t ParseUint16(const std::string &value, const char *field) {
|
|
35
|
+
return static_cast<uint16_t>(ParseUnsignedInRange(value, 65535, field));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
uint32_t ParseUint32(const std::string &value, const char *field) {
|
|
39
|
+
return static_cast<uint32_t>(ParseUnsignedInRange(value, 4294967295UL, field));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
} // namespace
|
|
43
|
+
|
|
44
|
+
std::string BuildGetRequest() {
|
|
45
|
+
return "get=1\n\n";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
wg::Device ParseGetResponse(const std::string &name, const std::string &response) {
|
|
49
|
+
wg::Device device;
|
|
50
|
+
device.name = name;
|
|
51
|
+
device.userspace = true;
|
|
52
|
+
|
|
53
|
+
wg::Peer *currentPeer = nullptr;
|
|
54
|
+
std::istringstream iss(response);
|
|
55
|
+
std::string line;
|
|
56
|
+
while (std::getline(iss, line)) {
|
|
57
|
+
if (line.empty()) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
size_t eq = line.find('=');
|
|
61
|
+
if (eq == std::string::npos) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
std::string key = line.substr(0, eq);
|
|
65
|
+
std::string value = line.substr(eq + 1);
|
|
66
|
+
|
|
67
|
+
if (key == "errno") {
|
|
68
|
+
int code = std::stoi(value);
|
|
69
|
+
if (code != 0) {
|
|
70
|
+
throw helpers::SystemError(code, "uapi get error, errno=" + value);
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (key == "public_key") {
|
|
76
|
+
// Starts a new peer section - everything until the next public_key=
|
|
77
|
+
// (or end of message) belongs to it.
|
|
78
|
+
device.peers.emplace_back();
|
|
79
|
+
currentPeer = &device.peers.back();
|
|
80
|
+
currentPeer->publicKey = crypto::KeyFromHex(value);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (currentPeer != nullptr) {
|
|
85
|
+
if (key == "preshared_key") {
|
|
86
|
+
currentPeer->presharedKey = crypto::KeyFromHex(value);
|
|
87
|
+
} else if (key == "endpoint") {
|
|
88
|
+
currentPeer->endpoint = value;
|
|
89
|
+
} else if (key == "last_handshake_time_sec") {
|
|
90
|
+
currentPeer->lastHandshakeTimeSec = std::stoll(value);
|
|
91
|
+
} else if (key == "rx_bytes") {
|
|
92
|
+
currentPeer->receiveBytes = std::stoull(value);
|
|
93
|
+
} else if (key == "tx_bytes") {
|
|
94
|
+
currentPeer->transmitBytes = std::stoull(value);
|
|
95
|
+
} else if (key == "persistent_keepalive_interval") {
|
|
96
|
+
currentPeer->persistentKeepaliveInterval = ParseUint16(value, "persistent_keepalive_interval");
|
|
97
|
+
} else if (key == "allowed_ip") {
|
|
98
|
+
currentPeer->allowedIPs.push_back(netlink::ParseCIDR(value));
|
|
99
|
+
} else if (key == "protocol_version") {
|
|
100
|
+
currentPeer->protocolVersion = ParseUint32(value, "protocol_version");
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Device-level fields (before the first peer section).
|
|
106
|
+
if (key == "private_key") {
|
|
107
|
+
device.privateKey = crypto::KeyFromHex(value);
|
|
108
|
+
} else if (key == "listen_port") {
|
|
109
|
+
device.listenPort = ParseUint16(value, "listen_port");
|
|
110
|
+
} else if (key == "fwmark") {
|
|
111
|
+
device.firewallMark = ParseUint32(value, "fwmark");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (device.privateKey && !crypto::IsZeroKey(*device.privateKey)) {
|
|
116
|
+
device.publicKey = crypto::PublicKeyFromPrivate(*device.privateKey);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return device;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
std::string BuildSetRequest(const wg::Config &cfg) {
|
|
123
|
+
std::ostringstream oss;
|
|
124
|
+
oss << "set=1\n";
|
|
125
|
+
|
|
126
|
+
if (cfg.privateKey) {
|
|
127
|
+
oss << "private_key=" << crypto::KeyToHex(*cfg.privateKey) << "\n";
|
|
128
|
+
}
|
|
129
|
+
if (cfg.listenPort) {
|
|
130
|
+
oss << "listen_port=" << *cfg.listenPort << "\n";
|
|
131
|
+
}
|
|
132
|
+
if (cfg.firewallMark) {
|
|
133
|
+
oss << "fwmark=" << *cfg.firewallMark << "\n";
|
|
134
|
+
}
|
|
135
|
+
if (cfg.replacePeers) {
|
|
136
|
+
oss << "replace_peers=true\n";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const auto &peer : cfg.peers) {
|
|
140
|
+
oss << "public_key=" << crypto::KeyToHex(peer.publicKey) << "\n";
|
|
141
|
+
if (peer.remove) {
|
|
142
|
+
oss << "remove=true\n";
|
|
143
|
+
}
|
|
144
|
+
if (peer.updateOnly) {
|
|
145
|
+
oss << "update_only=true\n";
|
|
146
|
+
}
|
|
147
|
+
if (peer.presharedKey) {
|
|
148
|
+
oss << "preshared_key=" << crypto::KeyToHex(*peer.presharedKey) << "\n";
|
|
149
|
+
}
|
|
150
|
+
if (peer.endpoint) {
|
|
151
|
+
oss << "endpoint=" << *peer.endpoint << "\n";
|
|
152
|
+
}
|
|
153
|
+
if (peer.persistentKeepaliveInterval) {
|
|
154
|
+
oss << "persistent_keepalive_interval=" << *peer.persistentKeepaliveInterval << "\n";
|
|
155
|
+
}
|
|
156
|
+
if (peer.replaceAllowedIPs) {
|
|
157
|
+
oss << "replace_allowed_ips=true\n";
|
|
158
|
+
}
|
|
159
|
+
for (const auto &aip : peer.allowedIPs) {
|
|
160
|
+
oss << "allowed_ip=" << netlink::FormatCIDR(aip) << "\n";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
oss << "\n";
|
|
165
|
+
return oss.str();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void ParseSetResponse(const std::string &response) {
|
|
169
|
+
std::istringstream iss(response);
|
|
170
|
+
std::string line;
|
|
171
|
+
while (std::getline(iss, line)) {
|
|
172
|
+
size_t eq = line.find('=');
|
|
173
|
+
if (eq == std::string::npos) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (line.substr(0, eq) == "errno") {
|
|
177
|
+
int code = std::stoi(line.substr(eq + 1));
|
|
178
|
+
if (code != 0) {
|
|
179
|
+
throw helpers::SystemError(code, "uapi set error, errno=" + line.substr(eq + 1));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
} // namespace uapi
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../WireGuardTypes.h"
|
|
4
|
+
|
|
5
|
+
namespace uapi {
|
|
6
|
+
|
|
7
|
+
// Builds a `get=1\n...\n\n` request body (just the literal request - no I/O).
|
|
8
|
+
std::string BuildGetRequest();
|
|
9
|
+
|
|
10
|
+
// Parses the response to a `get=1` request into a Device. Throws
|
|
11
|
+
// helpers::SystemError if the response's errno field is non-zero.
|
|
12
|
+
wg::Device ParseGetResponse(const std::string &name, const std::string &response);
|
|
13
|
+
|
|
14
|
+
// Builds a `set=1\n...\n\n` request applying `cfg`, per
|
|
15
|
+
// https://www.wireguard.com/xplatform/'s wire format.
|
|
16
|
+
std::string BuildSetRequest(const wg::Config &cfg);
|
|
17
|
+
|
|
18
|
+
// Parses the response to a `set=1` request. Throws helpers::SystemError if
|
|
19
|
+
// the response's errno field is non-zero.
|
|
20
|
+
void ParseSetResponse(const std::string &response);
|
|
21
|
+
|
|
22
|
+
} // namespace uapi
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#include "UapiSocket.h"
|
|
2
|
+
#include "../helpers/AsyncPromise.h"
|
|
3
|
+
#include "../helpers/IfName.h"
|
|
4
|
+
|
|
5
|
+
#include <cerrno>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include <dirent.h>
|
|
8
|
+
#include <sys/socket.h>
|
|
9
|
+
#include <sys/un.h>
|
|
10
|
+
#include <unistd.h>
|
|
11
|
+
|
|
12
|
+
namespace uapi {
|
|
13
|
+
|
|
14
|
+
const char *const kSocketDir = "/var/run/wireguard";
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
// `name` must be validated before being interpolated into a filesystem path -
|
|
19
|
+
// otherwise a name containing '/' or ".." lets a caller traverse outside
|
|
20
|
+
// kSocketDir (see HasSocket/Transact below, the only callers).
|
|
21
|
+
std::string SocketPath(const std::string &name) {
|
|
22
|
+
helpers::ValidateIfName(name);
|
|
23
|
+
return std::string(kSocketDir) + "/" + name + ".sock";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace
|
|
27
|
+
|
|
28
|
+
bool HasSocket(const std::string &name) {
|
|
29
|
+
std::string path = SocketPath(name);
|
|
30
|
+
return access(path.c_str(), F_OK) == 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
std::vector<std::string> ListInterfaceNames() {
|
|
34
|
+
std::vector<std::string> names;
|
|
35
|
+
DIR *dir = opendir(kSocketDir);
|
|
36
|
+
if (!dir) {
|
|
37
|
+
return names; // directory not present - no userspace interfaces, not an error
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
struct dirent *entry;
|
|
41
|
+
constexpr const char *suffix = ".sock";
|
|
42
|
+
const size_t suffixLen = 5;
|
|
43
|
+
while ((entry = readdir(dir)) != nullptr) {
|
|
44
|
+
std::string fname = entry->d_name;
|
|
45
|
+
if (fname.size() > suffixLen && fname.compare(fname.size() - suffixLen, suffixLen, suffix) == 0) {
|
|
46
|
+
names.push_back(fname.substr(0, fname.size() - suffixLen));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
closedir(dir);
|
|
50
|
+
return names;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
std::string Transact(const std::string &name, const std::string &request) {
|
|
54
|
+
std::string path = SocketPath(name);
|
|
55
|
+
|
|
56
|
+
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
57
|
+
if (fd < 0) {
|
|
58
|
+
throw helpers::SystemError(errno, std::string("socket: ") + std::strerror(errno));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
sockaddr_un addr{};
|
|
62
|
+
addr.sun_family = AF_UNIX;
|
|
63
|
+
if (path.size() >= sizeof(addr.sun_path)) {
|
|
64
|
+
close(fd);
|
|
65
|
+
throw std::invalid_argument("uapi socket path too long: " + path);
|
|
66
|
+
}
|
|
67
|
+
std::strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1);
|
|
68
|
+
|
|
69
|
+
if (connect(fd, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0) {
|
|
70
|
+
int err = errno;
|
|
71
|
+
close(fd);
|
|
72
|
+
throw helpers::SystemError(err, std::string("connect(") + path + "): " + std::strerror(err));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
size_t written = 0;
|
|
76
|
+
while (written < request.size()) {
|
|
77
|
+
ssize_t n = write(fd, request.data() + written, request.size() - written);
|
|
78
|
+
if (n < 0) {
|
|
79
|
+
int err = errno;
|
|
80
|
+
close(fd);
|
|
81
|
+
throw helpers::SystemError(err, std::string("write: ") + std::strerror(err));
|
|
82
|
+
}
|
|
83
|
+
written += static_cast<size_t>(n);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Some UAPI servers (e.g. wireguard-go) keep the connection open across
|
|
87
|
+
// multiple operations, only closing once they see EOF on their next read.
|
|
88
|
+
// Half-close our write side so the server's next read gets that EOF and it
|
|
89
|
+
// closes the connection (after flushing the response already written) -
|
|
90
|
+
// otherwise both sides block waiting for the other to close first.
|
|
91
|
+
if (shutdown(fd, SHUT_WR) < 0) {
|
|
92
|
+
int err = errno;
|
|
93
|
+
close(fd);
|
|
94
|
+
throw helpers::SystemError(err, std::string("shutdown: ") + std::strerror(err));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
std::string response;
|
|
98
|
+
char buf[4096];
|
|
99
|
+
for (;;) {
|
|
100
|
+
ssize_t n = read(fd, buf, sizeof(buf));
|
|
101
|
+
if (n < 0) {
|
|
102
|
+
int err = errno;
|
|
103
|
+
close(fd);
|
|
104
|
+
throw helpers::SystemError(err, std::string("read: ") + std::strerror(err));
|
|
105
|
+
}
|
|
106
|
+
if (n == 0) {
|
|
107
|
+
break; // EOF - server closes after responding
|
|
108
|
+
}
|
|
109
|
+
response.append(buf, static_cast<size_t>(n));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
close(fd);
|
|
113
|
+
return response;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
} // namespace uapi
|