node-netlink 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -1,2 +1,3 @@
1
1
  # node-netlink
2
+ [![Build Status](https://travis-ci.org/k13-engineering/node-netlink.svg?branch=master)](https://travis-ci.org/k13-engineering/node-netlink)<br/>
2
3
  netlink bindings for node.js
package/lib/error.js ADDED
@@ -0,0 +1,50 @@
1
+ const knownErrors = {
2
+ EPERM: {
3
+ code: 1,
4
+ message: "Operation not permitted",
5
+ },
6
+ ENOENT: {
7
+ code: 2,
8
+ message: "No such file or directory",
9
+ },
10
+ EAGAIN: {
11
+ code: 11,
12
+ message: "Resource temporarily unavailable",
13
+ },
14
+ EEXIST: {
15
+ code: 17,
16
+ message: "File already exists",
17
+ },
18
+ ENODEV: {
19
+ code: 19,
20
+ message: "No such device",
21
+ },
22
+ EINVAL: {
23
+ code: 22,
24
+ message: "Invalid argument",
25
+ },
26
+ ENOTSUP: {
27
+ code: 48,
28
+ message: "Operation not supported",
29
+ },
30
+ };
31
+
32
+ const createFromErrorCode = ({ errorCode, message }) => {
33
+ const knownErrorKey = Object.keys(knownErrors).find((key) => {
34
+ return knownErrors[key].code === errorCode;
35
+ });
36
+
37
+ if (knownErrorKey) {
38
+ const knownError = knownErrors[knownErrorKey];
39
+ const errorObject = new Error(`${message ? `${message}: ` : ""}${knownError.message}`);
40
+ errorObject.code = knownError.code;
41
+ errorObject.errno = errorCode;
42
+ return errorObject;
43
+ } else {
44
+ return new Error(`${message ? `${message}: ` : ""}unknown error code ${errorCode}`);
45
+ }
46
+ };
47
+
48
+ export default {
49
+ createFromErrorCode,
50
+ };
package/lib/index.js ADDED
@@ -0,0 +1,259 @@
1
+ import socket from "po6-socket";
2
+ import EventEmitter from "events";
3
+
4
+ import structures from "./structures.js";
5
+ import netlinkParser from "./parser.js";
6
+ import errorFactory from "./error.js";
7
+
8
+ const AF_NETLINK = 16n;
9
+ const SOCK_DGRAM = 2n;
10
+
11
+ const NLM_F_ECHO = 8n;
12
+ const NLM_F_REQUEST = 1n;
13
+ const NLM_F_MULTI = 2n;
14
+ const NLM_F_MATCH = 0x200n;
15
+ const NLM_F_DUMP = 0x300n;
16
+ const NLM_F_ACK = 4n;
17
+ const NLM_F_CREATE = 0x400n;
18
+ const NLM_F_EXCL = 0x200n;
19
+
20
+ const NLMSG_ERROR = 2n;
21
+ const NLMSG_DONE = 3n;
22
+
23
+ const open = ({ family, pid, groups }) => {
24
+ const emitter = new EventEmitter();
25
+
26
+ return socket
27
+ .create({
28
+ domain: AF_NETLINK,
29
+ type: SOCK_DGRAM,
30
+ protocol: BigInt(family),
31
+ })
32
+ .then((sock) => {
33
+ const netlinkAddress = structures.sockaddr_nl.format({
34
+ nl_family: AF_NETLINK,
35
+ nl_pid: BigInt(pid || 0),
36
+ nl_groups: BigInt(groups || 0),
37
+ });
38
+
39
+ const close = () => {
40
+ return sock.close();
41
+ };
42
+
43
+ sock.on("message", ({ data }) => {
44
+ const parsedPackets = netlinkParser.parseMessage({ message: data });
45
+
46
+ parsedPackets.forEach((packet) => {
47
+ emitter.emit("message", packet);
48
+ });
49
+ });
50
+
51
+ let seq = 1n;
52
+
53
+ const send = ({ header: requestedHeader, payload }) => {
54
+ let header = Object.assign({}, requestedHeader);
55
+
56
+ if (header.nlmsg_seq === undefined) {
57
+ header = Object.assign({}, header, {
58
+ nlmsg_seq: seq,
59
+ });
60
+ seq += 1n;
61
+ }
62
+
63
+ if (header.nlmsg_pid === undefined) {
64
+ header = Object.assign({}, header, {
65
+ // "nlmsg_pid": nl_pid
66
+ });
67
+ }
68
+
69
+ header = Object.assign({}, header, {
70
+ nlmsg_len: BigInt(structures.nlmsghdr.size) + BigInt(payload.length),
71
+ });
72
+
73
+ const headerAsBuf = structures.nlmsghdr.format(header);
74
+
75
+ const data = Buffer.concat([headerAsBuf, payload]);
76
+
77
+ const destBuffer = structures.sockaddr_nl.format({
78
+ nl_family: AF_NETLINK,
79
+ });
80
+
81
+ // TODO: flags
82
+
83
+ return sock.sendmsg({
84
+ data,
85
+ msghdr: {
86
+ msg_name: destBuffer,
87
+ },
88
+ });
89
+ };
90
+
91
+ let requested = {};
92
+
93
+ const maybeAppendMessageToResponse = ({ message }) => {
94
+ const { header } = message;
95
+
96
+ if (header.nlmsg_type === NLMSG_DONE) {
97
+ return;
98
+ }
99
+
100
+ requested = {
101
+ [header.nlmsg_seq]: {
102
+ ...requested[header.nlmsg_seq],
103
+ msgs: [...(requested[header.nlmsg_seq]?.msgs || []), message],
104
+ },
105
+ };
106
+ };
107
+
108
+ const maybeFinishRequest = ({ message }) => {
109
+ const { header } = message;
110
+ const req = requested[header.nlmsg_seq];
111
+
112
+ const isLast =
113
+ header.nlmsg_type === NLMSG_ERROR || header.nlmsg_type === NLMSG_DONE;
114
+
115
+ if (isLast) {
116
+ clearTimeout(req.schedule);
117
+ req.resolve(req.msgs);
118
+
119
+ const { [header.nlmsg_seq]: unused, ...other } = requested;
120
+ requested = other;
121
+ }
122
+ };
123
+
124
+ const processResponse = ({ message }) => {
125
+ const { header } = message;
126
+ const req = requested[header.nlmsg_seq];
127
+
128
+ if (!req) {
129
+ console.warn(
130
+ `could not find pending request for seq=${header.nlmsg_seq}`
131
+ );
132
+ console.warn(`nlmsg_type = ${header.nlmsg_type}`);
133
+
134
+ return;
135
+ }
136
+
137
+ maybeAppendMessageToResponse({ message });
138
+ maybeFinishRequest({ message });
139
+ };
140
+
141
+ emitter.on("message", (message) => {
142
+ const { header } = message;
143
+
144
+ if (header.nlmsg_flags & NLM_F_REQUEST) {
145
+ return;
146
+ }
147
+
148
+ if (header.nlmsg_seq === 0) {
149
+ return;
150
+ }
151
+
152
+ processResponse({ message });
153
+ });
154
+
155
+ const tryTalk = (message, { timeout = 10000 } = {}) => {
156
+ if (!(message.header.nlmsg_flags & NLM_F_ACK)) {
157
+ throw new Error(`talk only supports requests with NLM_F_ACK set`);
158
+ }
159
+
160
+ return Promise.resolve()
161
+ .then(() => {
162
+ return new Promise((resolve, reject) => {
163
+ const header = Object.assign({}, message.header, {
164
+ nlmsg_seq: seq,
165
+ });
166
+ seq += 1n;
167
+
168
+ const schedule = setTimeout(() => {
169
+ const { [header.nlmsg_seq]: unused, ...other } = requested;
170
+ requested = other;
171
+
172
+ reject(new Error("timeout reached"));
173
+ }, timeout);
174
+
175
+ requested = {
176
+ ...requested,
177
+ [header.nlmsg_seq]: {
178
+ msgs: [],
179
+ schedule,
180
+ resolve,
181
+ },
182
+ };
183
+
184
+ send({
185
+ header,
186
+ payload: message.payload,
187
+ }).catch(reject);
188
+ });
189
+ })
190
+ .then((packets) => {
191
+ let errorCode = 0;
192
+
193
+ const packetsWithoutError = packets.filter((pkt) => {
194
+ if (pkt.header.nlmsg_type === NLMSG_ERROR) {
195
+ const packetErrorCode = pkt.payload.readInt32LE(0);
196
+ if (packetErrorCode !== 0) {
197
+ errorCode = -packetErrorCode;
198
+ }
199
+ return false;
200
+ } else {
201
+ return true;
202
+ }
203
+ });
204
+
205
+ return {
206
+ errorCode,
207
+ packets: packetsWithoutError,
208
+ };
209
+ });
210
+ };
211
+
212
+ const talk = (obj, options) => {
213
+ return tryTalk(obj, options).then(({ errorCode, packets }) => {
214
+ if (errorCode !== 0) {
215
+ throw errorFactory.createFromErrorCode({ errorCode });
216
+ }
217
+
218
+ return packets;
219
+ });
220
+ };
221
+
222
+ return sock
223
+ .bind({
224
+ sockaddr: netlinkAddress,
225
+ })
226
+ .then(() => {
227
+ return sock.getsockname();
228
+ })
229
+ .then((actualAddressBuffer) => {
230
+ const { nl_pid } = structures.sockaddr_nl.parse(actualAddressBuffer);
231
+
232
+ return {
233
+ nl_pid,
234
+
235
+ talk,
236
+ tryTalk,
237
+ close,
238
+ on: emitter.on.bind(emitter),
239
+ };
240
+ });
241
+ });
242
+ };
243
+
244
+ export default {
245
+ NLM_F_ECHO,
246
+ NLM_F_REQUEST,
247
+ NLM_F_MULTI,
248
+ NLM_F_DUMP,
249
+ NLM_F_ACK,
250
+ NLM_F_CREATE,
251
+ NLM_F_EXCL,
252
+ NLM_F_MATCH,
253
+
254
+ NLMSG_DONE,
255
+ NLMSG_ERROR,
256
+
257
+ open,
258
+ createErrorFromErrorCode: errorFactory.createFromErrorCode,
259
+ };
package/lib/parser.js ADDED
@@ -0,0 +1,55 @@
1
+ import structures from "./structures.js";
2
+
3
+ const align4 = (addr) => {
4
+ return (addr + 3) & ~3;
5
+ };
6
+
7
+ const parseNetlinkMessage = ({ message }) => {
8
+ const parsed = structures.nlmsghdr.parse(message);
9
+
10
+ const header = {
11
+ nlmsg_type: parsed.nlmsg_type,
12
+ nlmsg_flags: parsed.nlmsg_flags,
13
+ nlmsg_seq: parsed.nlmsg_seq,
14
+ nlmsg_pid: parsed.nlmsg_pid
15
+ };
16
+
17
+ const payloadOffset = align4(structures.nlmsghdr.size);
18
+ const payloadLength = parsed.nlmsg_len - BigInt(payloadOffset);
19
+
20
+ if (payloadLength < 0) {
21
+ throw new Error("negative payload length, packet malformed");
22
+ }
23
+
24
+ const payload = message.slice(payloadOffset, payloadOffset + Number(payloadLength));
25
+ const space = align4(payloadOffset + payload.length);
26
+
27
+ return {
28
+ header,
29
+ payload,
30
+ space
31
+ };
32
+ };
33
+
34
+ const parseMessage = ({ message }) => {
35
+ let result = [];
36
+
37
+ let remaining = message;
38
+ while (remaining.length > 0) {
39
+ const { header, payload, space } = parseNetlinkMessage({ message: remaining });
40
+ const parsed = {
41
+ header,
42
+ payload
43
+ };
44
+
45
+ result = [...result, parsed ];
46
+
47
+ remaining = remaining.slice(space);
48
+ }
49
+
50
+ return result;
51
+ };
52
+
53
+ export default {
54
+ parseMessage
55
+ };
@@ -0,0 +1,21 @@
1
+ import struct from "ya-struct";
2
+
3
+ const sockaddr_nl = struct.define(({ field }) => {
4
+ field.UInt16("nl_family");
5
+ field.UInt16("nl_pad");
6
+ field.UInt32("nl_pid");
7
+ field.UInt32("nl_groups");
8
+ }).forHost();
9
+
10
+ const nlmsghdr = struct.define(({ field }) => {
11
+ field.UInt32("nlmsg_len");
12
+ field.UInt16("nlmsg_type");
13
+ field.UInt16("nlmsg_flags");
14
+ field.UInt32("nlmsg_seq");
15
+ field.UInt32("nlmsg_pid");
16
+ }).forHost();
17
+
18
+ export default {
19
+ sockaddr_nl,
20
+ nlmsghdr
21
+ };
package/package.json CHANGED
@@ -1,23 +1,28 @@
1
1
  {
2
2
  "name": "node-netlink",
3
- "version": "0.0.1",
3
+ "type": "module",
4
+ "version": "0.0.2",
4
5
  "description": "netlink bindings for node.js",
5
- "main": "index.js",
6
+ "main": "lib/index.js",
6
7
  "scripts": {
7
- "test": "mocha"
8
+ "test": "c8 --reporter lcov --reporter html --reporter text mocha",
9
+ "eslint": "eslint ."
8
10
  },
9
11
  "dependencies": {
10
- "nan": "^2.10.0"
12
+ "po6-socket": "^0.0.1",
13
+ "ya-struct": "^0.0.4"
11
14
  },
12
15
  "devDependencies": {
13
- "mocha": "^5.2.0"
16
+ "c8": "^7.10.0",
17
+ "eslint": "^7.32.0",
18
+ "mocha": "^9.1.3"
14
19
  },
15
20
  "repository": {
16
21
  "type": "git",
17
22
  "url": "git+https://github.com/k13-engineering/node-netlink.git"
18
23
  },
19
24
  "author": "Simon Kadisch",
20
- "license": "GPL-3.0",
25
+ "license": "LGPL 2.1",
21
26
  "bugs": {
22
27
  "url": "https://github.com/k13-engineering/node-netlink/issues"
23
28
  },
@@ -0,0 +1,11 @@
1
+ import netlink from "../lib/index.js";
2
+
3
+ const NETLINK_ROUTE = 0;
4
+
5
+ netlink.open({
6
+ family: NETLINK_ROUTE
7
+ }).then((nl) => {
8
+ nl.close();
9
+ }).catch((err) => {
10
+ console.error(err);
11
+ });
package/test/base.js CHANGED
@@ -1,4 +1,7 @@
1
- const netlink = require("../index.js");
1
+ /* global describe */
2
+ /* global it */
3
+
4
+ import netlink from "../lib/index.js";
2
5
 
3
6
  const NETLINK_ROUTE = 0;
4
7
 
@@ -7,10 +10,11 @@ describe("opening", function () {
7
10
 
8
11
  describe("normal case", () => {
9
12
  it("should open without error", () => {
10
- const nl = netlink.open({
11
- "family": NETLINK_ROUTE
13
+ netlink.open({
14
+ family: NETLINK_ROUTE
15
+ }).then((nl) => {
16
+ return nl.close();
12
17
  });
13
- nl.close();
14
18
  });
15
19
  });
16
20
  });
package/.npmignore DELETED
@@ -1,61 +0,0 @@
1
- # Logs
2
- logs
3
- *.log
4
- npm-debug.log*
5
- yarn-debug.log*
6
- yarn-error.log*
7
-
8
- # Runtime data
9
- pids
10
- *.pid
11
- *.seed
12
- *.pid.lock
13
-
14
- # Directory for instrumented libs generated by jscoverage/JSCover
15
- lib-cov
16
-
17
- # Coverage directory used by tools like istanbul
18
- coverage
19
-
20
- # nyc test coverage
21
- .nyc_output
22
-
23
- # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24
- .grunt
25
-
26
- # Bower dependency directory (https://bower.io/)
27
- bower_components
28
-
29
- # node-waf configuration
30
- .lock-wscript
31
-
32
- # Compiled binary addons (http://nodejs.org/api/addons.html)
33
- build/Release
34
-
35
- # Dependency directories
36
- node_modules/
37
- jspm_packages/
38
-
39
- # Typescript v1 declaration files
40
- typings/
41
-
42
- # Optional npm cache directory
43
- .npm
44
-
45
- # Optional eslint cache
46
- .eslintcache
47
-
48
- # Optional REPL history
49
- .node_repl_history
50
-
51
- # Output of 'npm pack'
52
- *.tgz
53
-
54
- # Yarn Integrity file
55
- .yarn-integrity
56
-
57
- # dotenv environment variables file
58
- .env
59
-
60
- build/
61
- package-lock.json
package/binding.gyp DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "targets": [
3
- {
4
- "target_name": "netlink-native",
5
- "sources": [ "native/netlink.cc" ],
6
- "include_dirs": [
7
- "<!(node -e \"require('nan')\")"
8
- ]
9
- }
10
- ]
11
- }
package/index.js DELETED
@@ -1,150 +0,0 @@
1
- const assert = require("assert");
2
- const fs = require("fs");
3
- const EventEmitter = require("events");
4
-
5
- const native = require("./build/Release/netlink-native.node");
6
-
7
- const AF_NETLINK = 16;
8
- const SOCK_DGRAM = 2;
9
-
10
- const NLM_F_ECHO = 8;
11
- const NLM_F_REQUEST = 1;
12
- const NLM_F_MULTI = 2;
13
- const NLM_F_DUMP = 0x300;
14
- const NLM_F_ACK = 4;
15
- const NLM_F_CREATE = 0x400;
16
- const NLM_F_EXCL = 0x200;
17
-
18
- const NLMSG_ERROR = 2;
19
- const NLMSG_DONE = 3;
20
-
21
- const open = (opts) => {
22
- const family = opts.family;
23
- assert(typeof family === "number" && !isNaN(family) && family >= 0, "opts.family should be a positive integer");
24
-
25
- const emitter = new EventEmitter();
26
-
27
- const n = new native.NetlinkSocket(opts.family, (msg) => {
28
- emitter.emit("message", msg);
29
- });
30
-
31
- let requested = {};
32
-
33
- emitter.on("message", (msg) => {
34
- // console.log("msg =", msg);
35
-
36
- msg.forEach((part) => {
37
- if (!(part.header.nlmsg_flags & NLM_F_REQUEST)) {
38
- if (part.header.nlmsg_seq > 0) {
39
- const req = requested[part.header.nlmsg_seq];
40
-
41
- if (req) {
42
- req.multi |= part.header.nlmsg_flags & NLM_F_MULTI;
43
-
44
- if (part.header.nlmsg_type === NLMSG_ERROR) {
45
- const err = part.payload.readInt32LE(0);
46
-
47
- if (err === 0) {
48
- req.cb(null, []);
49
- } else {
50
- req.cb(err);
51
- }
52
- } else {
53
- if (req.multi) {
54
- req.msgs = req.msgs.concat([part]);
55
-
56
- if (part.header.nlmsg_type === NLMSG_DONE) {
57
- req.cb(null, req.msgs);
58
- }
59
- } else {
60
- req.cb(null, [part]);
61
- }
62
- }
63
- } else {
64
- console.warn("could not find pending request for seq=" + part.header.nlmsg_seq);
65
- }
66
- }
67
- }
68
- });
69
- });
70
-
71
- let seq = 1;
72
-
73
- const send = (obj) => new Promise((resolve, reject) => {
74
- assert(typeof obj.header === "object" &&
75
- !Buffer.isBuffer(obj.header) &&
76
- !Array.isArray(obj.header), "header must be an object");
77
-
78
- let header = Object.assign({}, obj.header);
79
-
80
- if (header.nlmsg_seq === undefined) {
81
- header = Object.assign({}, header, {
82
- "nlmsg_seq": seq
83
- });
84
- seq += 1;
85
- }
86
-
87
- n.send(header, obj.payload, obj.flags, (err) => {
88
- resolve();
89
- });
90
- });
91
-
92
- const talk = (obj) => {
93
- return new Promise((resolve, reject) => {
94
- const header = Object.assign({}, obj.header, {
95
- "nlmsg_seq": seq
96
- });
97
- seq += 1;
98
-
99
- const schedule = setTimeout(() => {
100
- Reflect.deleteProperty(requested, header.nlmsg_seq);
101
- reject(new Error("timeout reached"));
102
- }, obj.timeout || 10000);
103
-
104
- requested[header.nlmsg_seq] = {
105
- "msgs": [],
106
- "multi": false,
107
- "schedule": schedule,
108
- "cb": (err, data) => {
109
- clearTimeout(schedule);
110
- Reflect.deleteProperty(requested, header.nlmsg_seq);
111
-
112
- if (err) {
113
- reject(err);
114
- } else {
115
- resolve(data);
116
- }
117
- }
118
- };
119
-
120
- send({
121
- "header": header,
122
- "payload": obj.payload
123
- }).catch(reject);
124
- });
125
- };
126
-
127
-
128
- return Object.freeze({
129
- send,
130
- talk,
131
- "on": emitter.on.bind(emitter),
132
- "once": emitter.once.bind(emitter),
133
- "close": () => n.close()
134
- });
135
- };
136
-
137
- module.exports = {
138
- NLM_F_ECHO,
139
- NLM_F_REQUEST,
140
- NLM_F_MULTI,
141
- NLM_F_DUMP,
142
- NLM_F_ACK,
143
- NLM_F_CREATE,
144
- NLM_F_EXCL,
145
-
146
- NLMSG_DONE,
147
- NLMSG_ERROR,
148
-
149
- open
150
- };