@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,398 @@
|
|
|
1
|
+
#include "NlAttr.h"
|
|
2
|
+
#include "../helpers/AsyncPromise.h"
|
|
3
|
+
#include "../helpers/IfName.h"
|
|
4
|
+
#include "wireguard_uapi.h"
|
|
5
|
+
|
|
6
|
+
#include <arpa/inet.h>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <netinet/in.h>
|
|
9
|
+
#include <stdexcept>
|
|
10
|
+
#include <sys/socket.h>
|
|
11
|
+
|
|
12
|
+
extern "C" {
|
|
13
|
+
#include <linux/genetlink.h>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
namespace netlink {
|
|
17
|
+
|
|
18
|
+
namespace {
|
|
19
|
+
|
|
20
|
+
// Strict decimal port parser: rejects empty input, leading/trailing garbage
|
|
21
|
+
// (e.g. "51820abc" or " 51820"), and anything outside 0..65535 - std::stoi
|
|
22
|
+
// alone accepts all of those by ignoring unparsed trailing characters.
|
|
23
|
+
uint16_t ParsePort(const std::string &s) {
|
|
24
|
+
if (s.empty()) {
|
|
25
|
+
throw std::invalid_argument("invalid port: " + s);
|
|
26
|
+
}
|
|
27
|
+
size_t consumed = 0;
|
|
28
|
+
int value;
|
|
29
|
+
try {
|
|
30
|
+
value = std::stoi(s, &consumed);
|
|
31
|
+
} catch (const std::exception &) {
|
|
32
|
+
throw std::invalid_argument("invalid port: " + s);
|
|
33
|
+
}
|
|
34
|
+
if (consumed != s.size() || value < 0 || value > 65535) {
|
|
35
|
+
throw std::invalid_argument("invalid port: " + s);
|
|
36
|
+
}
|
|
37
|
+
return static_cast<uint16_t>(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// "1.2.3.4:51820" or "[2001:db8::1]:51820" -> sockaddr_storage
|
|
41
|
+
sockaddr_storage ParseEndpoint(const std::string &endpoint) {
|
|
42
|
+
sockaddr_storage ss{};
|
|
43
|
+
std::memset(&ss, 0, sizeof(ss));
|
|
44
|
+
|
|
45
|
+
size_t portSep;
|
|
46
|
+
std::string host;
|
|
47
|
+
uint16_t port;
|
|
48
|
+
|
|
49
|
+
if (!endpoint.empty() && endpoint.front() == '[') {
|
|
50
|
+
size_t close = endpoint.find(']');
|
|
51
|
+
if (close == std::string::npos || close + 1 >= endpoint.size() || endpoint[close + 1] != ':') {
|
|
52
|
+
throw std::invalid_argument("invalid IPv6 endpoint: " + endpoint);
|
|
53
|
+
}
|
|
54
|
+
host = endpoint.substr(1, close - 1);
|
|
55
|
+
port = ParsePort(endpoint.substr(close + 2));
|
|
56
|
+
|
|
57
|
+
auto *sin6 = reinterpret_cast<sockaddr_in6 *>(&ss);
|
|
58
|
+
sin6->sin6_family = AF_INET6;
|
|
59
|
+
sin6->sin6_port = htons(port);
|
|
60
|
+
if (inet_pton(AF_INET6, host.c_str(), &sin6->sin6_addr) != 1) {
|
|
61
|
+
throw std::invalid_argument("invalid IPv6 address: " + host);
|
|
62
|
+
}
|
|
63
|
+
return ss;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
portSep = endpoint.rfind(':');
|
|
67
|
+
if (portSep == std::string::npos) {
|
|
68
|
+
throw std::invalid_argument("invalid endpoint (expected host:port): " + endpoint);
|
|
69
|
+
}
|
|
70
|
+
host = endpoint.substr(0, portSep);
|
|
71
|
+
port = ParsePort(endpoint.substr(portSep + 1));
|
|
72
|
+
|
|
73
|
+
auto *sin = reinterpret_cast<sockaddr_in *>(&ss);
|
|
74
|
+
sin->sin_family = AF_INET;
|
|
75
|
+
sin->sin_port = htons(port);
|
|
76
|
+
if (inet_pton(AF_INET, host.c_str(), &sin->sin_addr) != 1) {
|
|
77
|
+
throw std::invalid_argument("invalid IPv4 address: " + host);
|
|
78
|
+
}
|
|
79
|
+
return ss;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
std::string FormatEndpoint(const sockaddr *sa) {
|
|
83
|
+
char ipBuf[INET6_ADDRSTRLEN] = {0};
|
|
84
|
+
if (sa->sa_family == AF_INET) {
|
|
85
|
+
const auto *sin = reinterpret_cast<const sockaddr_in *>(sa);
|
|
86
|
+
inet_ntop(AF_INET, &sin->sin_addr, ipBuf, sizeof(ipBuf));
|
|
87
|
+
return std::string(ipBuf) + ":" + std::to_string(ntohs(sin->sin_port));
|
|
88
|
+
}
|
|
89
|
+
const auto *sin6 = reinterpret_cast<const sockaddr_in6 *>(sa);
|
|
90
|
+
inet_ntop(AF_INET6, &sin6->sin6_addr, ipBuf, sizeof(ipBuf));
|
|
91
|
+
return std::string("[") + ipBuf + "]:" + std::to_string(ntohs(sin6->sin6_port));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
void PutAllowedIPs(struct nlmsghdr *nlh, int parentAttr, const std::vector<wg::AllowedIP> &ips) {
|
|
95
|
+
struct nlattr *ipsNest = mnl_attr_nest_start(nlh, parentAttr);
|
|
96
|
+
for (size_t i = 0; i < ips.size(); i++) {
|
|
97
|
+
const auto &aip = ips[i];
|
|
98
|
+
struct nlattr *entry = mnl_attr_nest_start(nlh, static_cast<int>(i));
|
|
99
|
+
mnl_attr_put_u16(nlh, WGALLOWEDIP_A_FAMILY, aip.family);
|
|
100
|
+
if (aip.family == AF_INET) {
|
|
101
|
+
in_addr addr{};
|
|
102
|
+
inet_pton(AF_INET, aip.ip.c_str(), &addr);
|
|
103
|
+
mnl_attr_put(nlh, WGALLOWEDIP_A_IPADDR, sizeof(addr), &addr);
|
|
104
|
+
} else {
|
|
105
|
+
in6_addr addr{};
|
|
106
|
+
inet_pton(AF_INET6, aip.ip.c_str(), &addr);
|
|
107
|
+
mnl_attr_put(nlh, WGALLOWEDIP_A_IPADDR, sizeof(addr), &addr);
|
|
108
|
+
}
|
|
109
|
+
mnl_attr_put_u8(nlh, WGALLOWEDIP_A_CIDR_MASK, aip.cidr);
|
|
110
|
+
mnl_attr_nest_end(nlh, entry);
|
|
111
|
+
}
|
|
112
|
+
mnl_attr_nest_end(nlh, ipsNest);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
int ParseAllowedIPAttr(const struct nlattr *attr, void *data) {
|
|
116
|
+
auto *out = static_cast<wg::AllowedIP *>(data);
|
|
117
|
+
switch (mnl_attr_get_type(attr)) {
|
|
118
|
+
case WGALLOWEDIP_A_FAMILY:
|
|
119
|
+
out->family = static_cast<uint8_t>(mnl_attr_get_u16(attr));
|
|
120
|
+
break;
|
|
121
|
+
case WGALLOWEDIP_A_IPADDR: {
|
|
122
|
+
char buf[INET6_ADDRSTRLEN] = {0};
|
|
123
|
+
const void *raw = mnl_attr_get_payload(attr);
|
|
124
|
+
if (out->family == AF_INET6) {
|
|
125
|
+
inet_ntop(AF_INET6, raw, buf, sizeof(buf));
|
|
126
|
+
} else {
|
|
127
|
+
inet_ntop(AF_INET, raw, buf, sizeof(buf));
|
|
128
|
+
}
|
|
129
|
+
out->ip = buf;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case WGALLOWEDIP_A_CIDR_MASK:
|
|
133
|
+
out->cidr = mnl_attr_get_u8(attr);
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
return MNL_CB_OK;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
int ParseAllowedIPEntry(const struct nlattr *attr, void *data) {
|
|
142
|
+
auto *list = static_cast<std::vector<wg::AllowedIP> *>(data);
|
|
143
|
+
wg::AllowedIP aip;
|
|
144
|
+
mnl_attr_parse_nested(attr, ParseAllowedIPAttr, &aip);
|
|
145
|
+
list->push_back(aip);
|
|
146
|
+
return MNL_CB_OK;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
int ParsePeerAttr(const struct nlattr *attr, void *data) {
|
|
150
|
+
auto *peer = static_cast<wg::Peer *>(data);
|
|
151
|
+
int type = mnl_attr_get_type(attr);
|
|
152
|
+
switch (type) {
|
|
153
|
+
case WGPEER_A_PUBLIC_KEY:
|
|
154
|
+
std::memcpy(peer->publicKey.data(), mnl_attr_get_payload(attr), wg::kKeyLen);
|
|
155
|
+
break;
|
|
156
|
+
case WGPEER_A_PRESHARED_KEY: {
|
|
157
|
+
wg::Key psk{};
|
|
158
|
+
std::memcpy(psk.data(), mnl_attr_get_payload(attr), wg::kKeyLen);
|
|
159
|
+
peer->presharedKey = psk;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case WGPEER_A_ENDPOINT: {
|
|
163
|
+
const auto *sa = static_cast<const sockaddr *>(mnl_attr_get_payload(attr));
|
|
164
|
+
peer->endpoint = FormatEndpoint(sa);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
|
|
168
|
+
peer->persistentKeepaliveInterval = mnl_attr_get_u16(attr);
|
|
169
|
+
break;
|
|
170
|
+
case WGPEER_A_LAST_HANDSHAKE_TIME: {
|
|
171
|
+
// __kernel_timespec { int64 tv_sec; int64 tv_nsec; }
|
|
172
|
+
const auto *raw = static_cast<const int64_t *>(mnl_attr_get_payload(attr));
|
|
173
|
+
peer->lastHandshakeTimeSec = raw[0];
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case WGPEER_A_RX_BYTES:
|
|
177
|
+
peer->receiveBytes = mnl_attr_get_u64(attr);
|
|
178
|
+
break;
|
|
179
|
+
case WGPEER_A_TX_BYTES:
|
|
180
|
+
peer->transmitBytes = mnl_attr_get_u64(attr);
|
|
181
|
+
break;
|
|
182
|
+
case WGPEER_A_ALLOWEDIPS:
|
|
183
|
+
mnl_attr_parse_nested(attr, ParseAllowedIPEntry, &peer->allowedIPs);
|
|
184
|
+
break;
|
|
185
|
+
case WGPEER_A_PROTOCOL_VERSION:
|
|
186
|
+
peer->protocolVersion = mnl_attr_get_u32(attr);
|
|
187
|
+
break;
|
|
188
|
+
default:
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
return MNL_CB_OK;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
int ParsePeerEntry(const struct nlattr *attr, void *data) {
|
|
195
|
+
auto *peers = static_cast<std::vector<wg::Peer> *>(data);
|
|
196
|
+
wg::Peer peer;
|
|
197
|
+
mnl_attr_parse_nested(attr, ParsePeerAttr, &peer);
|
|
198
|
+
|
|
199
|
+
// When a peer's allowed-ips list is large, the kernel splits its dump across
|
|
200
|
+
// multiple WGPEER_A_PEERS entries (possibly in separate netlink messages);
|
|
201
|
+
// continuation entries repeat the same public key with only more allowed-ips.
|
|
202
|
+
// `peers` accumulates across the whole multi-part dump (see ParseDeviceMessage),
|
|
203
|
+
// so merging against the last entry catches splits across message boundaries too.
|
|
204
|
+
if (!peers->empty() && peers->back().publicKey == peer.publicKey) {
|
|
205
|
+
auto &existing = peers->back();
|
|
206
|
+
existing.allowedIPs.insert(existing.allowedIPs.end(), peer.allowedIPs.begin(), peer.allowedIPs.end());
|
|
207
|
+
return MNL_CB_OK;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
peers->push_back(peer);
|
|
211
|
+
return MNL_CB_OK;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
int ParseDeviceAttr(const struct nlattr *attr, void *data) {
|
|
215
|
+
auto *device = static_cast<wg::Device *>(data);
|
|
216
|
+
int type = mnl_attr_get_type(attr);
|
|
217
|
+
switch (type) {
|
|
218
|
+
case WGDEVICE_A_IFNAME:
|
|
219
|
+
device->name = mnl_attr_get_str(attr);
|
|
220
|
+
break;
|
|
221
|
+
case WGDEVICE_A_PRIVATE_KEY: {
|
|
222
|
+
wg::Key key{};
|
|
223
|
+
std::memcpy(key.data(), mnl_attr_get_payload(attr), wg::kKeyLen);
|
|
224
|
+
device->privateKey = key;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case WGDEVICE_A_PUBLIC_KEY: {
|
|
228
|
+
wg::Key key{};
|
|
229
|
+
std::memcpy(key.data(), mnl_attr_get_payload(attr), wg::kKeyLen);
|
|
230
|
+
device->publicKey = key;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case WGDEVICE_A_LISTEN_PORT:
|
|
234
|
+
device->listenPort = mnl_attr_get_u16(attr);
|
|
235
|
+
break;
|
|
236
|
+
case WGDEVICE_A_FWMARK:
|
|
237
|
+
device->firewallMark = mnl_attr_get_u32(attr);
|
|
238
|
+
break;
|
|
239
|
+
case WGDEVICE_A_PEERS:
|
|
240
|
+
mnl_attr_parse_nested(attr, ParsePeerEntry, &device->peers);
|
|
241
|
+
break;
|
|
242
|
+
default:
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
return MNL_CB_OK;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
} // namespace
|
|
249
|
+
|
|
250
|
+
wg::AllowedIP ParseCIDR(const std::string &cidr) {
|
|
251
|
+
size_t slash = cidr.find('/');
|
|
252
|
+
if (slash == std::string::npos) {
|
|
253
|
+
throw std::invalid_argument("invalid CIDR (missing /mask): " + cidr);
|
|
254
|
+
}
|
|
255
|
+
std::string ip = cidr.substr(0, slash);
|
|
256
|
+
std::string maskStr = cidr.substr(slash + 1);
|
|
257
|
+
|
|
258
|
+
int mask;
|
|
259
|
+
size_t consumed = 0;
|
|
260
|
+
try {
|
|
261
|
+
mask = std::stoi(maskStr, &consumed);
|
|
262
|
+
} catch (const std::exception &) {
|
|
263
|
+
throw std::invalid_argument("invalid CIDR mask: " + cidr);
|
|
264
|
+
}
|
|
265
|
+
if (consumed != maskStr.size()) {
|
|
266
|
+
throw std::invalid_argument("invalid CIDR mask: " + cidr); // trailing garbage, e.g. "24abc"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
wg::AllowedIP out;
|
|
270
|
+
out.ip = ip;
|
|
271
|
+
|
|
272
|
+
in6_addr v6{};
|
|
273
|
+
if (inet_pton(AF_INET6, ip.c_str(), &v6) == 1) {
|
|
274
|
+
out.family = AF_INET6;
|
|
275
|
+
if (mask < 0 || mask > 128) {
|
|
276
|
+
throw std::invalid_argument("invalid CIDR mask for IPv6 (must be 0-128): " + cidr);
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
in_addr v4{};
|
|
280
|
+
if (inet_pton(AF_INET, ip.c_str(), &v4) != 1) {
|
|
281
|
+
throw std::invalid_argument("invalid CIDR address: " + ip);
|
|
282
|
+
}
|
|
283
|
+
out.family = AF_INET;
|
|
284
|
+
if (mask < 0 || mask > 32) {
|
|
285
|
+
throw std::invalid_argument("invalid CIDR mask for IPv4 (must be 0-32): " + cidr);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
out.cidr = static_cast<uint8_t>(mask);
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
std::string FormatCIDR(const wg::AllowedIP &aip) {
|
|
294
|
+
return aip.ip + "/" + std::to_string(aip.cidr);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
struct nlmsghdr *BuildGetDeviceMessage(char *buf, uint16_t familyId, unsigned int seq, const std::string &ifname) {
|
|
298
|
+
helpers::ValidateIfName(ifname);
|
|
299
|
+
auto *nlh = mnl_nlmsg_put_header(buf);
|
|
300
|
+
nlh->nlmsg_type = familyId;
|
|
301
|
+
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
|
|
302
|
+
nlh->nlmsg_seq = seq;
|
|
303
|
+
|
|
304
|
+
auto *genl = static_cast<struct genlmsghdr *>(mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)));
|
|
305
|
+
genl->cmd = WG_CMD_GET_DEVICE;
|
|
306
|
+
genl->version = WG_GENL_VERSION;
|
|
307
|
+
|
|
308
|
+
mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, ifname.c_str());
|
|
309
|
+
return nlh;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
struct nlmsghdr *BuildSetDeviceMessage(char *buf, uint16_t familyId, unsigned int seq,
|
|
313
|
+
const std::string &ifname, const wg::Config &cfg) {
|
|
314
|
+
helpers::ValidateIfName(ifname);
|
|
315
|
+
auto *nlh = mnl_nlmsg_put_header(buf);
|
|
316
|
+
nlh->nlmsg_type = familyId;
|
|
317
|
+
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
318
|
+
nlh->nlmsg_seq = seq;
|
|
319
|
+
|
|
320
|
+
auto *genl = static_cast<struct genlmsghdr *>(mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)));
|
|
321
|
+
genl->cmd = WG_CMD_SET_DEVICE;
|
|
322
|
+
genl->version = WG_GENL_VERSION;
|
|
323
|
+
|
|
324
|
+
mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, ifname.c_str());
|
|
325
|
+
|
|
326
|
+
uint32_t flags = 0;
|
|
327
|
+
if (cfg.replacePeers) {
|
|
328
|
+
flags |= WGDEVICE_F_REPLACE_PEERS;
|
|
329
|
+
}
|
|
330
|
+
if (flags != 0) {
|
|
331
|
+
mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (cfg.privateKey) {
|
|
335
|
+
mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, wg::kKeyLen, cfg.privateKey->data());
|
|
336
|
+
}
|
|
337
|
+
if (cfg.listenPort) {
|
|
338
|
+
mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, *cfg.listenPort);
|
|
339
|
+
}
|
|
340
|
+
if (cfg.firewallMark) {
|
|
341
|
+
mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, *cfg.firewallMark);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!cfg.peers.empty()) {
|
|
345
|
+
struct nlattr *peersNest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);
|
|
346
|
+
for (size_t i = 0; i < cfg.peers.size(); i++) {
|
|
347
|
+
const auto &peer = cfg.peers[i];
|
|
348
|
+
struct nlattr *entry = mnl_attr_nest_start(nlh, static_cast<int>(i));
|
|
349
|
+
|
|
350
|
+
mnl_attr_put(nlh, WGPEER_A_PUBLIC_KEY, wg::kKeyLen, peer.publicKey.data());
|
|
351
|
+
|
|
352
|
+
uint32_t peerFlags = 0;
|
|
353
|
+
if (peer.remove) peerFlags |= WGPEER_F_REMOVE_ME;
|
|
354
|
+
if (peer.updateOnly) peerFlags |= WGPEER_F_UPDATE_ONLY;
|
|
355
|
+
if (peer.replaceAllowedIPs) peerFlags |= WGPEER_F_REPLACE_ALLOWEDIPS;
|
|
356
|
+
if (peerFlags != 0) {
|
|
357
|
+
mnl_attr_put_u32(nlh, WGPEER_A_FLAGS, peerFlags);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (peer.presharedKey) {
|
|
361
|
+
mnl_attr_put(nlh, WGPEER_A_PRESHARED_KEY, wg::kKeyLen, peer.presharedKey->data());
|
|
362
|
+
}
|
|
363
|
+
if (peer.endpoint) {
|
|
364
|
+
sockaddr_storage ss = ParseEndpoint(*peer.endpoint);
|
|
365
|
+
size_t len = ss.ss_family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
|
|
366
|
+
mnl_attr_put(nlh, WGPEER_A_ENDPOINT, len, &ss);
|
|
367
|
+
}
|
|
368
|
+
if (peer.persistentKeepaliveInterval) {
|
|
369
|
+
mnl_attr_put_u16(nlh, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, *peer.persistentKeepaliveInterval);
|
|
370
|
+
}
|
|
371
|
+
if (!peer.allowedIPs.empty()) {
|
|
372
|
+
PutAllowedIPs(nlh, WGPEER_A_ALLOWEDIPS, peer.allowedIPs);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
mnl_attr_nest_end(nlh, entry);
|
|
376
|
+
}
|
|
377
|
+
mnl_attr_nest_end(nlh, peersNest);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return nlh;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
void ParseDeviceMessage(const struct nlmsghdr *nlh, wg::Device &outDevice) {
|
|
384
|
+
mnl_attr_parse(nlh, sizeof(struct genlmsghdr), ParseDeviceAttr, &outDevice);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
size_t EstimateSetDeviceMessageSize(const wg::Config &cfg) {
|
|
388
|
+
// Generous per-element overestimates (nest headers + attr headers + padding)
|
|
389
|
+
// rather than exact NLA_ALIGN math - this only sizes a scratch buffer.
|
|
390
|
+
size_t total = 512; // nlmsghdr + genlmsghdr + ifname + scalar device attrs
|
|
391
|
+
for (const auto &peer : cfg.peers) {
|
|
392
|
+
total += 128; // peer nest header + public key/flags/psk/endpoint/keepalive
|
|
393
|
+
total += peer.allowedIPs.size() * 64; // each allowedip entry, generously
|
|
394
|
+
}
|
|
395
|
+
return total;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
} // namespace netlink
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../WireGuardTypes.h"
|
|
4
|
+
|
|
5
|
+
extern "C" {
|
|
6
|
+
#include <libmnl/libmnl.h>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
namespace netlink {
|
|
10
|
+
|
|
11
|
+
// Builds a WG_CMD_GET_DEVICE dump request for `ifname` into `buf`
|
|
12
|
+
// (caller-owned, must be >= MNL_SOCKET_BUFFER_SIZE). Returns the header.
|
|
13
|
+
struct nlmsghdr *BuildGetDeviceMessage(char *buf, uint16_t familyId, unsigned int seq, const std::string &ifname);
|
|
14
|
+
|
|
15
|
+
// Builds a WG_CMD_SET_DEVICE request applying `cfg` to `ifname` into `buf`.
|
|
16
|
+
// `buf` must be at least EstimateSetDeviceMessageSize(cfg) bytes - mnl_attr_put
|
|
17
|
+
// does not bounds-check, so an undersized buffer is a silent heap overflow.
|
|
18
|
+
struct nlmsghdr *BuildSetDeviceMessage(char *buf, uint16_t familyId, unsigned int seq,
|
|
19
|
+
const std::string &ifname, const wg::Config &cfg);
|
|
20
|
+
|
|
21
|
+
// Upper-bound estimate (with margin) of the encoded size of a WG_CMD_SET_DEVICE
|
|
22
|
+
// message for `cfg`. A single peer's allowed-ips list has no fixed cap, so the
|
|
23
|
+
// fixed MNL_SOCKET_BUFFER_SIZE used for most requests is not safe to assume here.
|
|
24
|
+
size_t EstimateSetDeviceMessageSize(const wg::Config &cfg);
|
|
25
|
+
|
|
26
|
+
// Parses one WG_CMD_GET_DEVICE reply message, filling device-level fields on
|
|
27
|
+
// first call and appending any peers found in this message to outDevice.peers.
|
|
28
|
+
// If a peer's allowed-ips list is large enough that the kernel splits its dump
|
|
29
|
+
// across multiple WGPEER_A_PEERS entries (possibly across multiple messages),
|
|
30
|
+
// continuation entries are merged into the prior Peer by public key rather than
|
|
31
|
+
// kept as separate entries - see ParsePeerEntry in NlAttr.cpp.
|
|
32
|
+
void ParseDeviceMessage(const struct nlmsghdr *nlh, wg::Device &outDevice);
|
|
33
|
+
|
|
34
|
+
// "192.168.1.0/24" / "fd00::/64" -> AllowedIP. Throws std::invalid_argument on bad input.
|
|
35
|
+
wg::AllowedIP ParseCIDR(const std::string &cidr);
|
|
36
|
+
|
|
37
|
+
// AllowedIP -> "192.168.1.0/24" form.
|
|
38
|
+
std::string FormatCIDR(const wg::AllowedIP &aip);
|
|
39
|
+
|
|
40
|
+
} // namespace netlink
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#include "NlSocket.h"
|
|
2
|
+
#include "../helpers/AsyncPromise.h"
|
|
3
|
+
#include "wireguard_uapi.h"
|
|
4
|
+
|
|
5
|
+
#include <cerrno>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
extern "C" {
|
|
10
|
+
#include <linux/genetlink.h>
|
|
11
|
+
#include <linux/netlink.h>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
namespace netlink {
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
const size_t kBufSize = MNL_SOCKET_BUFFER_SIZE;
|
|
18
|
+
|
|
19
|
+
int FamilyIdAttrCallback(const struct nlattr *attr, void *data) {
|
|
20
|
+
auto *out = static_cast<uint16_t *>(data);
|
|
21
|
+
if (mnl_attr_get_type(attr) == CTRL_ATTR_FAMILY_ID) {
|
|
22
|
+
*out = mnl_attr_get_u16(attr);
|
|
23
|
+
}
|
|
24
|
+
return MNL_CB_OK;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
int FamilyIdMsgCallback(const struct nlmsghdr *nlh, void *data) {
|
|
28
|
+
return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), FamilyIdAttrCallback, data);
|
|
29
|
+
}
|
|
30
|
+
} // namespace
|
|
31
|
+
|
|
32
|
+
NlSocket::NlSocket() {
|
|
33
|
+
sock_ = mnl_socket_open(NETLINK_GENERIC);
|
|
34
|
+
if (!sock_) {
|
|
35
|
+
throw helpers::SystemError(errno, std::string("mnl_socket_open: ") + std::strerror(errno));
|
|
36
|
+
}
|
|
37
|
+
if (mnl_socket_bind(sock_, 0, MNL_SOCKET_AUTOPID) < 0) {
|
|
38
|
+
int err = errno;
|
|
39
|
+
mnl_socket_close(sock_);
|
|
40
|
+
sock_ = nullptr;
|
|
41
|
+
throw helpers::SystemError(err, std::string("mnl_socket_bind: ") + std::strerror(err));
|
|
42
|
+
}
|
|
43
|
+
portid_ = mnl_socket_get_portid(sock_);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
NlSocket::~NlSocket() {
|
|
47
|
+
if (sock_) {
|
|
48
|
+
mnl_socket_close(sock_);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void NlSocket::SendAndReceive(struct nlmsghdr *nlh, const MessageHandler &handler) {
|
|
53
|
+
unsigned int seq = nlh->nlmsg_seq;
|
|
54
|
+
|
|
55
|
+
if (mnl_socket_sendto(sock_, nlh, nlh->nlmsg_len) < 0) {
|
|
56
|
+
throw helpers::SystemError(errno, std::string("mnl_socket_sendto: ") + std::strerror(errno));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
std::vector<char> buf(kBufSize);
|
|
60
|
+
for (;;) {
|
|
61
|
+
ssize_t ret = mnl_socket_recvfrom(sock_, buf.data(), buf.size());
|
|
62
|
+
if (ret < 0) {
|
|
63
|
+
throw helpers::SystemError(errno, std::string("mnl_socket_recvfrom: ") + std::strerror(errno));
|
|
64
|
+
}
|
|
65
|
+
if (ret == 0) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Walk every nlmsghdr in this datagram (a dump reply can pack several).
|
|
70
|
+
// mnl_nlmsg_ok/mnl_nlmsg_next take `int *`, not `size_t *` - aliasing a
|
|
71
|
+
// size_t through an int* is undefined behavior (and only happened to
|
|
72
|
+
// work here on little-endian, by luck).
|
|
73
|
+
auto *cur = reinterpret_cast<struct nlmsghdr *>(buf.data());
|
|
74
|
+
int remaining = static_cast<int>(ret);
|
|
75
|
+
bool done = false;
|
|
76
|
+
while (mnl_nlmsg_ok(cur, remaining)) {
|
|
77
|
+
if (cur->nlmsg_seq != seq || cur->nlmsg_pid != portid_) {
|
|
78
|
+
cur = mnl_nlmsg_next(cur, &remaining);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (cur->nlmsg_type == NLMSG_ERROR) {
|
|
82
|
+
auto *err = static_cast<struct nlmsgerr *>(mnl_nlmsg_get_payload(cur));
|
|
83
|
+
if (err->error != 0) {
|
|
84
|
+
int code = -err->error;
|
|
85
|
+
throw helpers::SystemError(code, std::string("netlink error: ") + std::strerror(code));
|
|
86
|
+
}
|
|
87
|
+
done = true;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
if (cur->nlmsg_type == NLMSG_DONE) {
|
|
91
|
+
done = true;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (!handler(cur)) {
|
|
95
|
+
done = true;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (!(cur->nlmsg_flags & NLM_F_MULTI)) {
|
|
99
|
+
done = true;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
cur = mnl_nlmsg_next(cur, &remaining);
|
|
103
|
+
}
|
|
104
|
+
if (done) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
uint16_t NlSocket::WireGuardFamilyId() {
|
|
111
|
+
if (wgFamilyId_ != 0) {
|
|
112
|
+
return wgFamilyId_;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
std::vector<char> buf(kBufSize);
|
|
116
|
+
unsigned int seq = NextSeq();
|
|
117
|
+
|
|
118
|
+
auto *nlh = mnl_nlmsg_put_header(buf.data());
|
|
119
|
+
nlh->nlmsg_type = GENL_ID_CTRL;
|
|
120
|
+
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
121
|
+
nlh->nlmsg_seq = seq;
|
|
122
|
+
|
|
123
|
+
auto *genl = static_cast<struct genlmsghdr *>(mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)));
|
|
124
|
+
genl->cmd = CTRL_CMD_GETFAMILY;
|
|
125
|
+
genl->version = 1;
|
|
126
|
+
|
|
127
|
+
mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, WG_GENL_NAME);
|
|
128
|
+
|
|
129
|
+
uint16_t familyId = 0;
|
|
130
|
+
SendAndReceive(nlh, [&](const struct nlmsghdr *reply) {
|
|
131
|
+
FamilyIdMsgCallback(reply, &familyId);
|
|
132
|
+
return true;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (familyId == 0) {
|
|
136
|
+
throw helpers::SystemError(ENODEV, "wireguard genetlink family not found (is the wireguard kernel module loaded?)");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
wgFamilyId_ = familyId;
|
|
140
|
+
return wgFamilyId_;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
} // namespace netlink
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <functional>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
extern "C" {
|
|
7
|
+
#include <libmnl/libmnl.h>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
namespace netlink {
|
|
11
|
+
|
|
12
|
+
// Thin wrapper around a single NETLINK_GENERIC mnl_socket: resolves the
|
|
13
|
+
// "wireguard" generic-netlink family once, then sends/receives messages
|
|
14
|
+
// against it. One instance is owned by WireGuardClient for its lifetime.
|
|
15
|
+
class NlSocket {
|
|
16
|
+
public:
|
|
17
|
+
using MessageHandler = std::function<bool(const struct nlmsghdr *)>; // return false to stop early
|
|
18
|
+
|
|
19
|
+
NlSocket();
|
|
20
|
+
~NlSocket();
|
|
21
|
+
|
|
22
|
+
NlSocket(const NlSocket &) = delete;
|
|
23
|
+
NlSocket &operator=(const NlSocket &) = delete;
|
|
24
|
+
|
|
25
|
+
// Resolves and caches the "wireguard" genl family id. Throws helpers::SystemError(ENODEV)
|
|
26
|
+
// if the wireguard kernel module isn't loaded / family doesn't exist.
|
|
27
|
+
uint16_t WireGuardFamilyId();
|
|
28
|
+
|
|
29
|
+
// Sends `nlh` (already built via mnl_nlmsg_put_header against this socket's nl buffer)
|
|
30
|
+
// and feeds every reply message to `handler` until the kernel signals completion
|
|
31
|
+
// (NLMSG_DONE for dumps, or a single ack/reply for non-dump requests).
|
|
32
|
+
void SendAndReceive(struct nlmsghdr *nlh, const MessageHandler &handler);
|
|
33
|
+
|
|
34
|
+
uint32_t Portid() const { return portid_; }
|
|
35
|
+
unsigned int NextSeq() { return ++seq_; }
|
|
36
|
+
|
|
37
|
+
private:
|
|
38
|
+
struct mnl_socket *sock_ = nullptr;
|
|
39
|
+
uint32_t portid_ = 0;
|
|
40
|
+
unsigned int seq_ = 0;
|
|
41
|
+
uint16_t wgFamilyId_ = 0;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
} // namespace netlink
|