@lakutata/netif 0.0.0-stage → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +299 -2
  3. package/README.zh-CN.md +229 -0
  4. package/binding.gyp +43 -0
  5. package/dist/cjs/exceptions/NetIfException.d.ts +17 -0
  6. package/dist/cjs/exceptions/NetIfException.js +24 -0
  7. package/dist/cjs/exceptions/NetIfNotEnforcedException.d.ts +12 -0
  8. package/dist/cjs/exceptions/NetIfNotEnforcedException.js +19 -0
  9. package/dist/cjs/exceptions/NetIfNotFoundException.d.ts +12 -0
  10. package/dist/cjs/exceptions/NetIfNotFoundException.js +19 -0
  11. package/dist/cjs/exceptions/NetIfNotSupportedException.d.ts +12 -0
  12. package/dist/cjs/exceptions/NetIfNotSupportedException.js +19 -0
  13. package/dist/cjs/exceptions/NetIfTimeoutException.d.ts +11 -0
  14. package/dist/cjs/exceptions/NetIfTimeoutException.js +18 -0
  15. package/dist/cjs/exports/Netif.d.ts +11 -0
  16. package/dist/cjs/exports/Netif.js +26 -0
  17. package/dist/cjs/lib/Addon.d.ts +5 -0
  18. package/dist/cjs/lib/Addon.js +9 -0
  19. package/dist/cjs/lib/Attachment.d.ts +152 -0
  20. package/dist/cjs/lib/Attachment.js +415 -0
  21. package/dist/cjs/lib/Enforcement.d.ts +22 -0
  22. package/dist/cjs/lib/Enforcement.js +45 -0
  23. package/dist/cjs/lib/Functions.d.ts +66 -0
  24. package/dist/cjs/lib/Functions.js +97 -0
  25. package/dist/cjs/lib/InterfaceListener.d.ts +92 -0
  26. package/dist/cjs/lib/InterfaceListener.js +99 -0
  27. package/dist/cjs/lib/InterfaceUDPSocket.d.ts +119 -0
  28. package/dist/cjs/lib/InterfaceUDPSocket.js +245 -0
  29. package/dist/cjs/lib/Internal.d.ts +10 -0
  30. package/dist/cjs/lib/Internal.js +9 -0
  31. package/dist/cjs/lib/Native.d.ts +54 -0
  32. package/dist/cjs/lib/Native.js +83 -0
  33. package/dist/cjs/lib/NetworkInterface.d.ts +313 -0
  34. package/dist/cjs/lib/NetworkInterface.js +391 -0
  35. package/dist/cjs/lib/Registry.d.ts +26 -0
  36. package/dist/cjs/lib/Registry.js +103 -0
  37. package/dist/cjs/lib/Snapshot.d.ts +17 -0
  38. package/dist/cjs/lib/Snapshot.js +84 -0
  39. package/dist/cjs/lib/Types.d.ts +63 -0
  40. package/dist/cjs/lib/Types.js +2 -0
  41. package/dist/cjs/package.json +1 -0
  42. package/dist/esm/exceptions/NetIfException.js +20 -0
  43. package/dist/esm/exceptions/NetIfNotEnforcedException.js +15 -0
  44. package/dist/esm/exceptions/NetIfNotFoundException.js +15 -0
  45. package/dist/esm/exceptions/NetIfNotSupportedException.js +15 -0
  46. package/dist/esm/exceptions/NetIfTimeoutException.js +14 -0
  47. package/dist/esm/exports/Netif.js +9 -0
  48. package/dist/esm/lib/Addon.js +10 -0
  49. package/dist/esm/lib/Attachment.js +410 -0
  50. package/dist/esm/lib/Enforcement.js +41 -0
  51. package/dist/esm/lib/Functions.js +92 -0
  52. package/dist/esm/lib/InterfaceListener.js +95 -0
  53. package/dist/esm/lib/InterfaceUDPSocket.js +241 -0
  54. package/dist/esm/lib/Internal.js +6 -0
  55. package/dist/esm/lib/Native.js +75 -0
  56. package/dist/esm/lib/NetworkInterface.js +386 -0
  57. package/dist/esm/lib/Registry.js +100 -0
  58. package/dist/esm/lib/Snapshot.js +79 -0
  59. package/dist/esm/lib/Types.js +1 -0
  60. package/dist/types/exceptions/NetIfException.d.ts +17 -0
  61. package/dist/types/exceptions/NetIfNotEnforcedException.d.ts +12 -0
  62. package/dist/types/exceptions/NetIfNotFoundException.d.ts +12 -0
  63. package/dist/types/exceptions/NetIfNotSupportedException.d.ts +12 -0
  64. package/dist/types/exceptions/NetIfTimeoutException.d.ts +11 -0
  65. package/dist/types/exports/Netif.d.ts +11 -0
  66. package/dist/types/lib/Addon.d.ts +5 -0
  67. package/dist/types/lib/Attachment.d.ts +152 -0
  68. package/dist/types/lib/Enforcement.d.ts +22 -0
  69. package/dist/types/lib/Functions.d.ts +66 -0
  70. package/dist/types/lib/InterfaceListener.d.ts +92 -0
  71. package/dist/types/lib/InterfaceUDPSocket.d.ts +119 -0
  72. package/dist/types/lib/Internal.d.ts +10 -0
  73. package/dist/types/lib/Native.d.ts +54 -0
  74. package/dist/types/lib/NetworkInterface.d.ts +313 -0
  75. package/dist/types/lib/Registry.d.ts +26 -0
  76. package/dist/types/lib/Snapshot.d.ts +17 -0
  77. package/dist/types/lib/Types.d.ts +63 -0
  78. package/native/Interfaces.cpp +34 -0
  79. package/native/Interfaces.h +47 -0
  80. package/native/Sockets.h +36 -0
  81. package/native/Watcher.h +27 -0
  82. package/native/addon.cpp +195 -0
  83. package/native/darwin/Interfaces.cpp +150 -0
  84. package/native/darwin/Sockets.cpp +44 -0
  85. package/native/darwin/Watcher.cpp +113 -0
  86. package/native/linux/Interfaces.cpp +133 -0
  87. package/native/linux/RouteNetlink.h +38 -0
  88. package/native/linux/Sockets.cpp +89 -0
  89. package/native/linux/Watcher.cpp +70 -0
  90. package/native/windows/Interfaces.cpp +78 -0
  91. package/native/windows/Sockets.cpp +56 -0
  92. package/native/windows/Watcher.cpp +53 -0
  93. package/package.json +81 -4
@@ -0,0 +1,195 @@
1
+ //The addon of @lakutata/netif: what Node.js cannot do by itself, given to JavaScript by Node-API (through
2
+ //node-addon-api). The errors of the system are thrown as errors with the code of the system (code, errno, syscall).
3
+ #include <napi.h>
4
+
5
+ #include <atomic>
6
+ #include <memory>
7
+ #include <system_error>
8
+
9
+ #include "Interfaces.h"
10
+ #include "Sockets.h"
11
+ #include "Watcher.h"
12
+
13
+ using namespace netif;
14
+
15
+ namespace {
16
+
17
+ //An error of the system as a JavaScript error: its message, the number (errno) and the call (syscall) it came from
18
+ Napi::Error systemError(Napi::Env env, const std::system_error &error)
19
+ {
20
+ Napi::Error thrown = Napi::Error::New(env, error.what());
21
+ thrown.Set("errno", Napi::Number::New(env, error.code().value()));
22
+ thrown.Set("syscall", Napi::String::New(env, error.what()));
23
+ return thrown;
24
+ }
25
+
26
+ Napi::Object addressObject(Napi::Env env, const Address &address)
27
+ {
28
+ Napi::Object object = Napi::Object::New(env);
29
+ object.Set("address", address.address);
30
+ object.Set("family", address.family == 4 ? "IPv4" : "IPv6");
31
+ object.Set("prefixLength", address.prefixLength);
32
+ object.Set("tentative", address.tentative);
33
+ if (address.family == 6) object.Set("scopeid", address.scopeId);
34
+ return object;
35
+ }
36
+
37
+ //list(): every interface of the machine, as [{name, index, mac?, up, running, loopback, addresses}]
38
+ Napi::Value List(const Napi::CallbackInfo &info)
39
+ {
40
+ Napi::Env env = info.Env();
41
+ std::vector<Interface> interfaces;
42
+ try {
43
+ interfaces = listInterfaces();
44
+ } catch (const std::system_error &error) {
45
+ throw systemError(env, error);
46
+ }
47
+ Napi::Array list = Napi::Array::New(env, interfaces.size());
48
+ for (size_t index = 0; index < interfaces.size(); index++) {
49
+ const Interface &entry = interfaces[index];
50
+ Napi::Object object = Napi::Object::New(env);
51
+ object.Set("name", entry.name);
52
+ object.Set("index", entry.index);
53
+ if (!entry.mac.empty()) object.Set("mac", entry.mac);
54
+ object.Set("up", entry.up);
55
+ object.Set("running", entry.running);
56
+ object.Set("loopback", entry.loopback);
57
+ Napi::Array addresses = Napi::Array::New(env, entry.addresses.size());
58
+ for (size_t position = 0; position < entry.addresses.size(); position++) {
59
+ addresses.Set(static_cast<uint32_t>(position), addressObject(env, entry.addresses[position]));
60
+ }
61
+ object.Set("addresses", addresses);
62
+ list.Set(static_cast<uint32_t>(index), object);
63
+ }
64
+ return list;
65
+ }
66
+
67
+ //bindSocket(name, family, stream, port, backlog): a socket bound to the interface of the name (Linux), as {fd, port}
68
+ Napi::Value BindSocket(const Napi::CallbackInfo &info)
69
+ {
70
+ Napi::Env env = info.Env();
71
+ BoundSocket bound;
72
+ try {
73
+ bound = bindToInterface(info[0].As<Napi::String>().Utf8Value(), info[1].As<Napi::Number>().Int32Value(), info[2].As<Napi::Boolean>().Value(), static_cast<uint16_t>(info[3].As<Napi::Number>().Uint32Value()), info[4].As<Napi::Number>().Int32Value());
74
+ } catch (const std::system_error &error) {
75
+ throw systemError(env, error);
76
+ }
77
+ Napi::Object result = Napi::Object::New(env);
78
+ result.Set("fd", bound.fd);
79
+ result.Set("port", bound.port);
80
+ return result;
81
+ }
82
+
83
+ //membership(fd, group, index, join): the socket joins or leaves the multicast group on the interface of the index (Linux)
84
+ Napi::Value Membership(const Napi::CallbackInfo &info)
85
+ {
86
+ Napi::Env env = info.Env();
87
+ try {
88
+ membership(info[0].As<Napi::Number>().Int32Value(), info[1].As<Napi::String>().Utf8Value(), info[2].As<Napi::Number>().Uint32Value(), info[3].As<Napi::Boolean>().Value());
89
+ } catch (const std::system_error &error) {
90
+ throw systemError(env, error);
91
+ }
92
+ return env.Undefined();
93
+ }
94
+
95
+ //weakReception(index, family): null when the system refuses, through another interface, the packets for the addresses of
96
+ //the interface of the index; otherwise {interface, setting}, the setting that makes it accept them
97
+ Napi::Value WeakReceptionOf(const Napi::CallbackInfo &info)
98
+ {
99
+ Napi::Env env = info.Env();
100
+ std::optional<WeakReception> found;
101
+ try {
102
+ found = weakReception(info[0].As<Napi::Number>().Uint32Value(), info[1].As<Napi::Number>().Int32Value());
103
+ } catch (const std::system_error &error) {
104
+ throw systemError(env, error);
105
+ }
106
+ if (!found) return env.Null();
107
+ Napi::Object result = Napi::Object::New(env);
108
+ result.Set("interface", found->interfaceName);
109
+ result.Set("setting", found->setting);
110
+ return result;
111
+ }
112
+
113
+ //The watching of an environment (the main thread, or a worker): the notifications of the system, and the function they
114
+ //call JavaScript by. A notification only signals a change: while one waits to be delivered, the next ones are dropped.
115
+ //The function does not keep the event loop alive.
116
+ struct Watching {
117
+ std::unique_ptr<Watcher> watcher;
118
+ Napi::ThreadSafeFunction function;
119
+ std::shared_ptr<std::atomic<bool>> pending = std::make_shared<std::atomic<bool>>(false);
120
+
121
+ //The notifications stopped first (no call once the watcher is gone), then the function released
122
+ void stop()
123
+ {
124
+ if (!watcher) return;
125
+ watcher.reset();
126
+ function.Release();
127
+ }
128
+ };
129
+
130
+ struct Environment {
131
+ Watching watching;
132
+ };
133
+
134
+ Environment &environmentOf(Napi::Env env)
135
+ {
136
+ return **env.GetInstanceData<std::shared_ptr<Environment>>();
137
+ }
138
+
139
+ //watch(changed): the notifications of the changes of the interfaces start, changed() called (with no argument) at each
140
+ //one; once only by environment
141
+ Napi::Value Watch(const Napi::CallbackInfo &info)
142
+ {
143
+ Napi::Env env = info.Env();
144
+ Watching &watching = environmentOf(env).watching;
145
+ if (watching.watcher) throw Napi::Error::New(env, "The interfaces are watched already");
146
+ if (!info[0].IsFunction()) throw Napi::TypeError::New(env, "watch() takes a function");
147
+ Napi::ThreadSafeFunction function = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(), "lakutata-netif", 0, 1);
148
+ function.Unref(env);
149
+ std::shared_ptr<std::atomic<bool>> pending = watching.pending;
150
+ pending->store(false);
151
+ try {
152
+ watching.watcher = std::make_unique<Watcher>([function, pending]() mutable {
153
+ if (pending->exchange(true)) return;
154
+ napi_status status = function.NonBlockingCall([pending](Napi::Env, Napi::Function changed) {
155
+ pending->store(false);
156
+ changed.Call({});
157
+ });
158
+ if (status != napi_ok) pending->store(false);
159
+ });
160
+ } catch (const std::system_error &error) {
161
+ function.Release();
162
+ throw systemError(env, error);
163
+ }
164
+ watching.function = function;
165
+ return env.Undefined();
166
+ }
167
+
168
+ //unwatch(): the notifications stopped
169
+ Napi::Value Unwatch(const Napi::CallbackInfo &info)
170
+ {
171
+ environmentOf(info.Env()).watching.stop();
172
+ return info.Env().Undefined();
173
+ }
174
+
175
+ Napi::Object Init(Napi::Env env, Napi::Object exports)
176
+ {
177
+ //Shared with the cleanup hook: alive whichever of the hook and the instance data goes first
178
+ auto environment = std::make_shared<Environment>();
179
+ env.SetInstanceData(new std::shared_ptr<Environment>(environment));
180
+ //Before the environment ends (a worker terminated, the process exiting): the notifications stopped
181
+ env.AddCleanupHook([environment]() {
182
+ environment->watching.stop();
183
+ });
184
+ exports.Set("list", Napi::Function::New(env, List));
185
+ exports.Set("watch", Napi::Function::New(env, Watch));
186
+ exports.Set("unwatch", Napi::Function::New(env, Unwatch));
187
+ exports.Set("bindSocket", Napi::Function::New(env, BindSocket));
188
+ exports.Set("membership", Napi::Function::New(env, Membership));
189
+ exports.Set("weakReception", Napi::Function::New(env, WeakReceptionOf));
190
+ return exports;
191
+ }
192
+
193
+ }
194
+
195
+ NODE_API_MODULE(netif, Init)
@@ -0,0 +1,150 @@
1
+ //The interfaces of macOS, read by getifaddrs(): the link entry of each interface (name, index, hardware address, flags),
2
+ //then its addresses. The link state comes from the media of the interface (SIOCGIFMEDIA) where it has one (IFF_RUNNING
3
+ //stays set on an Ethernet port without its cable), the duplicate address detection of an IPv6 address from its flags
4
+ //(SIOCGIFAFLAG_IN6).
5
+ #include "../Interfaces.h"
6
+
7
+ #include <ifaddrs.h>
8
+ #include <net/if.h>
9
+ #include <net/if_dl.h>
10
+ #include <net/if_media.h>
11
+ #include <netinet/in.h>
12
+ #include <netinet6/in6_var.h>
13
+ #include <sys/ioctl.h>
14
+ #include <sys/socket.h>
15
+ #include <unistd.h>
16
+
17
+ #include <cerrno>
18
+ #include <cstring>
19
+ #include <map>
20
+ #include <system_error>
21
+ #include <utility>
22
+
23
+ namespace netif {
24
+
25
+ namespace {
26
+
27
+ const char *const MASKED_MAC = "02:00:00:00:00:00";
28
+
29
+ class Descriptor {
30
+ public:
31
+ explicit Descriptor(int family) : fd(socket(family, SOCK_DGRAM, 0))
32
+ {
33
+ }
34
+
35
+ ~Descriptor()
36
+ {
37
+ if (fd >= 0) close(fd);
38
+ }
39
+
40
+ Descriptor(const Descriptor &) = delete;
41
+
42
+ Descriptor &operator=(const Descriptor &) = delete;
43
+
44
+ const int fd;
45
+ };
46
+
47
+ unsigned prefixLength(const sockaddr *netmask)
48
+ {
49
+ if (!netmask) return 0;
50
+ const unsigned char *bytes = nullptr;
51
+ size_t length = 0;
52
+ if (netmask->sa_family == AF_INET) {
53
+ bytes = reinterpret_cast<const unsigned char *>(&reinterpret_cast<const sockaddr_in *>(netmask)->sin_addr);
54
+ length = 4;
55
+ } else if (netmask->sa_family == AF_INET6) {
56
+ bytes = reinterpret_cast<const unsigned char *>(&reinterpret_cast<const sockaddr_in6 *>(netmask)->sin6_addr);
57
+ length = 16;
58
+ } else {
59
+ return 0;
60
+ }
61
+ unsigned bits = 0;
62
+ for (size_t index = 0; index < length; index++) bits += static_cast<unsigned>(__builtin_popcount(bytes[index]));
63
+ return bits;
64
+ }
65
+
66
+ //The link is up: its media says so, or (no media) the interface runs
67
+ bool linkActive(const Descriptor &probe, const char *name, unsigned int flags)
68
+ {
69
+ if (probe.fd >= 0) {
70
+ ifmediareq media{};
71
+ std::strncpy(media.ifm_name, name, sizeof media.ifm_name - 1);
72
+ if (ioctl(probe.fd, SIOCGIFMEDIA, &media) == 0 && (media.ifm_status & IFM_AVALID)) return media.ifm_status & IFM_ACTIVE;
73
+ }
74
+ return flags & IFF_RUNNING;
75
+ }
76
+
77
+ bool tentative(const Descriptor &probe, const char *name, const sockaddr_in6 *address)
78
+ {
79
+ if (probe.fd < 0) return false;
80
+ in6_ifreq request{};
81
+ std::strncpy(request.ifr_name, name, sizeof request.ifr_name - 1);
82
+ request.ifr_addr = *address;
83
+ if (ioctl(probe.fd, SIOCGIFAFLAG_IN6, &request) != 0) return false;
84
+ int flags = request.ifr_ifru.ifru_flags6;
85
+ if (flags & IN6_IFF_DUPLICATED) return true;
86
+ return (flags & IN6_IFF_TENTATIVE) && !(flags & IN6_IFF_OPTIMISTIC);
87
+ }
88
+
89
+ }
90
+
91
+ std::vector<Interface> listInterfaces()
92
+ {
93
+ ifaddrs *entries = nullptr;
94
+ if (getifaddrs(&entries) != 0) throw std::system_error(errno, std::generic_category(), "getifaddrs");
95
+ Descriptor probe4(AF_INET);
96
+ Descriptor probe6(AF_INET6);
97
+ std::vector<Interface> interfaces;
98
+ std::map<std::string, size_t> positions;
99
+ auto at = [&](const ifaddrs *entry) -> Interface & {
100
+ auto position = positions.find(entry->ifa_name);
101
+ if (position != positions.end()) return interfaces[position->second];
102
+ Interface created;
103
+ created.name = entry->ifa_name;
104
+ created.index = if_nametoindex(entry->ifa_name);
105
+ created.up = entry->ifa_flags & IFF_UP;
106
+ created.loopback = entry->ifa_flags & IFF_LOOPBACK;
107
+ created.running = linkActive(probe4, entry->ifa_name, entry->ifa_flags);
108
+ positions[created.name] = interfaces.size();
109
+ interfaces.push_back(std::move(created));
110
+ return interfaces.back();
111
+ };
112
+ for (const ifaddrs *entry = entries; entry; entry = entry->ifa_next) {
113
+ if (!entry->ifa_name) continue;
114
+ Interface &owner = at(entry);
115
+ if (!entry->ifa_addr) continue;
116
+ if (entry->ifa_addr->sa_family == AF_LINK) {
117
+ auto *link = reinterpret_cast<const sockaddr_dl *>(entry->ifa_addr);
118
+ if (link->sdl_index) owner.index = link->sdl_index;
119
+ owner.mac = macText(reinterpret_cast<const unsigned char *>(LLADDR(link)), link->sdl_alen);
120
+ //Newer macOS (27 at least) gives the processes without an entitlement 02:00:00:00:00:00 for every interface in
121
+ //place of its hardware address (os.networkInterfaces() too): no address then
122
+ if (owner.mac == MASKED_MAC) owner.mac.clear();
123
+ } else if (entry->ifa_addr->sa_family == AF_INET) {
124
+ Address address;
125
+ address.family = 4;
126
+ address.address = addressText(AF_INET, &reinterpret_cast<const sockaddr_in *>(entry->ifa_addr)->sin_addr);
127
+ address.prefixLength = prefixLength(entry->ifa_netmask);
128
+ owner.addresses.push_back(std::move(address));
129
+ } else if (entry->ifa_addr->sa_family == AF_INET6) {
130
+ sockaddr_in6 socketAddress = *reinterpret_cast<const sockaddr_in6 *>(entry->ifa_addr);
131
+ Address address;
132
+ address.family = 6;
133
+ address.prefixLength = prefixLength(entry->ifa_netmask);
134
+ address.tentative = tentative(probe6, entry->ifa_name, &socketAddress);
135
+ //A link-local address carries its scope in its second 16 bits inside the kernel (KAME): removed from the text
136
+ unsigned char *octets = socketAddress.sin6_addr.s6_addr;
137
+ if (octets[0] == 0xfe && (octets[1] & 0xc0) == 0x80) {
138
+ octets[2] = 0;
139
+ octets[3] = 0;
140
+ address.scopeId = owner.index;
141
+ }
142
+ address.address = addressText(AF_INET6, &socketAddress.sin6_addr);
143
+ owner.addresses.push_back(std::move(address));
144
+ }
145
+ }
146
+ freeifaddrs(entries);
147
+ return interfaces;
148
+ }
149
+
150
+ }
@@ -0,0 +1,44 @@
1
+ //macOS: no socket of an interface (IP_BOUND_IF only chooses the interface the packets leave by); the strong host model
2
+ //of the system (net.inet.ip.check_interface = 2, its IPv6 counterpart) refuses the packets of an address that come
3
+ //through another interface, as long as the forwarding of the family is off (XNU skips the check while it forwards).
4
+ #include "../Sockets.h"
5
+
6
+ #include <sys/sysctl.h>
7
+
8
+ #include <cerrno>
9
+ #include <system_error>
10
+
11
+ namespace netif {
12
+
13
+ namespace {
14
+
15
+ int setting(const char *name)
16
+ {
17
+ int value = 0;
18
+ size_t size = sizeof value;
19
+ if (sysctlbyname(name, &value, &size, nullptr, 0) != 0) return -1;
20
+ return value;
21
+ }
22
+
23
+ }
24
+
25
+ BoundSocket bindToInterface(const std::string &, int, bool, uint16_t, int)
26
+ {
27
+ throw std::system_error(ENOTSUP, std::generic_category(), "bind to an interface");
28
+ }
29
+
30
+ void membership(int, const std::string &, uint32_t, bool)
31
+ {
32
+ throw std::system_error(ENOTSUP, std::generic_category(), "membership");
33
+ }
34
+
35
+ std::optional<WeakReception> weakReception(uint32_t, int family)
36
+ {
37
+ const char *check = family == 6 ? "net.inet6.ip6.check_interface" : "net.inet.ip.check_interface";
38
+ const char *forwarding = family == 6 ? "net.inet6.ip6.forwarding" : "net.inet.ip.forwarding";
39
+ if (setting(check) != 2) return WeakReception{std::string(), std::string(check)};
40
+ if (setting(forwarding) != 0) return WeakReception{std::string(), std::string(forwarding)};
41
+ return std::nullopt;
42
+ }
43
+
44
+ }
@@ -0,0 +1,113 @@
1
+ //The notifications of macOS: the kernel events of the network (a PF_SYSTEM socket filtered to their class), read by a
2
+ //thread of the addon, which a pipe wakes to stop. Of the events only the ones of the interfaces (attached, detached,
3
+ //flags, link) and of their addresses count. The routing socket has no message for an interface attached or detached.
4
+ #include "../Watcher.h"
5
+
6
+ #include <net/net_kev.h>
7
+ #include <fcntl.h>
8
+ #include <poll.h>
9
+ #include <sys/ioctl.h>
10
+ #include <sys/kern_event.h>
11
+ #include <sys/socket.h>
12
+ #include <sys/sys_domain.h>
13
+ #include <unistd.h>
14
+
15
+ #include <cerrno>
16
+ #include <system_error>
17
+ #include <thread>
18
+ #include <vector>
19
+
20
+ namespace netif {
21
+
22
+ struct Watcher::State {
23
+ int socket = -1;
24
+ int wake[2] = {-1, -1};
25
+ std::thread thread;
26
+ };
27
+
28
+ namespace {
29
+
30
+ bool counts(const kern_event_msg *event)
31
+ {
32
+ if (event->vendor_code != KEV_VENDOR_APPLE || event->kev_class != KEV_NETWORK_CLASS) return false;
33
+ switch (event->kev_subclass) {
34
+ case KEV_INET_SUBCLASS:
35
+ return event->event_code == KEV_INET_NEW_ADDR || event->event_code == KEV_INET_CHANGED_ADDR || event->event_code == KEV_INET_ADDR_DELETED || event->event_code == KEV_INET_SIFNETMASK;
36
+ case KEV_INET6_SUBCLASS:
37
+ return event->event_code == KEV_INET6_NEW_USER_ADDR || event->event_code == KEV_INET6_CHANGED_ADDR || event->event_code == KEV_INET6_ADDR_DELETED || event->event_code == KEV_INET6_NEW_LL_ADDR || event->event_code == KEV_INET6_NEW_RTADV_ADDR;
38
+ case KEV_DL_SUBCLASS:
39
+ return event->event_code == KEV_DL_SIFFLAGS || event->event_code == KEV_DL_SIFMEDIA || event->event_code == KEV_DL_IF_ATTACHED || event->event_code == KEV_DL_IF_DETACHED || event->event_code == KEV_DL_LINK_OFF || event->event_code == KEV_DL_LINK_ON || event->event_code == KEV_DL_LINK_ADDRESS_CHANGED;
40
+ default:
41
+ return false;
42
+ }
43
+ }
44
+
45
+ void closeAll(int socket, const int (&wake)[2])
46
+ {
47
+ if (socket >= 0) close(socket);
48
+ for (int descriptor : wake) {
49
+ if (descriptor >= 0) close(descriptor);
50
+ }
51
+ }
52
+
53
+ }
54
+
55
+ Watcher::Watcher(std::function<void()> changed) : state(std::make_unique<State>())
56
+ {
57
+ state->socket = socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT);
58
+ if (state->socket < 0) throw std::system_error(errno, std::generic_category(), "socket(PF_SYSTEM)");
59
+ kev_request filter{KEV_VENDOR_APPLE, KEV_NETWORK_CLASS, KEV_ANY_SUBCLASS};
60
+ if (ioctl(state->socket, SIOCSKEVFILT, &filter) != 0 || pipe(state->wake) != 0) {
61
+ int error = errno;
62
+ closeAll(state->socket, state->wake);
63
+ state->socket = -1;
64
+ throw std::system_error(error, std::generic_category(), "SIOCSKEVFILT");
65
+ }
66
+ for (int descriptor : {state->socket, state->wake[0], state->wake[1]}) {
67
+ fcntl(descriptor, F_SETFD, FD_CLOEXEC);
68
+ fcntl(descriptor, F_SETFL, fcntl(descriptor, F_GETFL) | O_NONBLOCK);
69
+ }
70
+ State *watched = state.get();
71
+ state->thread = std::thread([watched, changed]() {
72
+ std::vector<char> buffer(8192);
73
+ pollfd descriptors[2] = {{watched->socket, POLLIN, 0}, {watched->wake[0], POLLIN, 0}};
74
+ for (;;) {
75
+ if (poll(descriptors, 2, -1) < 0) {
76
+ if (errno == EINTR) continue;
77
+ return;
78
+ }
79
+ if (descriptors[1].revents) return;
80
+ if (!descriptors[0].revents) continue;
81
+ bool any = false;
82
+ for (;;) {
83
+ ssize_t received = read(watched->socket, buffer.data(), buffer.size());
84
+ if (received < 0) {
85
+ if (errno == EINTR) continue;
86
+ if (errno == ENOBUFS) any = true;
87
+ break;
88
+ }
89
+ //The events of a read, one after the other
90
+ for (ssize_t offset = 0; offset + static_cast<ssize_t>(KEV_MSG_HEADER_SIZE) <= received;) {
91
+ auto *event = reinterpret_cast<const kern_event_msg *>(buffer.data() + offset);
92
+ if (event->total_size < KEV_MSG_HEADER_SIZE) break;
93
+ any = any || counts(event);
94
+ offset += event->total_size;
95
+ }
96
+ }
97
+ if (any) changed();
98
+ }
99
+ });
100
+ }
101
+
102
+ Watcher::~Watcher()
103
+ {
104
+ if (state->wake[1] >= 0) {
105
+ char byte = 1;
106
+ while (write(state->wake[1], &byte, 1) < 0 && errno == EINTR) {
107
+ }
108
+ }
109
+ if (state->thread.joinable()) state->thread.join();
110
+ closeAll(state->socket, state->wake);
111
+ }
112
+
113
+ }
@@ -0,0 +1,133 @@
1
+ //The interfaces of Linux, read by netlink: the dump of the links (name, index, hardware address, flags), then the one of
2
+ //the addresses with their flags (a tentative IPv6 address is in its duplicate address detection). getifaddrs() gives no
3
+ //flags of the addresses.
4
+ #include "../Interfaces.h"
5
+ #include "RouteNetlink.h"
6
+
7
+ #include <linux/if_addr.h>
8
+ #include <net/if.h>
9
+ #include <sys/socket.h>
10
+ #include <unistd.h>
11
+
12
+ #include <cerrno>
13
+ #include <map>
14
+ #include <system_error>
15
+ #include <utility>
16
+
17
+ namespace netif {
18
+
19
+ Netlink::Netlink(uint32_t groups)
20
+ {
21
+ descriptor = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
22
+ if (descriptor < 0) throw std::system_error(errno, std::generic_category(), "socket(AF_NETLINK)");
23
+ sockaddr_nl local{};
24
+ local.nl_family = AF_NETLINK;
25
+ local.nl_groups = groups;
26
+ if (bind(descriptor, reinterpret_cast<sockaddr *>(&local), sizeof local) < 0) {
27
+ int error = errno;
28
+ close(descriptor);
29
+ descriptor = -1;
30
+ throw std::system_error(error, std::generic_category(), "bind(AF_NETLINK)");
31
+ }
32
+ }
33
+
34
+ Netlink::~Netlink()
35
+ {
36
+ if (descriptor >= 0) close(descriptor);
37
+ }
38
+
39
+ void Netlink::dump(uint16_t type, unsigned char family, const std::function<void(const nlmsghdr *)> &handler)
40
+ {
41
+ struct {
42
+ nlmsghdr header;
43
+ rtgenmsg message;
44
+ } request{};
45
+ request.header.nlmsg_len = NLMSG_LENGTH(sizeof(rtgenmsg));
46
+ request.header.nlmsg_type = type;
47
+ request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
48
+ request.header.nlmsg_seq = ++sequence;
49
+ request.message.rtgen_family = family;
50
+ sockaddr_nl kernel{};
51
+ kernel.nl_family = AF_NETLINK;
52
+ if (sendto(descriptor, &request, request.header.nlmsg_len, 0, reinterpret_cast<sockaddr *>(&kernel), sizeof kernel) < 0) {
53
+ throw std::system_error(errno, std::generic_category(), "send(netlink)");
54
+ }
55
+ std::vector<char> buffer(65536);
56
+ for (;;) {
57
+ ssize_t received = recv(descriptor, buffer.data(), buffer.size(), 0);
58
+ if (received < 0) {
59
+ if (errno == EINTR) continue;
60
+ throw std::system_error(errno, std::generic_category(), "recv(netlink)");
61
+ }
62
+ int remaining = static_cast<int>(received);
63
+ for (auto *header = reinterpret_cast<nlmsghdr *>(buffer.data()); NLMSG_OK(header, remaining); header = NLMSG_NEXT(header, remaining)) {
64
+ if (header->nlmsg_seq != sequence) continue;
65
+ if (header->nlmsg_type == NLMSG_DONE) return;
66
+ if (header->nlmsg_type == NLMSG_ERROR) {
67
+ auto *error = static_cast<const nlmsgerr *>(NLMSG_DATA(header));
68
+ if (error->error == 0) continue;
69
+ throw std::system_error(-error->error, std::generic_category(), "netlink dump");
70
+ }
71
+ handler(header);
72
+ }
73
+ }
74
+ }
75
+
76
+ std::vector<Interface> listInterfaces()
77
+ {
78
+ Netlink netlink;
79
+ std::vector<Interface> interfaces;
80
+ std::map<uint32_t, size_t> positions;
81
+ netlink.dump(RTM_GETLINK, AF_UNSPEC, [&](const nlmsghdr *header) {
82
+ if (header->nlmsg_type != RTM_NEWLINK) return;
83
+ auto *info = static_cast<const ifinfomsg *>(NLMSG_DATA(header));
84
+ Interface entry;
85
+ entry.index = static_cast<uint32_t>(info->ifi_index);
86
+ entry.up = info->ifi_flags & IFF_UP;
87
+ entry.running = info->ifi_flags & IFF_RUNNING;
88
+ entry.loopback = info->ifi_flags & IFF_LOOPBACK;
89
+ int length = static_cast<int>(IFLA_PAYLOAD(header));
90
+ for (auto *attribute = IFLA_RTA(info); RTA_OK(attribute, length); attribute = RTA_NEXT(attribute, length)) {
91
+ if (attribute->rta_type == IFLA_IFNAME) {
92
+ entry.name = static_cast<const char *>(RTA_DATA(attribute));
93
+ } else if (attribute->rta_type == IFLA_ADDRESS) {
94
+ entry.mac = macText(static_cast<const unsigned char *>(RTA_DATA(attribute)), RTA_PAYLOAD(attribute));
95
+ }
96
+ }
97
+ positions[entry.index] = interfaces.size();
98
+ interfaces.push_back(std::move(entry));
99
+ });
100
+ netlink.dump(RTM_GETADDR, AF_UNSPEC, [&](const nlmsghdr *header) {
101
+ if (header->nlmsg_type != RTM_NEWADDR) return;
102
+ auto *info = static_cast<const ifaddrmsg *>(NLMSG_DATA(header));
103
+ if (info->ifa_family != AF_INET && info->ifa_family != AF_INET6) return;
104
+ auto position = positions.find(info->ifa_index);
105
+ if (position == positions.end()) return;
106
+ const void *local = nullptr;
107
+ const void *address = nullptr;
108
+ uint32_t flags = info->ifa_flags;
109
+ int length = static_cast<int>(IFA_PAYLOAD(header));
110
+ for (auto *attribute = IFA_RTA(info); RTA_OK(attribute, length); attribute = RTA_NEXT(attribute, length)) {
111
+ if (attribute->rta_type == IFA_LOCAL) local = RTA_DATA(attribute);
112
+ else if (attribute->rta_type == IFA_ADDRESS) address = RTA_DATA(attribute);
113
+ #ifdef IFA_FLAGS
114
+ else if (attribute->rta_type == IFA_FLAGS) flags = *static_cast<const uint32_t *>(RTA_DATA(attribute));
115
+ #endif
116
+ }
117
+ //IPv4: the local address is IFA_LOCAL (IFA_ADDRESS is the peer of a point-to-point link); IPv6: IFA_ADDRESS
118
+ const void *bytes = info->ifa_family == AF_INET && local ? local : (address ? address : local);
119
+ if (!bytes) return;
120
+ Address entry;
121
+ entry.family = info->ifa_family == AF_INET ? 4 : 6;
122
+ entry.address = addressText(info->ifa_family, bytes);
123
+ entry.prefixLength = info->ifa_prefixlen;
124
+ //An optimistic address (RFC 4429) is usable while its detection runs
125
+ entry.tentative = (flags & IFA_F_DADFAILED) || ((flags & IFA_F_TENTATIVE) && !(flags & IFA_F_OPTIMISTIC));
126
+ auto *octets = static_cast<const unsigned char *>(bytes);
127
+ if (entry.family == 6 && octets[0] == 0xfe && (octets[1] & 0xc0) == 0x80) entry.scopeId = info->ifa_index;
128
+ interfaces[position->second].addresses.push_back(std::move(entry));
129
+ });
130
+ return interfaces;
131
+ }
132
+
133
+ }
@@ -0,0 +1,38 @@
1
+ //A netlink socket of the routing family (NETLINK_ROUTE): the dumps of the links and the addresses the interfaces are
2
+ //read from, and the socket of the notifications of their changes. Not named Netlink.h: native is an include directory,
3
+ //and a file system ignoring case would take that file for <linux/netlink.h>
4
+ #pragma once
5
+
6
+ #include <linux/netlink.h>
7
+ #include <linux/rtnetlink.h>
8
+
9
+ #include <cstdint>
10
+ #include <functional>
11
+
12
+ namespace netif {
13
+
14
+ class Netlink {
15
+ public:
16
+ //A socket for dumps (groups 0), or one joined to the given groups of notifications (RTMGRP_*)
17
+ explicit Netlink(uint32_t groups = 0);
18
+
19
+ ~Netlink();
20
+
21
+ Netlink(const Netlink &) = delete;
22
+
23
+ Netlink &operator=(const Netlink &) = delete;
24
+
25
+ int fd() const
26
+ {
27
+ return descriptor;
28
+ }
29
+
30
+ //Every message of a dump of the given request type (RTM_GETLINK, RTM_GETADDR), given to the handler
31
+ void dump(uint16_t type, unsigned char family, const std::function<void(const nlmsghdr *)> &handler);
32
+
33
+ private:
34
+ int descriptor = -1;
35
+ uint32_t sequence = 0;
36
+ };
37
+
38
+ }