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/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."