@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.
Files changed (44) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +105 -0
  3. package/bin/x86_64-linux-gnu/node-wireguard.node +0 -0
  4. package/binding.gyp +53 -0
  5. package/lib/binding.d.ts +5 -0
  6. package/lib/binding.js +45 -0
  7. package/lib/index.d.ts +56 -0
  8. package/lib/index.js +115 -0
  9. package/lib/types/AllowedIP.d.ts +5 -0
  10. package/lib/types/AllowedIP.js +2 -0
  11. package/lib/types/Config.d.ts +17 -0
  12. package/lib/types/Config.js +2 -0
  13. package/lib/types/Device.d.ts +23 -0
  14. package/lib/types/Device.js +2 -0
  15. package/lib/types/Key.d.ts +5 -0
  16. package/lib/types/Key.js +2 -0
  17. package/lib/types/Peer.d.ts +22 -0
  18. package/lib/types/Peer.js +2 -0
  19. package/lib/types/PeerConfig.d.ts +24 -0
  20. package/lib/types/PeerConfig.js +2 -0
  21. package/lib/types/index.d.ts +6 -0
  22. package/lib/types/index.js +22 -0
  23. package/package.json +80 -0
  24. package/src/WireGuardClient.cpp +490 -0
  25. package/src/WireGuardClient.h +32 -0
  26. package/src/WireGuardTypes.h +68 -0
  27. package/src/crypto/Key.cpp +77 -0
  28. package/src/crypto/Key.h +30 -0
  29. package/src/helpers/Array.h +26 -0
  30. package/src/helpers/AsyncPromise.h +91 -0
  31. package/src/helpers/IfName.cpp +27 -0
  32. package/src/helpers/IfName.h +17 -0
  33. package/src/netlink/NlAttr.cpp +398 -0
  34. package/src/netlink/NlAttr.h +40 -0
  35. package/src/netlink/NlSocket.cpp +143 -0
  36. package/src/netlink/NlSocket.h +44 -0
  37. package/src/netlink/RtLink.cpp +217 -0
  38. package/src/netlink/RtLink.h +34 -0
  39. package/src/netlink/wireguard_uapi.h +68 -0
  40. package/src/node-wireguard.cpp +48 -0
  41. package/src/uapi/UapiCodec.cpp +185 -0
  42. package/src/uapi/UapiCodec.h +22 -0
  43. package/src/uapi/UapiSocket.cpp +116 -0
  44. 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