appium-ios-tuntap 0.2.3 → 0.2.5
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 +12 -0
- package/binding.gyp +5 -3
- package/package.json +1 -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/posix_tun_backend.h +58 -0
- package/src/native/posix_uv_poll_loop.cc +137 -0
- package/src/native/posix_uv_poll_loop.h +55 -0
- package/src/native/tun_backend.h +33 -11
- package/src/native/tun_backend_darwin.cc +38 -12
- package/src/native/tun_backend_linux.cc +41 -15
- package/src/tuntap.cc +75 -142
- package/src/native/tun_backend_common.cc +0 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.2.5](https://github.com/appium/appium-ios-tuntap/compare/v0.2.4...v0.2.5) (2026-05-14)
|
|
2
|
+
|
|
3
|
+
### Code Refactoring
|
|
4
|
+
|
|
5
|
+
* backends own fd, polling, and lifecycle ([#42](https://github.com/appium/appium-ios-tuntap/issues/42)) ([f089944](https://github.com/appium/appium-ios-tuntap/commit/f08994477cb9909b597386d0893abbf9b6a3c137))
|
|
6
|
+
|
|
7
|
+
## [0.2.4](https://github.com/appium/appium-ios-tuntap/compare/v0.2.3...v0.2.4) (2026-05-13)
|
|
8
|
+
|
|
9
|
+
### Code Refactoring
|
|
10
|
+
|
|
11
|
+
* eliminate tun_backend_common.cc indirection ([#41](https://github.com/appium/appium-ios-tuntap/issues/41)) ([5fd387e](https://github.com/appium/appium-ios-tuntap/commit/5fd387e230fb772dbf280472dbadb43d131a4a2e))
|
|
12
|
+
|
|
1
13
|
## [0.2.3](https://github.com/appium/appium-ios-tuntap/compare/v0.2.2...v0.2.3) (2026-05-13)
|
|
2
14
|
|
|
3
15
|
## [0.2.2](https://github.com/appium/appium-ios-tuntap/compare/v0.2.1...v0.2.2) (2026-04-30)
|
package/binding.gyp
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
{
|
|
4
4
|
"target_name": "tuntap",
|
|
5
5
|
"sources": [
|
|
6
|
-
"src/tuntap.cc"
|
|
7
|
-
"src/native/file_descriptor.cc",
|
|
8
|
-
"src/native/tun_backend_common.cc"
|
|
6
|
+
"src/tuntap.cc"
|
|
9
7
|
],
|
|
10
8
|
"include_dirs": [
|
|
11
9
|
"<!@(node -p \"require('node-addon-api').include\")"
|
|
@@ -65,6 +63,8 @@
|
|
|
65
63
|
"conditions": [
|
|
66
64
|
["OS=='linux'", {
|
|
67
65
|
"sources": [
|
|
66
|
+
"src/native/file_descriptor.cc",
|
|
67
|
+
"src/native/posix_uv_poll_loop.cc",
|
|
68
68
|
"src/native/tun_backend_linux.cc"
|
|
69
69
|
],
|
|
70
70
|
"cflags": [
|
|
@@ -79,6 +79,8 @@
|
|
|
79
79
|
}],
|
|
80
80
|
["OS=='mac'", {
|
|
81
81
|
"sources": [
|
|
82
|
+
"src/native/file_descriptor.cc",
|
|
83
|
+
"src/native/posix_uv_poll_loop.cc",
|
|
82
84
|
"src/native/tun_backend_darwin.cc"
|
|
83
85
|
],
|
|
84
86
|
"xcode_settings": {
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#if defined(__APPLE__) || defined(__linux__)
|
|
4
|
+
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <utility>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
#include "file_descriptor.h"
|
|
10
|
+
#include "posix_uv_poll_loop.h"
|
|
11
|
+
#include "tun_backend.h"
|
|
12
|
+
|
|
13
|
+
// Shared base class for POSIX TUN backends (Darwin, Linux). Owns the file
|
|
14
|
+
// descriptor, the assigned interface name, and the libuv poll loop. Concrete
|
|
15
|
+
// subclasses implement only the platform-specific OpenDevice, ReadPacket, and
|
|
16
|
+
// WritePacket.
|
|
17
|
+
class PosixTunBackend : public TunPlatformBackend {
|
|
18
|
+
public:
|
|
19
|
+
void CloseDevice() override {
|
|
20
|
+
poll_loop_.Stop();
|
|
21
|
+
fd_.reset();
|
|
22
|
+
interface_name_.clear();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
bool IsOpen() const override { return fd_.is_valid(); }
|
|
26
|
+
|
|
27
|
+
bool StartReceiveLoop(uv_loop_t* loop,
|
|
28
|
+
size_t buffer_size,
|
|
29
|
+
PacketCallback on_packet,
|
|
30
|
+
ErrorCallback on_error,
|
|
31
|
+
std::string& error) override {
|
|
32
|
+
if (!fd_.is_valid()) {
|
|
33
|
+
error = "Device not open";
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return poll_loop_.Start(
|
|
37
|
+
loop,
|
|
38
|
+
fd_.get(),
|
|
39
|
+
buffer_size,
|
|
40
|
+
[this](size_t size, std::vector<uint8_t>& out, std::string& err) {
|
|
41
|
+
return ReadPacket(size, out, err);
|
|
42
|
+
},
|
|
43
|
+
std::move(on_packet),
|
|
44
|
+
std::move(on_error),
|
|
45
|
+
error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void StopReceiveLoop() override { poll_loop_.Stop(); }
|
|
49
|
+
|
|
50
|
+
int GetNativeFd() const override { return fd_.get(); }
|
|
51
|
+
|
|
52
|
+
protected:
|
|
53
|
+
FileDescriptor fd_;
|
|
54
|
+
std::string interface_name_;
|
|
55
|
+
PosixUvPollLoop poll_loop_;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
#endif
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#if defined(__APPLE__) || defined(__linux__)
|
|
2
|
+
|
|
3
|
+
#include "posix_uv_poll_loop.h"
|
|
4
|
+
|
|
5
|
+
#include <cstdio>
|
|
6
|
+
#include <errno.h>
|
|
7
|
+
#include <fcntl.h>
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <utility>
|
|
10
|
+
|
|
11
|
+
PosixUvPollLoop::~PosixUvPollLoop() {
|
|
12
|
+
Stop();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
bool PosixUvPollLoop::Start(uv_loop_t* loop,
|
|
16
|
+
int fd,
|
|
17
|
+
size_t buffer_size,
|
|
18
|
+
ReadFn read_fn,
|
|
19
|
+
TunPlatformBackend::PacketCallback on_packet,
|
|
20
|
+
TunPlatformBackend::ErrorCallback on_error,
|
|
21
|
+
std::string& error) {
|
|
22
|
+
if (handle_) {
|
|
23
|
+
error = "Receive loop already started";
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (!loop || fd < 0 || buffer_size == 0) {
|
|
27
|
+
error = "Invalid receive-loop parameters";
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
auto state = std::make_unique<State>();
|
|
32
|
+
state->buffer_size = buffer_size;
|
|
33
|
+
state->read_fn = std::move(read_fn);
|
|
34
|
+
state->on_packet = std::move(on_packet);
|
|
35
|
+
state->on_error = std::move(on_error);
|
|
36
|
+
state->owner = this;
|
|
37
|
+
|
|
38
|
+
auto handle = std::make_unique<uv_poll_t>();
|
|
39
|
+
if (uv_poll_init(loop, handle.get(), fd) != 0) {
|
|
40
|
+
error = "Failed to initialize poll handle";
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handle->data = state.get();
|
|
45
|
+
if (uv_poll_start(handle.get(), UV_READABLE, &PosixUvPollLoop::OnPoll) != 0) {
|
|
46
|
+
uv_close(reinterpret_cast<uv_handle_t*>(handle.release()),
|
|
47
|
+
[](uv_handle_t* h) { delete reinterpret_cast<uv_poll_t*>(h); });
|
|
48
|
+
error = "Failed to start polling";
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
state_ = std::move(state);
|
|
53
|
+
handle_ = handle.release();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void PosixUvPollLoop::Stop() {
|
|
58
|
+
if (!handle_) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
uv_poll_stop(handle_);
|
|
63
|
+
handle_->data = nullptr;
|
|
64
|
+
uv_close(reinterpret_cast<uv_handle_t*>(handle_),
|
|
65
|
+
&PosixUvPollLoop::OnHandleClosed);
|
|
66
|
+
handle_ = nullptr;
|
|
67
|
+
state_.reset();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void PosixUvPollLoop::OnPoll(uv_poll_t* handle, int status, int events) {
|
|
71
|
+
auto* state = static_cast<State*>(handle->data);
|
|
72
|
+
if (!state) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Tear down the loop and notify the owner of a terminal condition. The
|
|
77
|
+
// callback is copied locally first because Stop() destroys `state` (and the
|
|
78
|
+
// std::function it owns) synchronously.
|
|
79
|
+
auto handle_terminal = [&](const std::string& msg) {
|
|
80
|
+
auto cb = state->on_error;
|
|
81
|
+
PosixUvPollLoop* owner = state->owner;
|
|
82
|
+
if (owner) {
|
|
83
|
+
owner->Stop();
|
|
84
|
+
}
|
|
85
|
+
if (cb) {
|
|
86
|
+
cb(msg);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (status < 0) {
|
|
91
|
+
handle_terminal(std::string("Poll error: ") + uv_strerror(status));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!(events & UV_READABLE) || !state->read_fn) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
std::vector<uint8_t> packet;
|
|
100
|
+
std::string error;
|
|
101
|
+
ReadPacketStatus rs = state->read_fn(state->buffer_size, packet, error);
|
|
102
|
+
|
|
103
|
+
switch (rs) {
|
|
104
|
+
case ReadPacketStatus::Data:
|
|
105
|
+
if (state->on_packet) {
|
|
106
|
+
state->on_packet(std::move(packet));
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
case ReadPacketStatus::NoData:
|
|
110
|
+
return;
|
|
111
|
+
case ReadPacketStatus::Closed:
|
|
112
|
+
handle_terminal("Device closed");
|
|
113
|
+
return;
|
|
114
|
+
case ReadPacketStatus::Error:
|
|
115
|
+
handle_terminal(error);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void PosixUvPollLoop::OnHandleClosed(uv_handle_t* handle) {
|
|
121
|
+
delete reinterpret_cast<uv_poll_t*>(handle);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
bool SetNonBlocking(int fd, std::string& error) {
|
|
125
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
|
126
|
+
if (flags < 0) {
|
|
127
|
+
error = std::string("Failed to get file descriptor flags: ") + strerror(errno);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
131
|
+
error = std::string("Failed to set non-blocking mode: ") + strerror(errno);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#endif
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#if defined(__APPLE__) || defined(__linux__)
|
|
4
|
+
|
|
5
|
+
#include <cstddef>
|
|
6
|
+
#include <functional>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include <uv.h>
|
|
12
|
+
|
|
13
|
+
#include "tun_backend.h"
|
|
14
|
+
|
|
15
|
+
class PosixUvPollLoop {
|
|
16
|
+
public:
|
|
17
|
+
using ReadFn = std::function<ReadPacketStatus(size_t,
|
|
18
|
+
std::vector<uint8_t>&,
|
|
19
|
+
std::string&)>;
|
|
20
|
+
|
|
21
|
+
PosixUvPollLoop() = default;
|
|
22
|
+
~PosixUvPollLoop();
|
|
23
|
+
|
|
24
|
+
PosixUvPollLoop(const PosixUvPollLoop&) = delete;
|
|
25
|
+
PosixUvPollLoop& operator=(const PosixUvPollLoop&) = delete;
|
|
26
|
+
|
|
27
|
+
bool Start(uv_loop_t* loop,
|
|
28
|
+
int fd,
|
|
29
|
+
size_t buffer_size,
|
|
30
|
+
ReadFn read_fn,
|
|
31
|
+
TunPlatformBackend::PacketCallback on_packet,
|
|
32
|
+
TunPlatformBackend::ErrorCallback on_error,
|
|
33
|
+
std::string& error);
|
|
34
|
+
|
|
35
|
+
void Stop();
|
|
36
|
+
|
|
37
|
+
private:
|
|
38
|
+
struct State {
|
|
39
|
+
size_t buffer_size = 0;
|
|
40
|
+
ReadFn read_fn;
|
|
41
|
+
TunPlatformBackend::PacketCallback on_packet;
|
|
42
|
+
TunPlatformBackend::ErrorCallback on_error;
|
|
43
|
+
PosixUvPollLoop* owner = nullptr;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
static void OnPoll(uv_poll_t* handle, int status, int events);
|
|
47
|
+
static void OnHandleClosed(uv_handle_t* handle);
|
|
48
|
+
|
|
49
|
+
uv_poll_t* handle_ = nullptr;
|
|
50
|
+
std::unique_ptr<State> state_;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
bool SetNonBlocking(int fd, std::string& error);
|
|
54
|
+
|
|
55
|
+
#endif
|
package/src/native/tun_backend.h
CHANGED
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
#include <cstddef>
|
|
8
8
|
#include <cstdint>
|
|
9
|
+
#include <functional>
|
|
9
10
|
#include <memory>
|
|
10
11
|
#include <string>
|
|
11
|
-
#include <sys/types.h>
|
|
12
12
|
#include <vector>
|
|
13
13
|
|
|
14
|
-
#
|
|
14
|
+
#ifdef _WIN32
|
|
15
|
+
#include <BaseTsd.h>
|
|
16
|
+
using ssize_t = SSIZE_T;
|
|
17
|
+
#else
|
|
18
|
+
#include <sys/types.h>
|
|
19
|
+
#endif
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
FileDescriptor fd;
|
|
18
|
-
std::string interface_name;
|
|
19
|
-
};
|
|
21
|
+
#include <uv.h>
|
|
20
22
|
|
|
21
23
|
enum class ReadPacketStatus {
|
|
22
24
|
Data,
|
|
@@ -27,12 +29,32 @@ enum class ReadPacketStatus {
|
|
|
27
29
|
|
|
28
30
|
class TunPlatformBackend {
|
|
29
31
|
public:
|
|
32
|
+
using PacketCallback = std::function<void(std::vector<uint8_t>)>;
|
|
33
|
+
using ErrorCallback = std::function<void(const std::string&)>;
|
|
34
|
+
|
|
30
35
|
virtual ~TunPlatformBackend() = default;
|
|
31
|
-
|
|
32
|
-
virtual
|
|
33
|
-
|
|
36
|
+
|
|
37
|
+
virtual bool OpenDevice(const std::string& requested_name,
|
|
38
|
+
std::string& out_interface_name,
|
|
39
|
+
std::string& error) = 0;
|
|
40
|
+
virtual void CloseDevice() = 0;
|
|
41
|
+
virtual bool IsOpen() const = 0;
|
|
42
|
+
|
|
43
|
+
virtual ReadPacketStatus ReadPacket(size_t max_payload_size,
|
|
44
|
+
std::vector<uint8_t>& out,
|
|
45
|
+
std::string& error) = 0;
|
|
46
|
+
virtual ssize_t WritePacket(const uint8_t* data,
|
|
47
|
+
size_t length,
|
|
48
|
+
std::string& error) = 0;
|
|
49
|
+
|
|
50
|
+
virtual bool StartReceiveLoop(uv_loop_t* loop,
|
|
51
|
+
size_t buffer_size,
|
|
52
|
+
PacketCallback on_packet,
|
|
53
|
+
ErrorCallback on_error,
|
|
54
|
+
std::string& error) = 0;
|
|
55
|
+
virtual void StopReceiveLoop() = 0;
|
|
56
|
+
|
|
57
|
+
virtual int GetNativeFd() const { return -1; }
|
|
34
58
|
};
|
|
35
59
|
|
|
36
|
-
bool SetNonBlocking(int fd, std::string& error);
|
|
37
60
|
std::unique_ptr<TunPlatformBackend> CreatePlatformBackend();
|
|
38
|
-
|
|
@@ -14,15 +14,23 @@
|
|
|
14
14
|
#include <netinet/in.h>
|
|
15
15
|
#include <netinet6/in6_var.h>
|
|
16
16
|
|
|
17
|
+
#include <utility>
|
|
18
|
+
|
|
19
|
+
#include "file_descriptor.h"
|
|
20
|
+
#include "posix_tun_backend.h"
|
|
21
|
+
#include "posix_uv_poll_loop.h"
|
|
22
|
+
|
|
17
23
|
#define UTUN_CONTROL_NAME "com.apple.net.utun_control"
|
|
18
24
|
|
|
19
25
|
namespace {
|
|
20
26
|
|
|
21
|
-
class DarwinTunBackend : public
|
|
27
|
+
class DarwinTunBackend : public PosixTunBackend {
|
|
22
28
|
public:
|
|
23
29
|
static constexpr size_t kUtunHeaderSize = 4;
|
|
24
30
|
|
|
25
|
-
bool OpenDevice(const std::string& requested_name,
|
|
31
|
+
bool OpenDevice(const std::string& requested_name,
|
|
32
|
+
std::string& out_interface_name,
|
|
33
|
+
std::string& error) override {
|
|
26
34
|
struct ctl_info ctl_info;
|
|
27
35
|
struct sockaddr_ctl socket_addr;
|
|
28
36
|
|
|
@@ -65,14 +73,26 @@ public:
|
|
|
65
73
|
return false;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
if (!SetNonBlocking(temp_fd.get(), error)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fd_ = std::move(temp_fd);
|
|
81
|
+
interface_name_ = std::string(interface_name);
|
|
82
|
+
out_interface_name = interface_name_;
|
|
70
83
|
return true;
|
|
71
84
|
}
|
|
72
85
|
|
|
73
|
-
ReadPacketStatus ReadPacket(
|
|
86
|
+
ReadPacketStatus ReadPacket(size_t max_payload_size,
|
|
87
|
+
std::vector<uint8_t>& out,
|
|
88
|
+
std::string& error) override {
|
|
89
|
+
if (!fd_.is_valid()) {
|
|
90
|
+
error = "Device not open";
|
|
91
|
+
return ReadPacketStatus::Error;
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
out.resize(max_payload_size + kUtunHeaderSize);
|
|
75
|
-
ssize_t bytes_read = read(
|
|
95
|
+
ssize_t bytes_read = read(fd_.get(), out.data(), out.size());
|
|
76
96
|
if (bytes_read < 0) {
|
|
77
97
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
78
98
|
out.clear();
|
|
@@ -97,21 +117,28 @@ public:
|
|
|
97
117
|
return ReadPacketStatus::Data;
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
ssize_t WritePacket(
|
|
120
|
+
ssize_t WritePacket(const uint8_t* data,
|
|
121
|
+
size_t length,
|
|
122
|
+
std::string& error) override {
|
|
123
|
+
if (!fd_.is_valid()) {
|
|
124
|
+
error = "Device not open";
|
|
125
|
+
return -1;
|
|
126
|
+
}
|
|
127
|
+
|
|
101
128
|
std::vector<uint8_t> frame(length + kUtunHeaderSize);
|
|
102
129
|
uint32_t family = htonl(AF_INET6);
|
|
103
130
|
memcpy(frame.data(), &family, kUtunHeaderSize);
|
|
104
131
|
memcpy(frame.data() + kUtunHeaderSize, data, length);
|
|
105
132
|
|
|
106
|
-
ssize_t bytes_written = write(
|
|
133
|
+
ssize_t bytes_written = write(fd_.get(), frame.data(), frame.size());
|
|
107
134
|
if (bytes_written < 0) {
|
|
108
135
|
error = std::string("Write error: ") + strerror(errno);
|
|
109
136
|
return -1;
|
|
110
137
|
}
|
|
111
138
|
|
|
112
139
|
return bytes_written > static_cast<ssize_t>(kUtunHeaderSize)
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
? bytes_written - static_cast<ssize_t>(kUtunHeaderSize)
|
|
141
|
+
: 0;
|
|
115
142
|
}
|
|
116
143
|
|
|
117
144
|
private:
|
|
@@ -144,9 +171,8 @@ private:
|
|
|
144
171
|
|
|
145
172
|
} // namespace
|
|
146
173
|
|
|
147
|
-
std::unique_ptr<TunPlatformBackend>
|
|
174
|
+
std::unique_ptr<TunPlatformBackend> CreatePlatformBackend() {
|
|
148
175
|
return std::make_unique<DarwinTunBackend>();
|
|
149
176
|
}
|
|
150
177
|
|
|
151
178
|
#endif
|
|
152
|
-
|
|
@@ -12,26 +12,35 @@
|
|
|
12
12
|
#include <linux/if.h>
|
|
13
13
|
#include <linux/if_tun.h>
|
|
14
14
|
|
|
15
|
+
#include <utility>
|
|
16
|
+
|
|
17
|
+
#include "file_descriptor.h"
|
|
18
|
+
#include "posix_tun_backend.h"
|
|
19
|
+
#include "posix_uv_poll_loop.h"
|
|
20
|
+
|
|
15
21
|
namespace {
|
|
22
|
+
|
|
16
23
|
constexpr const char* kTunDevicePath = "/dev/net/tun";
|
|
17
24
|
|
|
18
|
-
class LinuxTunBackend : public
|
|
25
|
+
class LinuxTunBackend : public PosixTunBackend {
|
|
19
26
|
public:
|
|
20
|
-
bool OpenDevice(const std::string& requested_name,
|
|
27
|
+
bool OpenDevice(const std::string& requested_name,
|
|
28
|
+
std::string& out_interface_name,
|
|
29
|
+
std::string& error) override {
|
|
21
30
|
struct stat statbuf;
|
|
22
31
|
if (stat(kTunDevicePath, &statbuf) != 0) {
|
|
23
32
|
error =
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
"TUN/TAP device not available: /dev/net/tun does not exist. "
|
|
34
|
+
"Please ensure the TUN/TAP kernel module is loaded (modprobe tun).";
|
|
26
35
|
return false;
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
FileDescriptor temp_fd(open(kTunDevicePath, O_RDWR));
|
|
30
39
|
if (!temp_fd.is_valid()) {
|
|
31
40
|
error =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
std::string("Failed to open ") + kTunDevicePath + ": " + strerror(errno) +
|
|
42
|
+
". This usually means you don't have sufficient permissions. "
|
|
43
|
+
"Try running with sudo or add your user to the 'tun' group.";
|
|
35
44
|
return false;
|
|
36
45
|
}
|
|
37
46
|
|
|
@@ -49,14 +58,26 @@ public:
|
|
|
49
58
|
return false;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
if (!SetNonBlocking(temp_fd.get(), error)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fd_ = std::move(temp_fd);
|
|
66
|
+
interface_name_ = std::string(ifr.ifr_name);
|
|
67
|
+
out_interface_name = interface_name_;
|
|
54
68
|
return true;
|
|
55
69
|
}
|
|
56
70
|
|
|
57
|
-
ReadPacketStatus ReadPacket(
|
|
71
|
+
ReadPacketStatus ReadPacket(size_t max_payload_size,
|
|
72
|
+
std::vector<uint8_t>& out,
|
|
73
|
+
std::string& error) override {
|
|
74
|
+
if (!fd_.is_valid()) {
|
|
75
|
+
error = "Device not open";
|
|
76
|
+
return ReadPacketStatus::Error;
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
out.resize(max_payload_size);
|
|
59
|
-
ssize_t bytes_read = read(
|
|
80
|
+
ssize_t bytes_read = read(fd_.get(), out.data(), out.size());
|
|
60
81
|
if (bytes_read < 0) {
|
|
61
82
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
62
83
|
out.clear();
|
|
@@ -74,8 +95,14 @@ public:
|
|
|
74
95
|
return ReadPacketStatus::Data;
|
|
75
96
|
}
|
|
76
97
|
|
|
77
|
-
ssize_t WritePacket(
|
|
78
|
-
|
|
98
|
+
ssize_t WritePacket(const uint8_t* data,
|
|
99
|
+
size_t length,
|
|
100
|
+
std::string& error) override {
|
|
101
|
+
if (!fd_.is_valid()) {
|
|
102
|
+
error = "Device not open";
|
|
103
|
+
return -1;
|
|
104
|
+
}
|
|
105
|
+
ssize_t bytes_written = write(fd_.get(), data, length);
|
|
79
106
|
if (bytes_written < 0) {
|
|
80
107
|
error = std::string("Write error: ") + strerror(errno);
|
|
81
108
|
return -1;
|
|
@@ -86,9 +113,8 @@ public:
|
|
|
86
113
|
|
|
87
114
|
} // namespace
|
|
88
115
|
|
|
89
|
-
std::unique_ptr<TunPlatformBackend>
|
|
116
|
+
std::unique_ptr<TunPlatformBackend> CreatePlatformBackend() {
|
|
90
117
|
return std::make_unique<LinuxTunBackend>();
|
|
91
118
|
}
|
|
92
119
|
|
|
93
120
|
#endif
|
|
94
|
-
|
package/src/tuntap.cc
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#include <napi.h>
|
|
2
|
-
#include <uv.h>
|
|
3
2
|
|
|
4
|
-
#include <string>
|
|
5
|
-
#include <vector>
|
|
6
|
-
#include <memory>
|
|
7
|
-
#include <mutex>
|
|
8
3
|
#include <atomic>
|
|
9
4
|
#include <cstdio>
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <utility>
|
|
9
|
+
#include <vector>
|
|
10
10
|
|
|
11
|
-
#include "native/file_descriptor.h"
|
|
12
11
|
#include "native/tun_backend.h"
|
|
13
12
|
|
|
14
13
|
class TunDevice : public Napi::ObjectWrap<TunDevice> {
|
|
@@ -30,24 +29,22 @@ private:
|
|
|
30
29
|
Napi::Value GetFd(const Napi::CallbackInfo& info);
|
|
31
30
|
Napi::Value StartPolling(const Napi::CallbackInfo& info);
|
|
32
31
|
|
|
33
|
-
FileDescriptor fd_;
|
|
34
|
-
std::string name_;
|
|
35
32
|
std::unique_ptr<TunPlatformBackend> backend_;
|
|
33
|
+
std::string requested_name_;
|
|
34
|
+
std::string interface_name_;
|
|
36
35
|
std::atomic<bool> is_open_;
|
|
37
36
|
std::mutex device_mutex_;
|
|
38
37
|
|
|
39
|
-
uv_poll_t* poll_handle_ = nullptr;
|
|
40
38
|
Napi::ThreadSafeFunction tsfn_;
|
|
39
|
+
std::atomic<bool> polling_;
|
|
41
40
|
static constexpr size_t MAX_POLL_BUFFER = 65535;
|
|
42
|
-
size_t poll_buffer_size_ = MAX_POLL_BUFFER;
|
|
43
41
|
|
|
44
|
-
void
|
|
45
|
-
|
|
42
|
+
void StopPollingLocked();
|
|
43
|
+
void ReleaseTsfnLocked();
|
|
46
44
|
};
|
|
47
45
|
|
|
48
46
|
Napi::FunctionReference TunDevice::constructor;
|
|
49
47
|
|
|
50
|
-
// Defines and exports the JS class constructor: new TunDevice(name?)
|
|
51
48
|
Napi::Object TunDevice::Init(Napi::Env env, Napi::Object exports) {
|
|
52
49
|
Napi::HandleScope scope(env);
|
|
53
50
|
|
|
@@ -68,25 +65,24 @@ Napi::Object TunDevice::Init(Napi::Env env, Napi::Object exports) {
|
|
|
68
65
|
return exports;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
// Creates a TunDevice wrapper; optional first arg is requested interface name.
|
|
72
68
|
TunDevice::TunDevice(const Napi::CallbackInfo& info)
|
|
73
|
-
|
|
69
|
+
: Napi::ObjectWrap<TunDevice>(info),
|
|
70
|
+
backend_(CreatePlatformBackend()),
|
|
71
|
+
is_open_(false),
|
|
72
|
+
polling_(false) {
|
|
74
73
|
Napi::Env env = info.Env();
|
|
75
74
|
Napi::HandleScope scope(env);
|
|
76
75
|
|
|
77
76
|
if (info.Length() > 0 && info[0].IsString()) {
|
|
78
|
-
|
|
77
|
+
requested_name_ = info[0].As<Napi::String>().Utf8Value();
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
// Ensures fd/poll resources are closed when object is destroyed.
|
|
83
81
|
TunDevice::~TunDevice() {
|
|
84
82
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
85
83
|
CloseInternal();
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
// JS: open() -> boolean
|
|
89
|
-
// Opens the backend device and configures the fd as non-blocking.
|
|
90
86
|
Napi::Value TunDevice::Open(const Napi::CallbackInfo& info) {
|
|
91
87
|
Napi::Env env = info.Env();
|
|
92
88
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -100,27 +96,18 @@ Napi::Value TunDevice::Open(const Napi::CallbackInfo& info) {
|
|
|
100
96
|
return Napi::Boolean::New(env, false);
|
|
101
97
|
}
|
|
102
98
|
|
|
103
|
-
OpenResult result;
|
|
104
99
|
std::string error;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return Napi::Boolean::New(env, false);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!SetNonBlocking(result.fd.get(), error)) {
|
|
100
|
+
std::string assigned_name;
|
|
101
|
+
if (!backend_->OpenDevice(requested_name_, assigned_name, error)) {
|
|
111
102
|
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
112
103
|
return Napi::Boolean::New(env, false);
|
|
113
104
|
}
|
|
114
105
|
|
|
115
|
-
|
|
116
|
-
name_ = result.interface_name;
|
|
106
|
+
interface_name_ = std::move(assigned_name);
|
|
117
107
|
is_open_ = true;
|
|
118
|
-
|
|
119
108
|
return Napi::Boolean::New(env, true);
|
|
120
109
|
}
|
|
121
110
|
|
|
122
|
-
// JS: close() -> boolean
|
|
123
|
-
// Safely closes device resources; calling multiple times is allowed.
|
|
124
111
|
Napi::Value TunDevice::Close(const Napi::CallbackInfo& info) {
|
|
125
112
|
Napi::Env env = info.Env();
|
|
126
113
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -128,13 +115,11 @@ Napi::Value TunDevice::Close(const Napi::CallbackInfo& info) {
|
|
|
128
115
|
return Napi::Boolean::New(env, true);
|
|
129
116
|
}
|
|
130
117
|
|
|
131
|
-
// JS: read(bufferSize?) -> Buffer
|
|
132
|
-
// Reads one payload packet, or returns an empty Buffer when no data is available.
|
|
133
118
|
Napi::Value TunDevice::Read(const Napi::CallbackInfo& info) {
|
|
134
119
|
Napi::Env env = info.Env();
|
|
135
120
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
136
121
|
|
|
137
|
-
if (!is_open_ || !
|
|
122
|
+
if (!is_open_ || !backend_ || !backend_->IsOpen()) {
|
|
138
123
|
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
139
124
|
return env.Null();
|
|
140
125
|
}
|
|
@@ -150,25 +135,22 @@ Napi::Value TunDevice::Read(const Napi::CallbackInfo& info) {
|
|
|
150
135
|
|
|
151
136
|
std::vector<uint8_t> packet;
|
|
152
137
|
std::string error;
|
|
153
|
-
ReadPacketStatus
|
|
154
|
-
if (
|
|
138
|
+
ReadPacketStatus rs = backend_->ReadPacket(buffer_size, packet, error);
|
|
139
|
+
if (rs == ReadPacketStatus::Error) {
|
|
155
140
|
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
156
141
|
return env.Null();
|
|
157
142
|
}
|
|
158
|
-
if (
|
|
143
|
+
if (rs == ReadPacketStatus::NoData || rs == ReadPacketStatus::Closed) {
|
|
159
144
|
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
160
145
|
}
|
|
161
|
-
|
|
162
146
|
return Napi::Buffer<uint8_t>::Copy(env, packet.data(), packet.size());
|
|
163
147
|
}
|
|
164
148
|
|
|
165
|
-
// JS: write(buffer) -> number
|
|
166
|
-
// Writes one packet and returns payload bytes accepted by the backend.
|
|
167
149
|
Napi::Value TunDevice::Write(const Napi::CallbackInfo& info) {
|
|
168
150
|
Napi::Env env = info.Env();
|
|
169
151
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
170
152
|
|
|
171
|
-
if (!is_open_ || !
|
|
153
|
+
if (!is_open_ || !backend_ || !backend_->IsOpen()) {
|
|
172
154
|
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
173
155
|
return Napi::Number::New(env, -1);
|
|
174
156
|
}
|
|
@@ -183,35 +165,29 @@ Napi::Value TunDevice::Write(const Napi::CallbackInfo& info) {
|
|
|
183
165
|
size_t length = buffer.Length();
|
|
184
166
|
|
|
185
167
|
std::string error;
|
|
186
|
-
ssize_t bytes_written = backend_->WritePacket(
|
|
168
|
+
ssize_t bytes_written = backend_->WritePacket(data, length, error);
|
|
187
169
|
if (bytes_written < 0) {
|
|
188
170
|
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
189
171
|
return Napi::Number::New(env, -1);
|
|
190
172
|
}
|
|
191
|
-
return Napi::Number::New(env, bytes_written);
|
|
173
|
+
return Napi::Number::New(env, static_cast<double>(bytes_written));
|
|
192
174
|
}
|
|
193
175
|
|
|
194
|
-
// JS: getName() -> string
|
|
195
|
-
// Returns the assigned interface name after open().
|
|
196
176
|
Napi::Value TunDevice::GetName(const Napi::CallbackInfo& info) {
|
|
197
177
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
198
|
-
return Napi::String::New(info.Env(),
|
|
178
|
+
return Napi::String::New(info.Env(), interface_name_);
|
|
199
179
|
}
|
|
200
180
|
|
|
201
|
-
// JS: getFd() -> number
|
|
202
|
-
// Returns the native file descriptor, or -1 before open()/after close().
|
|
203
181
|
Napi::Value TunDevice::GetFd(const Napi::CallbackInfo& info) {
|
|
204
182
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
205
|
-
return Napi::Number::New(info.Env(),
|
|
183
|
+
return Napi::Number::New(info.Env(), backend_ ? backend_->GetNativeFd() : -1);
|
|
206
184
|
}
|
|
207
185
|
|
|
208
|
-
// JS: startPolling(callback, bufferSize?) -> void
|
|
209
|
-
// Starts libuv polling and invokes callback with packet payload Buffers.
|
|
210
186
|
Napi::Value TunDevice::StartPolling(const Napi::CallbackInfo& info) {
|
|
211
187
|
Napi::Env env = info.Env();
|
|
212
188
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
213
189
|
|
|
214
|
-
if (!is_open_ || !
|
|
190
|
+
if (!is_open_ || !backend_ || !backend_->IsOpen()) {
|
|
215
191
|
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
216
192
|
return env.Null();
|
|
217
193
|
}
|
|
@@ -221,134 +197,91 @@ Napi::Value TunDevice::StartPolling(const Napi::CallbackInfo& info) {
|
|
|
221
197
|
return env.Null();
|
|
222
198
|
}
|
|
223
199
|
|
|
224
|
-
|
|
200
|
+
StopPollingLocked();
|
|
225
201
|
|
|
226
|
-
|
|
227
|
-
poll_buffer_size_ = MAX_POLL_BUFFER;
|
|
202
|
+
size_t buffer_size = MAX_POLL_BUFFER;
|
|
228
203
|
if (info.Length() > 1 && info[1].IsNumber()) {
|
|
229
204
|
auto size = info[1].As<Napi::Number>().Uint32Value();
|
|
230
205
|
if (size == 0 || size > MAX_POLL_BUFFER) {
|
|
231
206
|
Napi::RangeError::New(env, "Buffer size must be between 1 and " + std::to_string(MAX_POLL_BUFFER)).ThrowAsJavaScriptException();
|
|
232
207
|
return env.Null();
|
|
233
208
|
}
|
|
234
|
-
|
|
209
|
+
buffer_size = size;
|
|
235
210
|
}
|
|
236
211
|
|
|
237
212
|
tsfn_ = Napi::ThreadSafeFunction::New(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
);
|
|
213
|
+
env,
|
|
214
|
+
info[0].As<Napi::Function>(),
|
|
215
|
+
"TunDeviceDataCallback",
|
|
216
|
+
0,
|
|
217
|
+
1);
|
|
244
218
|
|
|
245
219
|
uv_loop_t* loop = nullptr;
|
|
246
220
|
napi_status napi_st = napi_get_uv_event_loop(env, &loop);
|
|
247
221
|
if (napi_st != napi_ok || loop == nullptr) {
|
|
248
|
-
|
|
249
|
-
tsfn_ = nullptr;
|
|
222
|
+
ReleaseTsfnLocked();
|
|
250
223
|
Napi::Error::New(env, "Failed to acquire event loop").ThrowAsJavaScriptException();
|
|
251
224
|
return env.Null();
|
|
252
225
|
}
|
|
253
226
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
227
|
+
Napi::ThreadSafeFunction tsfn = tsfn_;
|
|
228
|
+
auto packet_cb = [tsfn](std::vector<uint8_t> packet) mutable {
|
|
229
|
+
tsfn.BlockingCall(
|
|
230
|
+
[packet = std::move(packet)](Napi::Env env, Napi::Function jsCallback) {
|
|
231
|
+
if (env == nullptr || jsCallback.IsEmpty()) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
jsCallback.Call({Napi::Buffer<uint8_t>::Copy(env, packet.data(), packet.size())});
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
// Terminal errors from the receive loop (poll error, device closed, read
|
|
238
|
+
// error) call back here so the JS-side polling_ flag and TSFN are released
|
|
239
|
+
// promptly. Callback runs on the libuv thread, which only fires between JS
|
|
240
|
+
// ticks, so acquiring `device_mutex_` is safe.
|
|
241
|
+
auto error_cb = [this](const std::string& message) {
|
|
242
|
+
fprintf(stderr, "tuntap receive loop error: %s\n", message.c_str());
|
|
243
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
244
|
+
polling_ = false;
|
|
245
|
+
ReleaseTsfnLocked();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
std::string start_error;
|
|
249
|
+
if (!backend_->StartReceiveLoop(loop, buffer_size, std::move(packet_cb), std::move(error_cb), start_error)) {
|
|
250
|
+
ReleaseTsfnLocked();
|
|
251
|
+
Napi::Error::New(env, start_error).ThrowAsJavaScriptException();
|
|
259
252
|
return env.Null();
|
|
260
253
|
}
|
|
261
254
|
|
|
262
|
-
|
|
263
|
-
if (uv_poll_start(handle.get(), UV_READABLE, PollCallback) != 0) {
|
|
264
|
-
// Properly close the initialized-but-not-started handle
|
|
265
|
-
uv_close(reinterpret_cast<uv_handle_t*>(handle.release()), [](uv_handle_t* h) {
|
|
266
|
-
delete reinterpret_cast<uv_poll_t*>(h);
|
|
267
|
-
});
|
|
268
|
-
tsfn_.Release();
|
|
269
|
-
tsfn_ = nullptr;
|
|
270
|
-
Napi::Error::New(env, "Failed to start polling").ThrowAsJavaScriptException();
|
|
271
|
-
return env.Null();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
poll_handle_ = handle.release();
|
|
255
|
+
polling_ = true;
|
|
275
256
|
return env.Undefined();
|
|
276
257
|
}
|
|
277
258
|
|
|
278
|
-
// Node-API module entrypoint.
|
|
279
259
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
280
260
|
return TunDevice::Init(env, exports);
|
|
281
261
|
}
|
|
282
262
|
|
|
283
263
|
NODE_API_MODULE(tuntap, Init)
|
|
284
|
-
// #endregion
|
|
285
|
-
|
|
286
|
-
// #region Private implementation details
|
|
287
264
|
|
|
288
265
|
void TunDevice::CloseInternal() {
|
|
289
266
|
if (is_open_.exchange(false)) {
|
|
290
|
-
|
|
291
|
-
|
|
267
|
+
StopPollingLocked();
|
|
268
|
+
if (backend_) {
|
|
269
|
+
backend_->CloseDevice();
|
|
270
|
+
}
|
|
271
|
+
interface_name_.clear();
|
|
292
272
|
}
|
|
293
273
|
}
|
|
294
274
|
|
|
295
|
-
void TunDevice::
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
// Must use uv_close before freeing a libuv handle
|
|
299
|
-
uv_close(reinterpret_cast<uv_handle_t*>(poll_handle_), [](uv_handle_t* handle) {
|
|
300
|
-
delete reinterpret_cast<uv_poll_t*>(handle);
|
|
301
|
-
});
|
|
302
|
-
poll_handle_ = nullptr;
|
|
275
|
+
void TunDevice::StopPollingLocked() {
|
|
276
|
+
if (polling_.exchange(false) && backend_) {
|
|
277
|
+
backend_->StopReceiveLoop();
|
|
303
278
|
}
|
|
279
|
+
ReleaseTsfnLocked();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
void TunDevice::ReleaseTsfnLocked() {
|
|
304
283
|
if (tsfn_) {
|
|
305
284
|
tsfn_.Release();
|
|
306
285
|
tsfn_ = nullptr;
|
|
307
286
|
}
|
|
308
287
|
}
|
|
309
|
-
|
|
310
|
-
void TunDevice::PollCallback(uv_poll_t* handle, int status, int events) {
|
|
311
|
-
if (status < 0) {
|
|
312
|
-
fprintf(stderr, "tuntap poll error: %s\n", uv_strerror(status));
|
|
313
|
-
auto* self = static_cast<TunDevice*>(handle->data);
|
|
314
|
-
if (self) {
|
|
315
|
-
self->StopPolling();
|
|
316
|
-
}
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (!(events & UV_READABLE)) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
auto* self = static_cast<TunDevice*>(handle->data);
|
|
325
|
-
if (!self || !self->is_open_.load() || !self->fd_.is_valid()) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
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());
|
|
335
|
-
self->StopPolling();
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
// EOF/peer close: device is no longer readable, so tear down polling.
|
|
339
|
-
if (read_status == ReadPacketStatus::Closed) {
|
|
340
|
-
self->StopPolling();
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
// Transient empty read (e.g. EAGAIN): keep poll active and wait for next event.
|
|
344
|
-
if (read_status == ReadPacketStatus::NoData) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
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
|
-
);
|
|
354
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
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
|
-
|