appium-ios-tuntap 0.0.1
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/CHANGELOG.md +83 -0
- package/LICENSE +201 -0
- package/README.md +256 -0
- package/binding.gyp +84 -0
- package/build/Makefile +352 -0
- package/build/Release/.deps/Release/nothing.a.d +1 -0
- package/build/Release/.deps/Release/obj.target/nothing/node_modules/node-addon-api/nothing.o.d +4 -0
- package/build/Release/.deps/Release/obj.target/tuntap/src/tuntap.o.d +17 -0
- package/build/Release/.deps/Release/tuntap.node.d +1 -0
- package/build/Release/nothing.a +0 -0
- package/build/Release/obj.target/nothing/node_modules/node-addon-api/nothing.o +0 -0
- package/build/Release/obj.target/tuntap/src/tuntap.o +0 -0
- package/build/Release/tuntap.node +0 -0
- package/build/binding.Makefile +6 -0
- package/build/config.gypi +441 -0
- package/build/gyp-mac-tool +768 -0
- package/build/node_modules/node-addon-api/node_api.Makefile +6 -0
- package/build/node_modules/node-addon-api/nothing.target.mk +185 -0
- package/build/tuntap.target.mk +222 -0
- package/lib/TunTap.d.ts +40 -0
- package/lib/TunTap.js +347 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/logger.d.ts +1 -0
- package/lib/logger.js +9 -0
- package/lib/tunnel.d.ts +60 -0
- package/lib/tunnel.js +486 -0
- package/package.json +49 -0
- package/src/tuntap.cc +501 -0
- package/test/check-linux-prereqs.sh +96 -0
- package/test/test-tuntap.js +150 -0
package/src/tuntap.cc
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
#include <unistd.h>
|
|
3
|
+
#include <fcntl.h>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <string.h>
|
|
6
|
+
#include <errno.h>
|
|
7
|
+
#include <sys/ioctl.h>
|
|
8
|
+
#include <vector>
|
|
9
|
+
#include <memory>
|
|
10
|
+
#include <mutex>
|
|
11
|
+
#include <atomic>
|
|
12
|
+
#include <csignal>
|
|
13
|
+
|
|
14
|
+
#ifdef __APPLE__
|
|
15
|
+
#include <sys/kern_control.h>
|
|
16
|
+
#include <sys/socket.h>
|
|
17
|
+
#include <sys/sys_domain.h>
|
|
18
|
+
#include <net/if_utun.h>
|
|
19
|
+
#include <netinet/in.h>
|
|
20
|
+
#include <netinet6/in6_var.h>
|
|
21
|
+
#define UTUN_CONTROL_NAME "com.apple.net.utun_control"
|
|
22
|
+
#else
|
|
23
|
+
#include <linux/if.h>
|
|
24
|
+
#include <linux/if_tun.h>
|
|
25
|
+
#include <sys/stat.h>
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
// Global state for signal handling
|
|
29
|
+
static std::atomic<bool> g_shutdown_requested(false);
|
|
30
|
+
static std::mutex g_devices_mutex;
|
|
31
|
+
static std::vector<class TunDevice*> g_active_devices;
|
|
32
|
+
|
|
33
|
+
// Signal handler
|
|
34
|
+
static void signal_handler(int sig) {
|
|
35
|
+
if (sig == SIGINT || sig == SIGTERM) {
|
|
36
|
+
g_shutdown_requested.store(true);
|
|
37
|
+
|
|
38
|
+
// Close all active devices
|
|
39
|
+
std::lock_guard<std::mutex> lock(g_devices_mutex);
|
|
40
|
+
for (auto device : g_active_devices) {
|
|
41
|
+
if (device) {
|
|
42
|
+
// This will be handled in the device's close method
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// RAII wrapper for file descriptors
|
|
49
|
+
class FileDescriptor {
|
|
50
|
+
private:
|
|
51
|
+
int fd_;
|
|
52
|
+
|
|
53
|
+
public:
|
|
54
|
+
FileDescriptor() : fd_(-1) {}
|
|
55
|
+
explicit FileDescriptor(int fd) : fd_(fd) {}
|
|
56
|
+
|
|
57
|
+
~FileDescriptor() {
|
|
58
|
+
if (fd_ >= 0) {
|
|
59
|
+
::close(fd_);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Disable copy
|
|
64
|
+
FileDescriptor(const FileDescriptor&) = delete;
|
|
65
|
+
FileDescriptor& operator=(const FileDescriptor&) = delete;
|
|
66
|
+
|
|
67
|
+
// Enable move
|
|
68
|
+
FileDescriptor(FileDescriptor&& other) noexcept : fd_(other.fd_) {
|
|
69
|
+
other.fd_ = -1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
FileDescriptor& operator=(FileDescriptor&& other) noexcept {
|
|
73
|
+
if (this != &other) {
|
|
74
|
+
if (fd_ >= 0) {
|
|
75
|
+
::close(fd_);
|
|
76
|
+
}
|
|
77
|
+
fd_ = other.fd_;
|
|
78
|
+
other.fd_ = -1;
|
|
79
|
+
}
|
|
80
|
+
return *this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
int get() const { return fd_; }
|
|
84
|
+
|
|
85
|
+
int release() {
|
|
86
|
+
int temp = fd_;
|
|
87
|
+
fd_ = -1;
|
|
88
|
+
return temp;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
bool is_valid() const { return fd_ >= 0; }
|
|
92
|
+
|
|
93
|
+
void reset(int fd = -1) {
|
|
94
|
+
if (fd_ >= 0) {
|
|
95
|
+
::close(fd_);
|
|
96
|
+
}
|
|
97
|
+
fd_ = fd;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
class TunDevice : public Napi::ObjectWrap<TunDevice> {
|
|
102
|
+
public:
|
|
103
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
104
|
+
TunDevice(const Napi::CallbackInfo& info);
|
|
105
|
+
~TunDevice();
|
|
106
|
+
|
|
107
|
+
private:
|
|
108
|
+
static Napi::FunctionReference constructor;
|
|
109
|
+
static std::once_flag signal_handler_flag;
|
|
110
|
+
|
|
111
|
+
Napi::Value Open(const Napi::CallbackInfo& info);
|
|
112
|
+
Napi::Value Close(const Napi::CallbackInfo& info);
|
|
113
|
+
Napi::Value Read(const Napi::CallbackInfo& info);
|
|
114
|
+
Napi::Value Write(const Napi::CallbackInfo& info);
|
|
115
|
+
Napi::Value GetName(const Napi::CallbackInfo& info);
|
|
116
|
+
Napi::Value GetFd(const Napi::CallbackInfo& info);
|
|
117
|
+
|
|
118
|
+
FileDescriptor fd_;
|
|
119
|
+
std::string name_;
|
|
120
|
+
std::atomic<bool> is_open_;
|
|
121
|
+
std::mutex device_mutex_;
|
|
122
|
+
|
|
123
|
+
void RegisterDevice();
|
|
124
|
+
void UnregisterDevice();
|
|
125
|
+
void CloseInternal();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
Napi::FunctionReference TunDevice::constructor;
|
|
129
|
+
std::once_flag TunDevice::signal_handler_flag;
|
|
130
|
+
|
|
131
|
+
Napi::Object TunDevice::Init(Napi::Env env, Napi::Object exports) {
|
|
132
|
+
Napi::HandleScope scope(env);
|
|
133
|
+
|
|
134
|
+
// Install signal handlers once
|
|
135
|
+
std::call_once(signal_handler_flag, []() {
|
|
136
|
+
std::signal(SIGINT, signal_handler);
|
|
137
|
+
std::signal(SIGTERM, signal_handler);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
Napi::Function func = DefineClass(env, "TunDevice", {
|
|
141
|
+
InstanceMethod("open", &TunDevice::Open),
|
|
142
|
+
InstanceMethod("close", &TunDevice::Close),
|
|
143
|
+
InstanceMethod("read", &TunDevice::Read),
|
|
144
|
+
InstanceMethod("write", &TunDevice::Write),
|
|
145
|
+
InstanceMethod("getName", &TunDevice::GetName),
|
|
146
|
+
InstanceMethod("getFd", &TunDevice::GetFd),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
constructor = Napi::Persistent(func);
|
|
150
|
+
constructor.SuppressDestruct();
|
|
151
|
+
|
|
152
|
+
exports.Set("TunDevice", func);
|
|
153
|
+
return exports;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
TunDevice::TunDevice(const Napi::CallbackInfo& info)
|
|
157
|
+
: Napi::ObjectWrap<TunDevice>(info), is_open_(false) {
|
|
158
|
+
Napi::Env env = info.Env();
|
|
159
|
+
Napi::HandleScope scope(env);
|
|
160
|
+
|
|
161
|
+
if (info.Length() > 0 && info[0].IsString()) {
|
|
162
|
+
name_ = info[0].As<Napi::String>().Utf8Value();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
TunDevice::~TunDevice() {
|
|
167
|
+
CloseInternal();
|
|
168
|
+
UnregisterDevice();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
void TunDevice::RegisterDevice() {
|
|
172
|
+
std::lock_guard<std::mutex> lock(g_devices_mutex);
|
|
173
|
+
g_active_devices.push_back(this);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
void TunDevice::UnregisterDevice() {
|
|
177
|
+
std::lock_guard<std::mutex> lock(g_devices_mutex);
|
|
178
|
+
g_active_devices.erase(
|
|
179
|
+
std::remove(g_active_devices.begin(), g_active_devices.end(), this),
|
|
180
|
+
g_active_devices.end()
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
void TunDevice::CloseInternal() {
|
|
185
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
186
|
+
if (is_open_.exchange(false)) {
|
|
187
|
+
fd_.reset();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Napi::Value TunDevice::Open(const Napi::CallbackInfo& info) {
|
|
192
|
+
Napi::Env env = info.Env();
|
|
193
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
194
|
+
|
|
195
|
+
if (is_open_) {
|
|
196
|
+
return Napi::Boolean::New(env, true);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (g_shutdown_requested.load()) {
|
|
200
|
+
Napi::Error::New(env, "Shutdown in progress").ThrowAsJavaScriptException();
|
|
201
|
+
return Napi::Boolean::New(env, false);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#ifdef __APPLE__
|
|
205
|
+
// macOS implementation using utun interfaces
|
|
206
|
+
struct ctl_info ctlInfo;
|
|
207
|
+
struct sockaddr_ctl sc;
|
|
208
|
+
|
|
209
|
+
FileDescriptor temp_fd(socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL));
|
|
210
|
+
if (!temp_fd.is_valid()) {
|
|
211
|
+
std::string error = "Failed to create control socket: ";
|
|
212
|
+
error += strerror(errno);
|
|
213
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
214
|
+
return Napi::Boolean::New(env, false);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
memset(&ctlInfo, 0, sizeof(ctlInfo));
|
|
218
|
+
strncpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name) - 1);
|
|
219
|
+
ctlInfo.ctl_name[sizeof(ctlInfo.ctl_name) - 1] = '\0';
|
|
220
|
+
|
|
221
|
+
if (ioctl(temp_fd.get(), CTLIOCGINFO, &ctlInfo) < 0) {
|
|
222
|
+
std::string error = "Failed to get utun control info: ";
|
|
223
|
+
error += strerror(errno);
|
|
224
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
225
|
+
return Napi::Boolean::New(env, false);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
memset(&sc, 0, sizeof(sc));
|
|
229
|
+
sc.sc_len = sizeof(sc);
|
|
230
|
+
sc.sc_family = AF_SYSTEM;
|
|
231
|
+
sc.ss_sysaddr = SYSPROTO_CONTROL;
|
|
232
|
+
sc.sc_id = ctlInfo.ctl_id;
|
|
233
|
+
|
|
234
|
+
// Parse utun number if provided, otherwise use a default (utun0 = unit 1)
|
|
235
|
+
int utun_unit = 0;
|
|
236
|
+
if (!name_.empty() && name_.find("utun") == 0) {
|
|
237
|
+
try {
|
|
238
|
+
utun_unit = std::stoi(name_.substr(4)) + 1; // +1 because kernel uses unit=1 for utun0
|
|
239
|
+
} catch(...) {
|
|
240
|
+
utun_unit = 0;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (utun_unit > 0) {
|
|
245
|
+
sc.sc_unit = utun_unit;
|
|
246
|
+
// Try to connect with the specified unit
|
|
247
|
+
if (connect(temp_fd.get(), (struct sockaddr*)&sc, sizeof(sc)) < 0) {
|
|
248
|
+
std::string error = "Failed to connect to utun control socket with specified unit: ";
|
|
249
|
+
error += strerror(errno);
|
|
250
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
251
|
+
return Napi::Boolean::New(env, false);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
// Find the first available unit
|
|
255
|
+
bool connected = false;
|
|
256
|
+
for (sc.sc_unit = 1; sc.sc_unit < 255; sc.sc_unit++) {
|
|
257
|
+
if (connect(temp_fd.get(), (struct sockaddr*)&sc, sizeof(sc)) == 0) {
|
|
258
|
+
connected = true;
|
|
259
|
+
break;
|
|
260
|
+
} else if (errno != EBUSY) {
|
|
261
|
+
std::string error = "Failed to connect to utun control socket: ";
|
|
262
|
+
error += strerror(errno);
|
|
263
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
264
|
+
return Napi::Boolean::New(env, false);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!connected) {
|
|
269
|
+
Napi::Error::New(env, "Could not find an available utun device").ThrowAsJavaScriptException();
|
|
270
|
+
return Napi::Boolean::New(env, false);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Get the utun device name
|
|
275
|
+
char utunname[20];
|
|
276
|
+
socklen_t utunname_len = sizeof(utunname);
|
|
277
|
+
if (getsockopt(temp_fd.get(), SYSPROTO_CONTROL, UTUN_OPT_IFNAME, utunname, &utunname_len) < 0) {
|
|
278
|
+
std::string error = "Failed to get utun interface name: ";
|
|
279
|
+
error += strerror(errno);
|
|
280
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
281
|
+
return Napi::Boolean::New(env, false);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
name_ = std::string(utunname);
|
|
285
|
+
|
|
286
|
+
#else
|
|
287
|
+
// Linux implementation using TUN/TAP
|
|
288
|
+
// First check if /dev/net/tun exists
|
|
289
|
+
struct stat statbuf;
|
|
290
|
+
if (stat("/dev/net/tun", &statbuf) != 0) {
|
|
291
|
+
std::string error = "TUN/TAP device not available: /dev/net/tun does not exist. ";
|
|
292
|
+
error += "Please ensure the TUN/TAP kernel module is loaded (modprobe tun).";
|
|
293
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
294
|
+
return Napi::Boolean::New(env, false);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
FileDescriptor temp_fd(open("/dev/net/tun", O_RDWR));
|
|
298
|
+
if (!temp_fd.is_valid()) {
|
|
299
|
+
std::string error = "Failed to open /dev/net/tun: ";
|
|
300
|
+
error += strerror(errno);
|
|
301
|
+
error += ". This usually means you don't have sufficient permissions. ";
|
|
302
|
+
error += "Try running with sudo or add your user to the 'tun' group.";
|
|
303
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
304
|
+
return Napi::Boolean::New(env, false);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
struct ifreq ifr;
|
|
308
|
+
memset(&ifr, 0, sizeof(ifr));
|
|
309
|
+
|
|
310
|
+
// Set flags - IFF_TUN for TUN device, IFF_NO_PI to not provide packet info
|
|
311
|
+
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
|
312
|
+
|
|
313
|
+
// If name is provided, use it
|
|
314
|
+
if (!name_.empty()) {
|
|
315
|
+
strncpy(ifr.ifr_name, name_.c_str(), IFNAMSIZ - 1);
|
|
316
|
+
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (ioctl(temp_fd.get(), TUNSETIFF, &ifr) < 0) {
|
|
320
|
+
std::string error = "Failed to configure TUN device: ";
|
|
321
|
+
error += strerror(errno);
|
|
322
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
323
|
+
return Napi::Boolean::New(env, false);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
name_ = std::string(ifr.ifr_name);
|
|
327
|
+
#endif
|
|
328
|
+
|
|
329
|
+
// Set non-blocking mode
|
|
330
|
+
int flags = fcntl(temp_fd.get(), F_GETFL, 0);
|
|
331
|
+
if (flags < 0) {
|
|
332
|
+
std::string error = "Failed to get file descriptor flags: ";
|
|
333
|
+
error += strerror(errno);
|
|
334
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
335
|
+
return Napi::Boolean::New(env, false);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (fcntl(temp_fd.get(), F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
339
|
+
std::string error = "Failed to set non-blocking mode: ";
|
|
340
|
+
error += strerror(errno);
|
|
341
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
342
|
+
return Napi::Boolean::New(env, false);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Transfer ownership to member variable
|
|
346
|
+
fd_ = std::move(temp_fd);
|
|
347
|
+
is_open_ = true;
|
|
348
|
+
|
|
349
|
+
// Register this device for signal handling
|
|
350
|
+
RegisterDevice();
|
|
351
|
+
|
|
352
|
+
return Napi::Boolean::New(env, true);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
Napi::Value TunDevice::Close(const Napi::CallbackInfo& info) {
|
|
356
|
+
Napi::Env env = info.Env();
|
|
357
|
+
CloseInternal();
|
|
358
|
+
return Napi::Boolean::New(env, true);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
Napi::Value TunDevice::Read(const Napi::CallbackInfo& info) {
|
|
362
|
+
Napi::Env env = info.Env();
|
|
363
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
364
|
+
|
|
365
|
+
if (!is_open_ || !fd_.is_valid()) {
|
|
366
|
+
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
367
|
+
return env.Null();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (g_shutdown_requested.load()) {
|
|
371
|
+
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Read buffer size
|
|
375
|
+
size_t buffer_size = 4096; // Default
|
|
376
|
+
if (info.Length() > 0 && info[0].IsNumber()) {
|
|
377
|
+
buffer_size = info[0].As<Napi::Number>().Uint32Value();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Create buffer for reading
|
|
381
|
+
Napi::Buffer<uint8_t> buffer = Napi::Buffer<uint8_t>::New(env, buffer_size);
|
|
382
|
+
uint8_t* data = buffer.Data();
|
|
383
|
+
|
|
384
|
+
#ifdef __APPLE__
|
|
385
|
+
// On macOS, reads include a 4-byte protocol family prefix
|
|
386
|
+
// We'll read the packet and then remove this prefix
|
|
387
|
+
std::vector<uint8_t> tmp_buffer(buffer_size + 4);
|
|
388
|
+
|
|
389
|
+
ssize_t bytes_read = read(fd_.get(), tmp_buffer.data(), buffer_size + 4);
|
|
390
|
+
if (bytes_read <= 0) {
|
|
391
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
392
|
+
// No data available
|
|
393
|
+
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Error occurred
|
|
397
|
+
std::string error = "Read error: ";
|
|
398
|
+
error += strerror(errno);
|
|
399
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
400
|
+
return env.Null();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Skip the 4-byte protocol family header
|
|
404
|
+
if (bytes_read > 4) {
|
|
405
|
+
memcpy(data, tmp_buffer.data() + 4, bytes_read - 4);
|
|
406
|
+
return Napi::Buffer<uint8_t>::Copy(env, data, bytes_read - 4);
|
|
407
|
+
} else {
|
|
408
|
+
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
409
|
+
}
|
|
410
|
+
#else
|
|
411
|
+
// On Linux, we read directly into the buffer
|
|
412
|
+
ssize_t bytes_read = read(fd_.get(), data, buffer_size);
|
|
413
|
+
if (bytes_read < 0) {
|
|
414
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
415
|
+
// No data available
|
|
416
|
+
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Error occurred
|
|
420
|
+
std::string error = "Read error: ";
|
|
421
|
+
error += strerror(errno);
|
|
422
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
423
|
+
return env.Null();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return Napi::Buffer<uint8_t>::Copy(env, data, bytes_read);
|
|
427
|
+
#endif
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
Napi::Value TunDevice::Write(const Napi::CallbackInfo& info) {
|
|
431
|
+
Napi::Env env = info.Env();
|
|
432
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
433
|
+
|
|
434
|
+
if (!is_open_ || !fd_.is_valid()) {
|
|
435
|
+
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
436
|
+
return Napi::Number::New(env, -1);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (g_shutdown_requested.load()) {
|
|
440
|
+
Napi::Error::New(env, "Shutdown in progress").ThrowAsJavaScriptException();
|
|
441
|
+
return Napi::Number::New(env, -1);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (info.Length() < 1 || !info[0].IsBuffer()) {
|
|
445
|
+
Napi::TypeError::New(env, "Expected buffer as first argument").ThrowAsJavaScriptException();
|
|
446
|
+
return Napi::Number::New(env, -1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
Napi::Buffer<uint8_t> buffer = info[0].As<Napi::Buffer<uint8_t>>();
|
|
450
|
+
uint8_t* data = buffer.Data();
|
|
451
|
+
size_t length = buffer.Length();
|
|
452
|
+
|
|
453
|
+
#ifdef __APPLE__
|
|
454
|
+
// On macOS, we need to prepend a 4-byte protocol family header
|
|
455
|
+
// For IPv6, the protocol family is AF_INET6 (30 on macOS)
|
|
456
|
+
std::vector<uint8_t> tmp_buffer(length + 4);
|
|
457
|
+
uint32_t family = htonl(AF_INET6);
|
|
458
|
+
|
|
459
|
+
memcpy(tmp_buffer.data(), &family, 4);
|
|
460
|
+
memcpy(tmp_buffer.data() + 4, data, length);
|
|
461
|
+
|
|
462
|
+
ssize_t bytes_written = write(fd_.get(), tmp_buffer.data(), length + 4);
|
|
463
|
+
if (bytes_written < 0) {
|
|
464
|
+
std::string error = "Write error: ";
|
|
465
|
+
error += strerror(errno);
|
|
466
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
467
|
+
return Napi::Number::New(env, -1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Return the original data length without the header
|
|
471
|
+
return Napi::Number::New(env, bytes_written > 4 ? bytes_written - 4 : 0);
|
|
472
|
+
#else
|
|
473
|
+
// On Linux, we write directly from the buffer
|
|
474
|
+
ssize_t bytes_written = write(fd_.get(), data, length);
|
|
475
|
+
if (bytes_written < 0) {
|
|
476
|
+
std::string error = "Write error: ";
|
|
477
|
+
error += strerror(errno);
|
|
478
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
479
|
+
return Napi::Number::New(env, -1);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return Napi::Number::New(env, bytes_written);
|
|
483
|
+
#endif
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
Napi::Value TunDevice::GetName(const Napi::CallbackInfo& info) {
|
|
487
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
488
|
+
return Napi::String::New(info.Env(), name_);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
Napi::Value TunDevice::GetFd(const Napi::CallbackInfo& info) {
|
|
492
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
493
|
+
return Napi::Number::New(info.Env(), fd_.get());
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Module initialization
|
|
497
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
498
|
+
return TunDevice::Init(env, exports);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
NODE_API_MODULE(tuntap, Init)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# ANSI color codes
|
|
4
|
+
GREEN='\033[0;32m'
|
|
5
|
+
RED='\033[0;31m'
|
|
6
|
+
YELLOW='\033[0;33m'
|
|
7
|
+
NC='\033[0m' # No Color
|
|
8
|
+
|
|
9
|
+
echo "TunTap Bridge Linux Prerequisites Check"
|
|
10
|
+
echo "======================================"
|
|
11
|
+
echo
|
|
12
|
+
|
|
13
|
+
# Check if running as root
|
|
14
|
+
if [ "$EUID" -ne 0 ]; then
|
|
15
|
+
echo -e "${YELLOW}Warning: Not running as root. Some checks may fail.${NC}"
|
|
16
|
+
echo "For a complete check, run this script with sudo."
|
|
17
|
+
echo
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Check if TUN/TAP module is loaded
|
|
21
|
+
echo -n "Checking TUN/TAP kernel module... "
|
|
22
|
+
if lsmod | grep -q "^tun"; then
|
|
23
|
+
echo -e "${GREEN}LOADED${NC}"
|
|
24
|
+
else
|
|
25
|
+
echo -e "${RED}NOT LOADED${NC}"
|
|
26
|
+
echo " To load the TUN/TAP module, run: sudo modprobe tun"
|
|
27
|
+
echo " To load it automatically at boot, run: echo \"tun\" | sudo tee -a /etc/modules"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Check if /dev/net/tun exists
|
|
31
|
+
echo -n "Checking /dev/net/tun device... "
|
|
32
|
+
if [ -e /dev/net/tun ]; then
|
|
33
|
+
echo -e "${GREEN}EXISTS${NC}"
|
|
34
|
+
|
|
35
|
+
# Check permissions on /dev/net/tun
|
|
36
|
+
echo -n "Checking /dev/net/tun permissions... "
|
|
37
|
+
TUN_PERMS=$(ls -la /dev/net/tun | awk '{print $1}')
|
|
38
|
+
TUN_GROUP=$(ls -la /dev/net/tun | awk '{print $4}')
|
|
39
|
+
echo "$TUN_PERMS ($TUN_GROUP)"
|
|
40
|
+
|
|
41
|
+
# Check if current user can access /dev/net/tun
|
|
42
|
+
echo -n "Can current user access /dev/net/tun? "
|
|
43
|
+
if [ -r /dev/net/tun ] && [ -w /dev/net/tun ]; then
|
|
44
|
+
echo -e "${GREEN}YES${NC}"
|
|
45
|
+
else
|
|
46
|
+
echo -e "${RED}NO${NC}"
|
|
47
|
+
echo " To fix permissions, you can:"
|
|
48
|
+
echo " 1. Run your application with sudo"
|
|
49
|
+
echo " 2. Add your user to the '$TUN_GROUP' group: sudo usermod -a -G $TUN_GROUP $USER"
|
|
50
|
+
echo " 3. Create a udev rule: echo 'KERNEL==\"tun\", GROUP=\"$USER\", MODE=\"0660\"' | sudo tee /etc/udev/rules.d/99-tuntap.rules"
|
|
51
|
+
fi
|
|
52
|
+
else
|
|
53
|
+
echo -e "${RED}MISSING${NC}"
|
|
54
|
+
echo " The TUN/TAP device file is missing. This usually means the kernel module is not loaded."
|
|
55
|
+
echo " Try running: sudo modprobe tun"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Check for iproute2 package
|
|
59
|
+
echo -n "Checking for 'ip' command (iproute2)... "
|
|
60
|
+
if command -v ip &> /dev/null; then
|
|
61
|
+
echo -e "${GREEN}INSTALLED${NC} ($(which ip))"
|
|
62
|
+
else
|
|
63
|
+
echo -e "${RED}MISSING${NC}"
|
|
64
|
+
echo " The 'ip' command is required for configuring network interfaces."
|
|
65
|
+
echo " Install it with: sudo apt install iproute2 (Debian/Ubuntu)"
|
|
66
|
+
echo " sudo yum install iproute (CentOS/RHEL)"
|
|
67
|
+
echo " sudo pacman -S iproute2 (Arch Linux)"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Check for kernel headers
|
|
71
|
+
echo -n "Checking for kernel headers... "
|
|
72
|
+
KERNEL_VERSION=$(uname -r)
|
|
73
|
+
if [ -d "/lib/modules/$KERNEL_VERSION/build" ]; then
|
|
74
|
+
echo -e "${GREEN}INSTALLED${NC}"
|
|
75
|
+
else
|
|
76
|
+
echo -e "${YELLOW}POSSIBLY MISSING${NC}"
|
|
77
|
+
echo " Kernel headers are required for building the native module."
|
|
78
|
+
echo " Install them with: sudo apt install linux-headers-$(uname -r) (Debian/Ubuntu)"
|
|
79
|
+
echo " sudo yum install kernel-devel (CentOS/RHEL)"
|
|
80
|
+
echo " sudo pacman -S linux-headers (Arch Linux)"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Check for sudo privileges
|
|
84
|
+
echo -n "Checking sudo privileges... "
|
|
85
|
+
if sudo -n true 2>/dev/null; then
|
|
86
|
+
echo -e "${GREEN}AVAILABLE${NC}"
|
|
87
|
+
else
|
|
88
|
+
echo -e "${YELLOW}REQUIRES PASSWORD${NC}"
|
|
89
|
+
echo " The module uses sudo to configure network interfaces."
|
|
90
|
+
echo " You'll be prompted for your password when running applications that use this module."
|
|
91
|
+
echo " To avoid password prompts, you can configure sudo to allow specific commands without a password."
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
echo
|
|
95
|
+
echo "Prerequisites check completed."
|
|
96
|
+
echo "For more information, see the README.md file."
|