appium-ios-tuntap 0.2.0 → 0.2.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 +6 -0
- package/binding.gyp +11 -1
- package/package.json +2 -1
- package/prebuilds/darwin-arm64/appium-ios-tuntap.node +0 -0
- package/prebuilds/darwin-x64/appium-ios-tuntap.node +0 -0
- package/prebuilds/linux-arm64/appium-ios-tuntap.node +0 -0
- package/prebuilds/linux-x64/appium-ios-tuntap.node +0 -0
- package/src/native/file_descriptor.cc +49 -0
- package/src/native/file_descriptor.h +22 -0
- package/src/native/tun_backend.h +38 -0
- package/src/native/tun_backend_common.cc +27 -0
- package/src/native/tun_backend_darwin.cc +152 -0
- package/src/native/tun_backend_linux.cc +94 -0
- package/src/tuntap.cc +80 -266
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.2.1](https://github.com/appium/appium-ios-tuntap/compare/v0.2.0...v0.2.1) (2026-04-13)
|
|
2
|
+
|
|
3
|
+
### Miscellaneous Chores
|
|
4
|
+
|
|
5
|
+
* Refactor native tuntap implementation ([#33](https://github.com/appium/appium-ios-tuntap/issues/33)) ([c24636e](https://github.com/appium/appium-ios-tuntap/commit/c24636ed054dd37a36a08b6b5c09bfd7f40d55b1))
|
|
6
|
+
|
|
1
7
|
## [0.2.0](https://github.com/appium/appium-ios-tuntap/compare/v0.1.10...v0.2.0) (2026-04-13)
|
|
2
8
|
|
|
3
9
|
### Features
|
package/binding.gyp
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
"targets": [
|
|
3
3
|
{
|
|
4
4
|
"target_name": "tuntap",
|
|
5
|
-
"sources": [
|
|
5
|
+
"sources": [
|
|
6
|
+
"src/tuntap.cc",
|
|
7
|
+
"src/native/file_descriptor.cc",
|
|
8
|
+
"src/native/tun_backend_common.cc"
|
|
9
|
+
],
|
|
6
10
|
"include_dirs": [
|
|
7
11
|
"<!@(node -p \"require('node-addon-api').include\")"
|
|
8
12
|
],
|
|
@@ -60,6 +64,9 @@
|
|
|
60
64
|
],
|
|
61
65
|
"conditions": [
|
|
62
66
|
["OS=='linux'", {
|
|
67
|
+
"sources": [
|
|
68
|
+
"src/native/tun_backend_linux.cc"
|
|
69
|
+
],
|
|
63
70
|
"cflags": [
|
|
64
71
|
"-pthread"
|
|
65
72
|
],
|
|
@@ -71,6 +78,9 @@
|
|
|
71
78
|
]
|
|
72
79
|
}],
|
|
73
80
|
["OS=='mac'", {
|
|
81
|
+
"sources": [
|
|
82
|
+
"src/native/tun_backend_darwin.cc"
|
|
83
|
+
],
|
|
74
84
|
"xcode_settings": {
|
|
75
85
|
"OTHER_LDFLAGS": [
|
|
76
86
|
"-framework", "SystemConfiguration",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-ios-tuntap",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Native TUN/TAP interface module for Node.js",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"src/tuntap.cc",
|
|
25
|
+
"src/native",
|
|
25
26
|
"lib",
|
|
26
27
|
"prebuilds",
|
|
27
28
|
"binding.gyp",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#include "file_descriptor.h"
|
|
2
|
+
|
|
3
|
+
#include <unistd.h>
|
|
4
|
+
|
|
5
|
+
FileDescriptor::FileDescriptor() : fd_(-1) {}
|
|
6
|
+
|
|
7
|
+
FileDescriptor::FileDescriptor(int fd) : fd_(fd) {}
|
|
8
|
+
|
|
9
|
+
FileDescriptor::~FileDescriptor() {
|
|
10
|
+
if (fd_ >= 0) {
|
|
11
|
+
::close(fd_);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
FileDescriptor::FileDescriptor(FileDescriptor&& other) noexcept : fd_(other.fd_) {
|
|
16
|
+
other.fd_ = -1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
FileDescriptor& FileDescriptor::operator=(FileDescriptor&& other) noexcept {
|
|
20
|
+
if (this != &other) {
|
|
21
|
+
if (fd_ >= 0) {
|
|
22
|
+
::close(fd_);
|
|
23
|
+
}
|
|
24
|
+
fd_ = other.fd_;
|
|
25
|
+
other.fd_ = -1;
|
|
26
|
+
}
|
|
27
|
+
return *this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
int FileDescriptor::get() const {
|
|
31
|
+
return fd_;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
int FileDescriptor::release() {
|
|
35
|
+
int temp = fd_;
|
|
36
|
+
fd_ = -1;
|
|
37
|
+
return temp;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
bool FileDescriptor::is_valid() const {
|
|
41
|
+
return fd_ >= 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void FileDescriptor::reset(int fd) {
|
|
45
|
+
if (fd_ >= 0) {
|
|
46
|
+
::close(fd_);
|
|
47
|
+
}
|
|
48
|
+
fd_ = fd;
|
|
49
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
class FileDescriptor {
|
|
4
|
+
public:
|
|
5
|
+
FileDescriptor();
|
|
6
|
+
explicit FileDescriptor(int fd);
|
|
7
|
+
~FileDescriptor();
|
|
8
|
+
|
|
9
|
+
FileDescriptor(const FileDescriptor&) = delete;
|
|
10
|
+
FileDescriptor& operator=(const FileDescriptor&) = delete;
|
|
11
|
+
|
|
12
|
+
FileDescriptor(FileDescriptor&& other) noexcept;
|
|
13
|
+
FileDescriptor& operator=(FileDescriptor&& other) noexcept;
|
|
14
|
+
|
|
15
|
+
int get() const;
|
|
16
|
+
int release();
|
|
17
|
+
bool is_valid() const;
|
|
18
|
+
void reset(int fd = -1);
|
|
19
|
+
|
|
20
|
+
private:
|
|
21
|
+
int fd_;
|
|
22
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#if !defined(__linux__) && !defined(__APPLE__)
|
|
4
|
+
#error "appium-ios-tuntap native addon supports only Linux and macOS"
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
#include <cstddef>
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
#include <memory>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <sys/types.h>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
#include "file_descriptor.h"
|
|
15
|
+
|
|
16
|
+
struct OpenResult {
|
|
17
|
+
FileDescriptor fd;
|
|
18
|
+
std::string interface_name;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
enum class ReadPacketStatus {
|
|
22
|
+
Data,
|
|
23
|
+
NoData,
|
|
24
|
+
Closed,
|
|
25
|
+
Error,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
class TunPlatformBackend {
|
|
29
|
+
public:
|
|
30
|
+
virtual ~TunPlatformBackend() = default;
|
|
31
|
+
virtual bool OpenDevice(const std::string& requested_name, OpenResult& out, std::string& error) = 0;
|
|
32
|
+
virtual ReadPacketStatus ReadPacket(int fd, size_t max_payload_size, std::vector<uint8_t>& out, std::string& error) = 0;
|
|
33
|
+
virtual ssize_t WritePacket(int fd, const uint8_t* data, size_t length, std::string& error) = 0;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
bool SetNonBlocking(int fd, std::string& error);
|
|
37
|
+
std::unique_ptr<TunPlatformBackend> CreatePlatformBackend();
|
|
38
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#include "tun_backend.h"
|
|
2
|
+
|
|
3
|
+
#include <errno.h>
|
|
4
|
+
#include <fcntl.h>
|
|
5
|
+
#include <string.h>
|
|
6
|
+
|
|
7
|
+
std::unique_ptr<TunPlatformBackend> CreatePlatformTunBackend();
|
|
8
|
+
|
|
9
|
+
bool SetNonBlocking(int fd, std::string& error) {
|
|
10
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
|
11
|
+
if (flags < 0) {
|
|
12
|
+
error = std::string("Failed to get file descriptor flags: ") + strerror(errno);
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
17
|
+
error = std::string("Failed to set non-blocking mode: ") + strerror(errno);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
std::unique_ptr<TunPlatformBackend> CreatePlatformBackend() {
|
|
25
|
+
return CreatePlatformTunBackend();
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#ifdef __APPLE__
|
|
2
|
+
|
|
3
|
+
#include "tun_backend.h"
|
|
4
|
+
|
|
5
|
+
#include <errno.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
#include <sys/ioctl.h>
|
|
8
|
+
#include <sys/kern_control.h>
|
|
9
|
+
#include <sys/socket.h>
|
|
10
|
+
#include <sys/sys_domain.h>
|
|
11
|
+
#include <unistd.h>
|
|
12
|
+
|
|
13
|
+
#include <net/if_utun.h>
|
|
14
|
+
#include <netinet/in.h>
|
|
15
|
+
#include <netinet6/in6_var.h>
|
|
16
|
+
|
|
17
|
+
#define UTUN_CONTROL_NAME "com.apple.net.utun_control"
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
class DarwinTunBackend : public TunPlatformBackend {
|
|
22
|
+
public:
|
|
23
|
+
static constexpr size_t kUtunHeaderSize = 4;
|
|
24
|
+
|
|
25
|
+
bool OpenDevice(const std::string& requested_name, OpenResult& out, std::string& error) override {
|
|
26
|
+
struct ctl_info ctl_info;
|
|
27
|
+
struct sockaddr_ctl socket_addr;
|
|
28
|
+
|
|
29
|
+
FileDescriptor temp_fd(socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL));
|
|
30
|
+
if (!temp_fd.is_valid()) {
|
|
31
|
+
error = std::string("Failed to create control socket: ") + strerror(errno);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
memset(&ctl_info, 0, sizeof(ctl_info));
|
|
36
|
+
strncpy(ctl_info.ctl_name, UTUN_CONTROL_NAME, sizeof(ctl_info.ctl_name) - 1);
|
|
37
|
+
ctl_info.ctl_name[sizeof(ctl_info.ctl_name) - 1] = '\0';
|
|
38
|
+
|
|
39
|
+
if (ioctl(temp_fd.get(), CTLIOCGINFO, &ctl_info) < 0) {
|
|
40
|
+
error = std::string("Failed to get utun control info: ") + strerror(errno);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
memset(&socket_addr, 0, sizeof(socket_addr));
|
|
45
|
+
socket_addr.sc_len = sizeof(socket_addr);
|
|
46
|
+
socket_addr.sc_family = AF_SYSTEM;
|
|
47
|
+
socket_addr.ss_sysaddr = SYSPROTO_CONTROL;
|
|
48
|
+
socket_addr.sc_id = ctl_info.ctl_id;
|
|
49
|
+
|
|
50
|
+
int utun_unit = ParseRequestedUtunUnit(requested_name);
|
|
51
|
+
if (utun_unit > 0) {
|
|
52
|
+
socket_addr.sc_unit = utun_unit;
|
|
53
|
+
if (connect(temp_fd.get(), reinterpret_cast<struct sockaddr*>(&socket_addr), sizeof(socket_addr)) < 0) {
|
|
54
|
+
error = std::string("Failed to connect to utun with specified unit: ") + strerror(errno);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
} else if (!ConnectFirstAvailableUnit(temp_fd.get(), socket_addr, error)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
char interface_name[20];
|
|
62
|
+
socklen_t interface_name_len = sizeof(interface_name);
|
|
63
|
+
if (getsockopt(temp_fd.get(), SYSPROTO_CONTROL, UTUN_OPT_IFNAME, interface_name, &interface_name_len) < 0) {
|
|
64
|
+
error = std::string("Failed to get utun interface name: ") + strerror(errno);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
out.fd = std::move(temp_fd);
|
|
69
|
+
out.interface_name = std::string(interface_name);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ReadPacketStatus ReadPacket(int fd, size_t max_payload_size, std::vector<uint8_t>& out, std::string& error) override {
|
|
74
|
+
out.resize(max_payload_size + kUtunHeaderSize);
|
|
75
|
+
ssize_t bytes_read = read(fd, out.data(), out.size());
|
|
76
|
+
if (bytes_read < 0) {
|
|
77
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
78
|
+
out.clear();
|
|
79
|
+
return ReadPacketStatus::NoData;
|
|
80
|
+
}
|
|
81
|
+
error = std::string("Read error: ") + strerror(errno);
|
|
82
|
+
return ReadPacketStatus::Error;
|
|
83
|
+
}
|
|
84
|
+
if (bytes_read == 0) {
|
|
85
|
+
out.clear();
|
|
86
|
+
return ReadPacketStatus::Closed;
|
|
87
|
+
}
|
|
88
|
+
if (bytes_read <= static_cast<ssize_t>(kUtunHeaderSize)) {
|
|
89
|
+
out.clear();
|
|
90
|
+
return ReadPacketStatus::NoData;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const auto payload_len = static_cast<size_t>(bytes_read - kUtunHeaderSize);
|
|
94
|
+
// Collapse the utun 4-byte address-family prefix in-place.
|
|
95
|
+
memmove(out.data(), out.data() + kUtunHeaderSize, payload_len);
|
|
96
|
+
out.resize(payload_len);
|
|
97
|
+
return ReadPacketStatus::Data;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ssize_t WritePacket(int fd, const uint8_t* data, size_t length, std::string& error) override {
|
|
101
|
+
std::vector<uint8_t> frame(length + kUtunHeaderSize);
|
|
102
|
+
uint32_t family = htonl(AF_INET6);
|
|
103
|
+
memcpy(frame.data(), &family, kUtunHeaderSize);
|
|
104
|
+
memcpy(frame.data() + kUtunHeaderSize, data, length);
|
|
105
|
+
|
|
106
|
+
ssize_t bytes_written = write(fd, frame.data(), frame.size());
|
|
107
|
+
if (bytes_written < 0) {
|
|
108
|
+
error = std::string("Write error: ") + strerror(errno);
|
|
109
|
+
return -1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return bytes_written > static_cast<ssize_t>(kUtunHeaderSize)
|
|
113
|
+
? bytes_written - static_cast<ssize_t>(kUtunHeaderSize)
|
|
114
|
+
: 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private:
|
|
118
|
+
static int ParseRequestedUtunUnit(const std::string& requested_name) {
|
|
119
|
+
if (requested_name.empty() || requested_name.find("utun") != 0) {
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
return std::stoi(requested_name.substr(4)) + 1;
|
|
124
|
+
} catch (...) {
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static bool ConnectFirstAvailableUnit(int fd, struct sockaddr_ctl& socket_addr, std::string& error) {
|
|
130
|
+
for (socket_addr.sc_unit = 1; socket_addr.sc_unit < 255; socket_addr.sc_unit++) {
|
|
131
|
+
if (connect(fd, reinterpret_cast<struct sockaddr*>(&socket_addr), sizeof(socket_addr)) == 0) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (errno != EBUSY) {
|
|
135
|
+
error = std::string("Failed to connect to utun control socket: ") + strerror(errno);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
error = "Could not find an available utun device";
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
} // namespace
|
|
146
|
+
|
|
147
|
+
std::unique_ptr<TunPlatformBackend> CreatePlatformTunBackend() {
|
|
148
|
+
return std::make_unique<DarwinTunBackend>();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#endif
|
|
152
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#ifdef __linux__
|
|
2
|
+
|
|
3
|
+
#include "tun_backend.h"
|
|
4
|
+
|
|
5
|
+
#include <errno.h>
|
|
6
|
+
#include <fcntl.h>
|
|
7
|
+
#include <string.h>
|
|
8
|
+
#include <sys/ioctl.h>
|
|
9
|
+
#include <sys/stat.h>
|
|
10
|
+
#include <unistd.h>
|
|
11
|
+
|
|
12
|
+
#include <linux/if.h>
|
|
13
|
+
#include <linux/if_tun.h>
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
constexpr const char* kTunDevicePath = "/dev/net/tun";
|
|
17
|
+
|
|
18
|
+
class LinuxTunBackend : public TunPlatformBackend {
|
|
19
|
+
public:
|
|
20
|
+
bool OpenDevice(const std::string& requested_name, OpenResult& out, std::string& error) override {
|
|
21
|
+
struct stat statbuf;
|
|
22
|
+
if (stat(kTunDevicePath, &statbuf) != 0) {
|
|
23
|
+
error =
|
|
24
|
+
"TUN/TAP device not available: /dev/net/tun does not exist. "
|
|
25
|
+
"Please ensure the TUN/TAP kernel module is loaded (modprobe tun).";
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
FileDescriptor temp_fd(open(kTunDevicePath, O_RDWR));
|
|
30
|
+
if (!temp_fd.is_valid()) {
|
|
31
|
+
error =
|
|
32
|
+
std::string("Failed to open ") + kTunDevicePath + ": " + strerror(errno) +
|
|
33
|
+
". This usually means you don't have sufficient permissions. "
|
|
34
|
+
"Try running with sudo or add your user to the 'tun' group.";
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
struct ifreq ifr;
|
|
39
|
+
memset(&ifr, 0, sizeof(ifr));
|
|
40
|
+
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
|
41
|
+
|
|
42
|
+
if (!requested_name.empty()) {
|
|
43
|
+
strncpy(ifr.ifr_name, requested_name.c_str(), IFNAMSIZ - 1);
|
|
44
|
+
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (ioctl(temp_fd.get(), TUNSETIFF, &ifr) < 0) {
|
|
48
|
+
error = std::string("Failed to configure TUN device: ") + strerror(errno);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
out.fd = std::move(temp_fd);
|
|
53
|
+
out.interface_name = std::string(ifr.ifr_name);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ReadPacketStatus ReadPacket(int fd, size_t max_payload_size, std::vector<uint8_t>& out, std::string& error) override {
|
|
58
|
+
out.resize(max_payload_size);
|
|
59
|
+
ssize_t bytes_read = read(fd, out.data(), out.size());
|
|
60
|
+
if (bytes_read < 0) {
|
|
61
|
+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
62
|
+
out.clear();
|
|
63
|
+
return ReadPacketStatus::NoData;
|
|
64
|
+
}
|
|
65
|
+
error = std::string("Read error: ") + strerror(errno);
|
|
66
|
+
return ReadPacketStatus::Error;
|
|
67
|
+
}
|
|
68
|
+
if (bytes_read == 0) {
|
|
69
|
+
out.clear();
|
|
70
|
+
return ReadPacketStatus::Closed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
out.resize(static_cast<size_t>(bytes_read));
|
|
74
|
+
return ReadPacketStatus::Data;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ssize_t WritePacket(int fd, const uint8_t* data, size_t length, std::string& error) override {
|
|
78
|
+
ssize_t bytes_written = write(fd, data, length);
|
|
79
|
+
if (bytes_written < 0) {
|
|
80
|
+
error = std::string("Write error: ") + strerror(errno);
|
|
81
|
+
return -1;
|
|
82
|
+
}
|
|
83
|
+
return bytes_written;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
} // namespace
|
|
88
|
+
|
|
89
|
+
std::unique_ptr<TunPlatformBackend> CreatePlatformTunBackend() {
|
|
90
|
+
return std::make_unique<LinuxTunBackend>();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#endif
|
|
94
|
+
|
package/src/tuntap.cc
CHANGED
|
@@ -1,82 +1,15 @@
|
|
|
1
1
|
#include <napi.h>
|
|
2
|
-
#include <
|
|
3
|
-
|
|
2
|
+
#include <uv.h>
|
|
3
|
+
|
|
4
4
|
#include <string>
|
|
5
|
-
#include <string.h>
|
|
6
|
-
#include <errno.h>
|
|
7
|
-
#include <sys/ioctl.h>
|
|
8
5
|
#include <vector>
|
|
9
6
|
#include <memory>
|
|
10
7
|
#include <mutex>
|
|
11
8
|
#include <atomic>
|
|
12
|
-
#include <
|
|
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
|
-
#define UTUN_HEADER_SIZE 4
|
|
23
|
-
#else
|
|
24
|
-
#include <linux/if.h>
|
|
25
|
-
#include <linux/if_tun.h>
|
|
26
|
-
#include <sys/stat.h>
|
|
27
|
-
#define UTUN_HEADER_SIZE 0
|
|
28
|
-
#endif
|
|
29
|
-
|
|
30
|
-
// RAII wrapper for file descriptors
|
|
31
|
-
class FileDescriptor {
|
|
32
|
-
private:
|
|
33
|
-
int fd_;
|
|
34
|
-
|
|
35
|
-
public:
|
|
36
|
-
FileDescriptor() : fd_(-1) {}
|
|
37
|
-
explicit FileDescriptor(int fd) : fd_(fd) {}
|
|
38
|
-
|
|
39
|
-
~FileDescriptor() {
|
|
40
|
-
if (fd_ >= 0) {
|
|
41
|
-
::close(fd_);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
FileDescriptor(const FileDescriptor&) = delete;
|
|
46
|
-
FileDescriptor& operator=(const FileDescriptor&) = delete;
|
|
47
|
-
|
|
48
|
-
FileDescriptor(FileDescriptor&& other) noexcept : fd_(other.fd_) {
|
|
49
|
-
other.fd_ = -1;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
FileDescriptor& operator=(FileDescriptor&& other) noexcept {
|
|
53
|
-
if (this != &other) {
|
|
54
|
-
if (fd_ >= 0) {
|
|
55
|
-
::close(fd_);
|
|
56
|
-
}
|
|
57
|
-
fd_ = other.fd_;
|
|
58
|
-
other.fd_ = -1;
|
|
59
|
-
}
|
|
60
|
-
return *this;
|
|
61
|
-
}
|
|
9
|
+
#include <cstdio>
|
|
62
10
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
int release() {
|
|
66
|
-
int temp = fd_;
|
|
67
|
-
fd_ = -1;
|
|
68
|
-
return temp;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
bool is_valid() const { return fd_ >= 0; }
|
|
72
|
-
|
|
73
|
-
void reset(int fd = -1) {
|
|
74
|
-
if (fd_ >= 0) {
|
|
75
|
-
::close(fd_);
|
|
76
|
-
}
|
|
77
|
-
fd_ = fd;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
11
|
+
#include "native/file_descriptor.h"
|
|
12
|
+
#include "native/tun_backend.h"
|
|
80
13
|
|
|
81
14
|
class TunDevice : public Napi::ObjectWrap<TunDevice> {
|
|
82
15
|
public:
|
|
@@ -99,6 +32,7 @@ private:
|
|
|
99
32
|
|
|
100
33
|
FileDescriptor fd_;
|
|
101
34
|
std::string name_;
|
|
35
|
+
std::unique_ptr<TunPlatformBackend> backend_;
|
|
102
36
|
std::atomic<bool> is_open_;
|
|
103
37
|
std::mutex device_mutex_;
|
|
104
38
|
|
|
@@ -113,6 +47,7 @@ private:
|
|
|
113
47
|
|
|
114
48
|
Napi::FunctionReference TunDevice::constructor;
|
|
115
49
|
|
|
50
|
+
// Defines and exports the JS class constructor: new TunDevice(name?)
|
|
116
51
|
Napi::Object TunDevice::Init(Napi::Env env, Napi::Object exports) {
|
|
117
52
|
Napi::HandleScope scope(env);
|
|
118
53
|
|
|
@@ -133,8 +68,9 @@ Napi::Object TunDevice::Init(Napi::Env env, Napi::Object exports) {
|
|
|
133
68
|
return exports;
|
|
134
69
|
}
|
|
135
70
|
|
|
71
|
+
// Creates a TunDevice wrapper; optional first arg is requested interface name.
|
|
136
72
|
TunDevice::TunDevice(const Napi::CallbackInfo& info)
|
|
137
|
-
: Napi::ObjectWrap<TunDevice>(info), is_open_(false) {
|
|
73
|
+
: Napi::ObjectWrap<TunDevice>(info), backend_(CreatePlatformBackend()), is_open_(false) {
|
|
138
74
|
Napi::Env env = info.Env();
|
|
139
75
|
Napi::HandleScope scope(env);
|
|
140
76
|
|
|
@@ -143,18 +79,14 @@ TunDevice::TunDevice(const Napi::CallbackInfo& info)
|
|
|
143
79
|
}
|
|
144
80
|
}
|
|
145
81
|
|
|
82
|
+
// Ensures fd/poll resources are closed when object is destroyed.
|
|
146
83
|
TunDevice::~TunDevice() {
|
|
147
84
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
148
85
|
CloseInternal();
|
|
149
86
|
}
|
|
150
87
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
StopPolling();
|
|
154
|
-
fd_.reset();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
88
|
+
// JS: open() -> boolean
|
|
89
|
+
// Opens the backend device and configures the fd as non-blocking.
|
|
158
90
|
Napi::Value TunDevice::Open(const Napi::CallbackInfo& info) {
|
|
159
91
|
Napi::Env env = info.Env();
|
|
160
92
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -162,140 +94,33 @@ Napi::Value TunDevice::Open(const Napi::CallbackInfo& info) {
|
|
|
162
94
|
if (is_open_) {
|
|
163
95
|
return Napi::Boolean::New(env, true);
|
|
164
96
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// macOS: create utun interface via PF_SYSTEM control socket
|
|
168
|
-
struct ctl_info ctlInfo;
|
|
169
|
-
struct sockaddr_ctl sc;
|
|
170
|
-
|
|
171
|
-
FileDescriptor temp_fd(socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL));
|
|
172
|
-
if (!temp_fd.is_valid()) {
|
|
173
|
-
Napi::Error::New(env, std::string("Failed to create control socket: ") + strerror(errno))
|
|
97
|
+
if (!backend_) {
|
|
98
|
+
Napi::Error::New(env, "Unsupported platform: no native TUN backend available")
|
|
174
99
|
.ThrowAsJavaScriptException();
|
|
175
100
|
return Napi::Boolean::New(env, false);
|
|
176
101
|
}
|
|
177
102
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (ioctl(temp_fd.get(), CTLIOCGINFO, &ctlInfo) < 0) {
|
|
183
|
-
Napi::Error::New(env, std::string("Failed to get utun control info: ") + strerror(errno))
|
|
184
|
-
.ThrowAsJavaScriptException();
|
|
103
|
+
OpenResult result;
|
|
104
|
+
std::string error;
|
|
105
|
+
if (!backend_->OpenDevice(name_, result, error)) {
|
|
106
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
185
107
|
return Napi::Boolean::New(env, false);
|
|
186
108
|
}
|
|
187
109
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
sc.sc_family = AF_SYSTEM;
|
|
191
|
-
sc.ss_sysaddr = SYSPROTO_CONTROL;
|
|
192
|
-
sc.sc_id = ctlInfo.ctl_id;
|
|
193
|
-
|
|
194
|
-
// Parse utun number if provided, otherwise auto-select (utun0 = unit 1)
|
|
195
|
-
int utun_unit = 0;
|
|
196
|
-
if (!name_.empty() && name_.find("utun") == 0) {
|
|
197
|
-
try {
|
|
198
|
-
utun_unit = std::stoi(name_.substr(4)) + 1; // +1 because kernel uses unit=1 for utun0
|
|
199
|
-
} catch(...) {
|
|
200
|
-
utun_unit = 0;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (utun_unit > 0) {
|
|
205
|
-
sc.sc_unit = utun_unit;
|
|
206
|
-
if (connect(temp_fd.get(), (struct sockaddr*)&sc, sizeof(sc)) < 0) {
|
|
207
|
-
Napi::Error::New(env, std::string("Failed to connect to utun with specified unit: ") + strerror(errno))
|
|
208
|
-
.ThrowAsJavaScriptException();
|
|
209
|
-
return Napi::Boolean::New(env, false);
|
|
210
|
-
}
|
|
211
|
-
} else {
|
|
212
|
-
bool connected = false;
|
|
213
|
-
for (sc.sc_unit = 1; sc.sc_unit < 255; sc.sc_unit++) {
|
|
214
|
-
if (connect(temp_fd.get(), (struct sockaddr*)&sc, sizeof(sc)) == 0) {
|
|
215
|
-
connected = true;
|
|
216
|
-
break;
|
|
217
|
-
} else if (errno != EBUSY) {
|
|
218
|
-
Napi::Error::New(env, std::string("Failed to connect to utun control socket: ") + strerror(errno))
|
|
219
|
-
.ThrowAsJavaScriptException();
|
|
220
|
-
return Napi::Boolean::New(env, false);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (!connected) {
|
|
225
|
-
Napi::Error::New(env, "Could not find an available utun device").ThrowAsJavaScriptException();
|
|
226
|
-
return Napi::Boolean::New(env, false);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
char utunname[20];
|
|
231
|
-
socklen_t utunname_len = sizeof(utunname);
|
|
232
|
-
if (getsockopt(temp_fd.get(), SYSPROTO_CONTROL, UTUN_OPT_IFNAME, utunname, &utunname_len) < 0) {
|
|
233
|
-
Napi::Error::New(env, std::string("Failed to get utun interface name: ") + strerror(errno))
|
|
234
|
-
.ThrowAsJavaScriptException();
|
|
235
|
-
return Napi::Boolean::New(env, false);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
name_ = std::string(utunname);
|
|
239
|
-
|
|
240
|
-
#else
|
|
241
|
-
// Linux: create TUN device via /dev/net/tun
|
|
242
|
-
struct stat statbuf;
|
|
243
|
-
if (stat("/dev/net/tun", &statbuf) != 0) {
|
|
244
|
-
Napi::Error::New(env,
|
|
245
|
-
"TUN/TAP device not available: /dev/net/tun does not exist. "
|
|
246
|
-
"Please ensure the TUN/TAP kernel module is loaded (modprobe tun).")
|
|
247
|
-
.ThrowAsJavaScriptException();
|
|
110
|
+
if (!SetNonBlocking(result.fd.get(), error)) {
|
|
111
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
248
112
|
return Napi::Boolean::New(env, false);
|
|
249
113
|
}
|
|
250
114
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
Napi::Error::New(env,
|
|
254
|
-
std::string("Failed to open /dev/net/tun: ") + strerror(errno) +
|
|
255
|
-
". This usually means you don't have sufficient permissions. "
|
|
256
|
-
"Try running with sudo or add your user to the 'tun' group.")
|
|
257
|
-
.ThrowAsJavaScriptException();
|
|
258
|
-
return Napi::Boolean::New(env, false);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
struct ifreq ifr;
|
|
262
|
-
memset(&ifr, 0, sizeof(ifr));
|
|
263
|
-
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
|
264
|
-
|
|
265
|
-
if (!name_.empty()) {
|
|
266
|
-
strncpy(ifr.ifr_name, name_.c_str(), IFNAMSIZ - 1);
|
|
267
|
-
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (ioctl(temp_fd.get(), TUNSETIFF, &ifr) < 0) {
|
|
271
|
-
Napi::Error::New(env, std::string("Failed to configure TUN device: ") + strerror(errno))
|
|
272
|
-
.ThrowAsJavaScriptException();
|
|
273
|
-
return Napi::Boolean::New(env, false);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
name_ = std::string(ifr.ifr_name);
|
|
277
|
-
#endif
|
|
278
|
-
|
|
279
|
-
// Set non-blocking mode
|
|
280
|
-
int flags = fcntl(temp_fd.get(), F_GETFL, 0);
|
|
281
|
-
if (flags < 0) {
|
|
282
|
-
Napi::Error::New(env, std::string("Failed to get file descriptor flags: ") + strerror(errno))
|
|
283
|
-
.ThrowAsJavaScriptException();
|
|
284
|
-
return Napi::Boolean::New(env, false);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (fcntl(temp_fd.get(), F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
288
|
-
Napi::Error::New(env, std::string("Failed to set non-blocking mode: ") + strerror(errno))
|
|
289
|
-
.ThrowAsJavaScriptException();
|
|
290
|
-
return Napi::Boolean::New(env, false);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
fd_ = std::move(temp_fd);
|
|
115
|
+
fd_ = std::move(result.fd);
|
|
116
|
+
name_ = result.interface_name;
|
|
294
117
|
is_open_ = true;
|
|
295
118
|
|
|
296
119
|
return Napi::Boolean::New(env, true);
|
|
297
120
|
}
|
|
298
121
|
|
|
122
|
+
// JS: close() -> boolean
|
|
123
|
+
// Safely closes device resources; calling multiple times is allowed.
|
|
299
124
|
Napi::Value TunDevice::Close(const Napi::CallbackInfo& info) {
|
|
300
125
|
Napi::Env env = info.Env();
|
|
301
126
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -303,6 +128,8 @@ Napi::Value TunDevice::Close(const Napi::CallbackInfo& info) {
|
|
|
303
128
|
return Napi::Boolean::New(env, true);
|
|
304
129
|
}
|
|
305
130
|
|
|
131
|
+
// JS: read(bufferSize?) -> Buffer
|
|
132
|
+
// Reads one payload packet, or returns an empty Buffer when no data is available.
|
|
306
133
|
Napi::Value TunDevice::Read(const Napi::CallbackInfo& info) {
|
|
307
134
|
Napi::Env env = info.Env();
|
|
308
135
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -321,38 +148,22 @@ Napi::Value TunDevice::Read(const Napi::CallbackInfo& info) {
|
|
|
321
148
|
}
|
|
322
149
|
}
|
|
323
150
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
330
|
-
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
331
|
-
}
|
|
332
|
-
Napi::Error::New(env, std::string("Read error: ") + strerror(errno))
|
|
333
|
-
.ThrowAsJavaScriptException();
|
|
151
|
+
std::vector<uint8_t> packet;
|
|
152
|
+
std::string error;
|
|
153
|
+
ReadPacketStatus read_status = backend_->ReadPacket(fd_.get(), buffer_size, packet, error);
|
|
154
|
+
if (read_status == ReadPacketStatus::Error) {
|
|
155
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
334
156
|
return env.Null();
|
|
335
157
|
}
|
|
336
|
-
if (
|
|
158
|
+
if (read_status == ReadPacketStatus::NoData || read_status == ReadPacketStatus::Closed) {
|
|
337
159
|
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
338
160
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// Linux: raw IP packets directly
|
|
342
|
-
std::vector<uint8_t> raw(buffer_size);
|
|
343
|
-
ssize_t n = read(fd_.get(), raw.data(), raw.size());
|
|
344
|
-
if (n < 0) {
|
|
345
|
-
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
346
|
-
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
347
|
-
}
|
|
348
|
-
Napi::Error::New(env, std::string("Read error: ") + strerror(errno))
|
|
349
|
-
.ThrowAsJavaScriptException();
|
|
350
|
-
return env.Null();
|
|
351
|
-
}
|
|
352
|
-
return Napi::Buffer<uint8_t>::Copy(env, raw.data(), n);
|
|
353
|
-
#endif
|
|
161
|
+
|
|
162
|
+
return Napi::Buffer<uint8_t>::Copy(env, packet.data(), packet.size());
|
|
354
163
|
}
|
|
355
164
|
|
|
165
|
+
// JS: write(buffer) -> number
|
|
166
|
+
// Writes one packet and returns payload bytes accepted by the backend.
|
|
356
167
|
Napi::Value TunDevice::Write(const Napi::CallbackInfo& info) {
|
|
357
168
|
Napi::Env env = info.Env();
|
|
358
169
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -371,41 +182,31 @@ Napi::Value TunDevice::Write(const Napi::CallbackInfo& info) {
|
|
|
371
182
|
uint8_t* data = buffer.Data();
|
|
372
183
|
size_t length = buffer.Length();
|
|
373
184
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
std::vector<uint8_t> frame(length + 4);
|
|
377
|
-
uint32_t family = htonl(AF_INET6);
|
|
378
|
-
memcpy(frame.data(), &family, 4);
|
|
379
|
-
memcpy(frame.data() + 4, data, length);
|
|
380
|
-
|
|
381
|
-
ssize_t bytes_written = write(fd_.get(), frame.data(), frame.size());
|
|
382
|
-
if (bytes_written < 0) {
|
|
383
|
-
Napi::Error::New(env, std::string("Write error: ") + strerror(errno))
|
|
384
|
-
.ThrowAsJavaScriptException();
|
|
385
|
-
return Napi::Number::New(env, -1);
|
|
386
|
-
}
|
|
387
|
-
return Napi::Number::New(env, bytes_written > 4 ? bytes_written - 4 : 0);
|
|
388
|
-
#else
|
|
389
|
-
ssize_t bytes_written = write(fd_.get(), data, length);
|
|
185
|
+
std::string error;
|
|
186
|
+
ssize_t bytes_written = backend_->WritePacket(fd_.get(), data, length, error);
|
|
390
187
|
if (bytes_written < 0) {
|
|
391
|
-
Napi::Error::New(env,
|
|
392
|
-
.ThrowAsJavaScriptException();
|
|
188
|
+
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
393
189
|
return Napi::Number::New(env, -1);
|
|
394
190
|
}
|
|
395
191
|
return Napi::Number::New(env, bytes_written);
|
|
396
|
-
#endif
|
|
397
192
|
}
|
|
398
193
|
|
|
194
|
+
// JS: getName() -> string
|
|
195
|
+
// Returns the assigned interface name after open().
|
|
399
196
|
Napi::Value TunDevice::GetName(const Napi::CallbackInfo& info) {
|
|
400
197
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
401
198
|
return Napi::String::New(info.Env(), name_);
|
|
402
199
|
}
|
|
403
200
|
|
|
201
|
+
// JS: getFd() -> number
|
|
202
|
+
// Returns the native file descriptor, or -1 before open()/after close().
|
|
404
203
|
Napi::Value TunDevice::GetFd(const Napi::CallbackInfo& info) {
|
|
405
204
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
406
205
|
return Napi::Number::New(info.Env(), fd_.get());
|
|
407
206
|
}
|
|
408
207
|
|
|
208
|
+
// JS: startPolling(callback, bufferSize?) -> void
|
|
209
|
+
// Starts libuv polling and invokes callback with packet payload Buffers.
|
|
409
210
|
Napi::Value TunDevice::StartPolling(const Napi::CallbackInfo& info) {
|
|
410
211
|
Napi::Env env = info.Env();
|
|
411
212
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -474,6 +275,23 @@ Napi::Value TunDevice::StartPolling(const Napi::CallbackInfo& info) {
|
|
|
474
275
|
return env.Undefined();
|
|
475
276
|
}
|
|
476
277
|
|
|
278
|
+
// Node-API module entrypoint.
|
|
279
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
280
|
+
return TunDevice::Init(env, exports);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
NODE_API_MODULE(tuntap, Init)
|
|
284
|
+
// #endregion
|
|
285
|
+
|
|
286
|
+
// #region Private implementation details
|
|
287
|
+
|
|
288
|
+
void TunDevice::CloseInternal() {
|
|
289
|
+
if (is_open_.exchange(false)) {
|
|
290
|
+
StopPolling();
|
|
291
|
+
fd_.reset();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
477
295
|
void TunDevice::StopPolling() {
|
|
478
296
|
if (poll_handle_) {
|
|
479
297
|
uv_poll_stop(poll_handle_);
|
|
@@ -508,33 +326,29 @@ void TunDevice::PollCallback(uv_poll_t* handle, int status, int events) {
|
|
|
508
326
|
return;
|
|
509
327
|
}
|
|
510
328
|
|
|
511
|
-
std::vector<uint8_t>
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
329
|
+
std::vector<uint8_t> packet;
|
|
330
|
+
std::string error;
|
|
331
|
+
ReadPacketStatus read_status = self->backend_->ReadPacket(self->fd_.get(), self->poll_buffer_size_, packet, error);
|
|
332
|
+
// Backend reported an unrecoverable read failure; stop polling this fd.
|
|
333
|
+
if (read_status == ReadPacketStatus::Error) {
|
|
334
|
+
fprintf(stderr, "tuntap read error: %s\n", error.c_str());
|
|
515
335
|
self->StopPolling();
|
|
516
336
|
return;
|
|
517
337
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
self->StopPolling();
|
|
522
|
-
}
|
|
338
|
+
// EOF/peer close: device is no longer readable, so tear down polling.
|
|
339
|
+
if (read_status == ReadPacketStatus::Closed) {
|
|
340
|
+
self->StopPolling();
|
|
523
341
|
return;
|
|
524
342
|
}
|
|
525
|
-
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
[buf = std::move(buffer), bytes_read](Napi::Env env, Napi::Function jsCallback) {
|
|
529
|
-
if (env == nullptr || jsCallback.IsEmpty()) return;
|
|
530
|
-
jsCallback.Call({ Napi::Buffer<uint8_t>::Copy(env, buf.data() + UTUN_HEADER_SIZE, bytes_read - UTUN_HEADER_SIZE) });
|
|
531
|
-
}
|
|
532
|
-
);
|
|
343
|
+
// Transient empty read (e.g. EAGAIN): keep poll active and wait for next event.
|
|
344
|
+
if (read_status == ReadPacketStatus::NoData) {
|
|
345
|
+
return;
|
|
533
346
|
}
|
|
534
|
-
}
|
|
535
347
|
|
|
536
|
-
|
|
537
|
-
|
|
348
|
+
self->tsfn_.BlockingCall(
|
|
349
|
+
[packet = std::move(packet)](Napi::Env env, Napi::Function jsCallback) {
|
|
350
|
+
if (env == nullptr || jsCallback.IsEmpty()) return;
|
|
351
|
+
jsCallback.Call({ Napi::Buffer<uint8_t>::Copy(env, packet.data(), packet.size()) });
|
|
352
|
+
}
|
|
353
|
+
);
|
|
538
354
|
}
|
|
539
|
-
|
|
540
|
-
NODE_API_MODULE(tuntap, Init)
|