node-netlink 0.0.4 → 0.0.6
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 +242 -2
- package/dist/lib/constants.d.ts +47 -0
- package/dist/lib/constants.js +118 -0
- package/dist/lib/errno.d.ts +9 -0
- package/dist/lib/errno.js +27 -0
- package/dist/lib/index.d.ts +12 -0
- package/dist/lib/index.js +52 -0
- package/dist/lib/message.d.ts +45 -0
- package/dist/lib/message.js +130 -0
- package/dist/lib/netlink-socket.d.ts +74 -0
- package/dist/lib/netlink-socket.js +314 -0
- package/dist/lib/po6-transport.d.ts +30 -0
- package/dist/lib/po6-transport.js +175 -0
- package/dist/lib/structures.d.ts +174 -0
- package/dist/lib/structures.js +96 -0
- package/package.json +38 -12
- package/.eslintrc +0 -90
- package/lib/error.js +0 -50
- package/lib/index.js +0 -259
- package/lib/parser.js +0 -55
- package/lib/structures.js +0 -21
- package/samples/test.js +0 -11
- package/test/base.js +0 -20
package/README.md
CHANGED
|
@@ -1,3 +1,243 @@
|
|
|
1
1
|
# node-netlink
|
|
2
|
-
|
|
3
|
-
netlink
|
|
2
|
+
|
|
3
|
+
[](https://github.com/k13-engineering/node-netlink/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/node-netlink)
|
|
5
|
+
|
|
6
|
+
The [netlink](https://man7.org/linux/man-pages/man7/netlink.7.html) protocol for Node.js on Linux, written in TypeScript.
|
|
7
|
+
|
|
8
|
+
- **Bring your own socket.** node-netlink never opens, binds or closes a socket. You pass in a netlink socket you own, and the library speaks the protocol on top of it.
|
|
9
|
+
- **Request/response matching.** `talk()` sends a request, collects all responses including multipart dumps and resolves once the kernel acknowledges it or reports an error.
|
|
10
|
+
- **Synchronous where possible.** Attaching to a socket, querying its address and sending are plain function calls. Only waiting for responses of the kernel returns a promise.
|
|
11
|
+
- **No native code.** The adapter for file descriptors uses the [po6](https://www.npmjs.com/package/po6) API and the poller you pass in, e.g. [@k13engineering/uv-poll](https://www.npmjs.com/package/@k13engineering/uv-poll).
|
|
12
|
+
- **Checked against the C headers.** The layouts of `struct sockaddr_nl`, `struct nlmsghdr` and `struct nlmsgerr` are defined with [ya-struct](https://www.npmjs.com/package/ya-struct) and compared with `<linux/netlink.h>` in the tests, as are all constants.
|
|
13
|
+
|
|
14
|
+
node-netlink handles the netlink framing. The payloads of the individual protocols, e.g. `struct ifinfomsg` and attributes of rtnetlink, are `Uint8Array`s that you format and parse yourself.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Linux
|
|
19
|
+
- Node.js 24 or newer
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install node-netlink
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
To use the included adapter for file descriptors, install po6 with its syscall and memory backends and a poller:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm install po6 syscall-napi buffer2address @k13engineering/uv-poll
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Setup
|
|
36
|
+
|
|
37
|
+
Open and bind the socket with po6, then attach node-netlink to it:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createPoller } from "@k13engineering/uv-poll";
|
|
41
|
+
import { pinBuffer } from "buffer2address";
|
|
42
|
+
import { createKernelAbiFor, createLinuxKernelInterface, createPo6Api, hostAbi } from "po6";
|
|
43
|
+
import { syscall, syscallNumbers } from "syscall-napi";
|
|
44
|
+
import {
|
|
45
|
+
AF_NETLINK,
|
|
46
|
+
NETLINK_ROUTE,
|
|
47
|
+
createNetlinkSocket,
|
|
48
|
+
createPo6NetlinkTransport,
|
|
49
|
+
formatNetlinkAddress,
|
|
50
|
+
} from "node-netlink";
|
|
51
|
+
|
|
52
|
+
const kernelAbi = createKernelAbiFor({ machineAbi: hostAbi });
|
|
53
|
+
const po6 = createPo6Api({
|
|
54
|
+
kernelInterface: createLinuxKernelInterface({ syscall, syscallNumbers }),
|
|
55
|
+
kernelAbi,
|
|
56
|
+
memory: { pinBuffer },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// <linux/net.h> and <asm-generic/fcntl.h>
|
|
60
|
+
const SOCK_RAW = 3n;
|
|
61
|
+
const SOCK_CLOEXEC = 0o2000000n;
|
|
62
|
+
|
|
63
|
+
// your socket: opened, bound and closed by your code
|
|
64
|
+
const { errno, fd } = po6.socket({ domain: AF_NETLINK, type: SOCK_RAW | SOCK_CLOEXEC, protocol: NETLINK_ROUTE });
|
|
65
|
+
if (errno !== undefined) {
|
|
66
|
+
throw po6.createErrorFromErrno({ operation: "socket()", errno });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { errno: bindErrno } = po6.bind({ fd, sockaddr: formatNetlinkAddress({ address: { nl_pid: 0n, nl_groups: 0n } }) });
|
|
70
|
+
if (bindErrno !== undefined) {
|
|
71
|
+
throw po6.createErrorFromErrno({ operation: "bind()", errno: bindErrno });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// the netlink protocol on top of it
|
|
75
|
+
const transport = createPo6NetlinkTransport({ fd, po6, kernelAbi, createPoller });
|
|
76
|
+
const netlink = createNetlinkSocket({ transport });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
When you are done, detach node-netlink and close the socket yourself:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
netlink.detach();
|
|
83
|
+
po6.close({ fd });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Requests
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { NLM_F_DUMP } from "node-netlink";
|
|
90
|
+
|
|
91
|
+
// <linux/rtnetlink.h>
|
|
92
|
+
const RTM_GETLINK = 18n;
|
|
93
|
+
|
|
94
|
+
// dump all network interfaces, the payload is a zeroed struct ifinfomsg
|
|
95
|
+
const links = await netlink.talk({
|
|
96
|
+
header: { nlmsg_type: RTM_GETLINK, nlmsg_flags: NLM_F_DUMP },
|
|
97
|
+
payload: new Uint8Array(16),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
links.forEach(({ header, payload }) => {
|
|
101
|
+
const index = new DataView(payload.buffer, payload.byteOffset).getInt32(4, true);
|
|
102
|
+
console.log(`type ${header.nlmsg_type}, interface index ${index}`);
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`talk()` rejects if the kernel reports an error, e.g. `netlink request of type 16 failed with EPERM`. The error has the `errno` attached. Use `tryTalk()` to get the errno as a value instead:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const { errno, messages } = await netlink.tryTalk({
|
|
110
|
+
header: { nlmsg_type: RTM_GETLINK },
|
|
111
|
+
payload: ifinfomsgOfInterface,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (errno !== undefined) {
|
|
115
|
+
// e.g. 19 (ENODEV) if the interface does not exist
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Notifications
|
|
120
|
+
|
|
121
|
+
Bind the socket to multicast groups, e.g. `nl_groups: 1n` for `RTMGRP_LINK`. Messages that are not responses to a pending request are passed to `onMessage`:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const netlink = createNetlinkSocket({
|
|
125
|
+
transport,
|
|
126
|
+
onMessage: ({ message }) => {
|
|
127
|
+
console.log("link changed", message.header.nlmsg_type);
|
|
128
|
+
},
|
|
129
|
+
onError: ({ error }) => {
|
|
130
|
+
console.error(error);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
While attached, the poller keeps the Node.js process alive.
|
|
136
|
+
|
|
137
|
+
See [examples/](examples/) for complete programs that list interfaces and monitor link changes.
|
|
138
|
+
|
|
139
|
+
## API
|
|
140
|
+
|
|
141
|
+
All functions take a single object of arguments. Header fields are `bigint`s, like the values ya-struct parses.
|
|
142
|
+
|
|
143
|
+
### `createNetlinkSocket({ transport, onMessage?, onError?, structures? })`
|
|
144
|
+
|
|
145
|
+
Attaches the netlink protocol to `transport` and starts listening on it.
|
|
146
|
+
|
|
147
|
+
- `transport`: the socket to use, see [Transports](#transports)
|
|
148
|
+
- `onMessage({ message })`: called for every received message that is not a response to a pending request
|
|
149
|
+
- `onError({ error })`: called for errors while receiving, e.g. `ENOBUFS` when the receive buffer overflowed and messages were lost. Without a handler, such errors are thrown asynchronously as uncaught exceptions.
|
|
150
|
+
- `structures`: layouts of the kernel structures, `hostStructures` by default
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
|
|
154
|
+
| Member | Description |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| `nl_pid`, `nl_groups` | the address the socket is bound to, as reported by the transport |
|
|
157
|
+
| `talk({ header, payload, timeoutMs? })` | sends a request and resolves with all responses, rejects if the kernel reports an error |
|
|
158
|
+
| `tryTalk({ header, payload, timeoutMs? })` | like `talk()`, but resolves with `{ errno, messages }`. `errno` is `undefined` on success |
|
|
159
|
+
| `send({ header, payload })` | sends a message without waiting for a response and returns `{ nlmsg_seq }`. `nlmsg_flags` defaults to `NLM_F_REQUEST`, `nlmsg_seq` to the next sequence number |
|
|
160
|
+
| `detach()` | stops listening and rejects pending requests. The socket stays open, closing it is up to you. Calling it again has no effect |
|
|
161
|
+
|
|
162
|
+
For `talk()` and `tryTalk()`, `header` is `{ nlmsg_type, nlmsg_flags? }`. `NLM_F_REQUEST` and `NLM_F_ACK` are always set, and the sequence number is assigned automatically. A request completes with the acknowledgement (`NLMSG_ERROR`) or, for dumps, with `NLMSG_DONE`. `timeoutMs` defaults to 10 seconds.
|
|
163
|
+
|
|
164
|
+
A message is `{ header: { nlmsg_type, nlmsg_flags, nlmsg_seq, nlmsg_pid }, payload: Uint8Array }`. The responses of `talk()` and `tryTalk()` don't include the terminating `NLMSG_ERROR` or `NLMSG_DONE`.
|
|
165
|
+
|
|
166
|
+
### Transports
|
|
167
|
+
|
|
168
|
+
A transport is the socket node-netlink talks through:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
type TNetlinkTransport = {
|
|
172
|
+
// the address the socket is bound to
|
|
173
|
+
address: { nl_pid: bigint, nl_groups: bigint };
|
|
174
|
+
// sends a datagram to the kernel, throws on failure
|
|
175
|
+
send: (args: { data: Uint8Array }) => void;
|
|
176
|
+
// passes received datagrams to onData until stop() is called
|
|
177
|
+
listen: (args: {
|
|
178
|
+
onData: (args: { data: Uint8Array }) => void;
|
|
179
|
+
onError: (args: { error: Error }) => void;
|
|
180
|
+
}) => { stop: () => void };
|
|
181
|
+
};
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Implement it yourself, e.g. as a fake in tests, or adapt a file descriptor with `createPo6NetlinkTransport({ fd, po6, kernelAbi, createPoller, structures? })`:
|
|
185
|
+
|
|
186
|
+
- `fd`: a netlink socket, opened and bound by you. Blocking and non-blocking sockets both work, as it receives with `MSG_DONTWAIT`.
|
|
187
|
+
- `po6`: an object with `getsockname()`, `sendmsg()`, `recvmsg()` and `createErrorFromErrno()` of the po6 API
|
|
188
|
+
- `kernelAbi`: the po6 kernel ABI, for the `MSG_*` constants and errno values
|
|
189
|
+
- `createPoller({ fd })`: returns a poller with `armOnce({ readable, error })` and `close()`, like `createPoller` of @k13engineering/uv-poll
|
|
190
|
+
|
|
191
|
+
The adapter queries the address with `getsockname()` when it is created and throws if `fd` is not a netlink socket. `listen()` creates a poller, `stop()` closes it. The file descriptor is never closed.
|
|
192
|
+
|
|
193
|
+
### Messages
|
|
194
|
+
|
|
195
|
+
| Function | Description |
|
|
196
|
+
| --- | --- |
|
|
197
|
+
| `parseMessages({ data, structures })` | splits a datagram into its messages, throws if it is malformed |
|
|
198
|
+
| `formatMessage({ message, structures })` | formats a message, `nlmsg_len` is calculated |
|
|
199
|
+
| `errnoOfMessage({ message, structures })` | the errno of an `NLMSG_ERROR` or `NLMSG_DONE`, `undefined` for acknowledgements and other messages |
|
|
200
|
+
| `nlmsgAlign({ length })` | rounds up to the netlink alignment of 4 bytes |
|
|
201
|
+
| `formatNetlinkAddress({ address, structures? })` | formats a `struct sockaddr_nl`, e.g. for `bind()` |
|
|
202
|
+
| `createErrorFromErrno({ operation, errno })` | creates the errors `talk()` rejects with |
|
|
203
|
+
|
|
204
|
+
Pass `hostStructures` as `structures`, or `createNetlinkStructuresFor({ abi })` for another byte order. The ya-struct definitions are exported as `sockaddrNlDefinition`, `nlmsghdrDefinition` and `nlmsgerrDefinition`.
|
|
205
|
+
|
|
206
|
+
### Constants
|
|
207
|
+
|
|
208
|
+
`AF_NETLINK`, the protocol families `NETLINK_*`, the header flags `NLM_F_*` and the message types `NLMSG_*` of `<linux/netlink.h>` are exported as `bigint`s.
|
|
209
|
+
|
|
210
|
+
## Migrating from 0.0.x
|
|
211
|
+
|
|
212
|
+
- node-netlink no longer creates sockets. Replace `netlink.open({ family, pid, groups })` with your own `socket()` and `bind()`, see [Setup](#setup), and `createNetlinkSocket({ transport })`. The new function is synchronous.
|
|
213
|
+
- `close()` is replaced by `detach()`, which leaves the socket open. Close it yourself afterwards.
|
|
214
|
+
- The package is an ES module with named exports, there is no default export anymore.
|
|
215
|
+
- `talk(message, { timeout })` is now `talk({ header, payload, timeoutMs })`. `NLM_F_REQUEST` and `NLM_F_ACK` are set automatically instead of being required.
|
|
216
|
+
- `tryTalk()` resolves with `{ errno, messages }` instead of `{ errorCode, packets }`. `errno` is `undefined` instead of `0` on success.
|
|
217
|
+
- `on("message", listener)` is replaced by the `onMessage` and `onError` options. Responses to pending requests are no longer passed to it.
|
|
218
|
+
- `createErrorFromErrorCode({ errorCode })` is now `createErrorFromErrno({ operation, errno })`.
|
|
219
|
+
- po6-socket was replaced by po6, which needs no native code of its own.
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
npm ci
|
|
225
|
+
npm run build # transpile to dist/
|
|
226
|
+
npm run type-check
|
|
227
|
+
npm run test # mocha with c8, 100% coverage required
|
|
228
|
+
npm run lint
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The sources are in `lib/`, tests are next to them as `*.spec.ts`:
|
|
232
|
+
|
|
233
|
+
- `lib/netlink-socket.ts`: requests, responses and sequence numbers on top of an injected transport
|
|
234
|
+
- `lib/po6-transport.ts`: the transport for a file descriptor, using injected po6 syscalls and an injected poller
|
|
235
|
+
- `lib/message.ts`, `lib/structures.ts`, `lib/constants.ts`, `lib/errno.ts`: framing, structure layouts, constants and errors
|
|
236
|
+
|
|
237
|
+
The unit tests use the fakes in `lib/test-support/`. `lib/index.spec.ts` opens real sockets with `lib/test-support/host-socket.ts` and talks to the kernel of the host via `NETLINK_ROUTE`, which needs no privileges. The structure layouts and constants are compared with the C headers by compiling C programs, so `gcc` and the Linux headers are required.
|
|
238
|
+
|
|
239
|
+
Releases are published by pushing a tag like `v0.1.0`, which builds the package, merges `package.npm.json` into `package.json` and sets the version.
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
LGPL-2.1, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
declare const AF_NETLINK = 16n;
|
|
2
|
+
declare const NETLINK_ROUTE = 0n;
|
|
3
|
+
declare const NETLINK_UNUSED = 1n;
|
|
4
|
+
declare const NETLINK_USERSOCK = 2n;
|
|
5
|
+
declare const NETLINK_FIREWALL = 3n;
|
|
6
|
+
declare const NETLINK_SOCK_DIAG = 4n;
|
|
7
|
+
declare const NETLINK_NFLOG = 5n;
|
|
8
|
+
declare const NETLINK_XFRM = 6n;
|
|
9
|
+
declare const NETLINK_SELINUX = 7n;
|
|
10
|
+
declare const NETLINK_ISCSI = 8n;
|
|
11
|
+
declare const NETLINK_AUDIT = 9n;
|
|
12
|
+
declare const NETLINK_FIB_LOOKUP = 10n;
|
|
13
|
+
declare const NETLINK_CONNECTOR = 11n;
|
|
14
|
+
declare const NETLINK_NETFILTER = 12n;
|
|
15
|
+
declare const NETLINK_IP6_FW = 13n;
|
|
16
|
+
declare const NETLINK_DNRTMSG = 14n;
|
|
17
|
+
declare const NETLINK_KOBJECT_UEVENT = 15n;
|
|
18
|
+
declare const NETLINK_GENERIC = 16n;
|
|
19
|
+
declare const NETLINK_SCSITRANSPORT = 18n;
|
|
20
|
+
declare const NETLINK_ECRYPTFS = 19n;
|
|
21
|
+
declare const NETLINK_RDMA = 20n;
|
|
22
|
+
declare const NETLINK_CRYPTO = 21n;
|
|
23
|
+
declare const NETLINK_SMC = 22n;
|
|
24
|
+
declare const NLM_F_REQUEST = 1n;
|
|
25
|
+
declare const NLM_F_MULTI = 2n;
|
|
26
|
+
declare const NLM_F_ACK = 4n;
|
|
27
|
+
declare const NLM_F_ECHO = 8n;
|
|
28
|
+
declare const NLM_F_DUMP_INTR = 16n;
|
|
29
|
+
declare const NLM_F_DUMP_FILTERED = 32n;
|
|
30
|
+
declare const NLM_F_ROOT = 256n;
|
|
31
|
+
declare const NLM_F_MATCH = 512n;
|
|
32
|
+
declare const NLM_F_ATOMIC = 1024n;
|
|
33
|
+
declare const NLM_F_DUMP = 768n;
|
|
34
|
+
declare const NLM_F_REPLACE = 256n;
|
|
35
|
+
declare const NLM_F_EXCL = 512n;
|
|
36
|
+
declare const NLM_F_CREATE = 1024n;
|
|
37
|
+
declare const NLM_F_APPEND = 2048n;
|
|
38
|
+
declare const NLM_F_NONREC = 256n;
|
|
39
|
+
declare const NLM_F_BULK = 512n;
|
|
40
|
+
declare const NLM_F_CAPPED = 256n;
|
|
41
|
+
declare const NLM_F_ACK_TLVS = 512n;
|
|
42
|
+
declare const NLMSG_NOOP = 1n;
|
|
43
|
+
declare const NLMSG_ERROR = 2n;
|
|
44
|
+
declare const NLMSG_DONE = 3n;
|
|
45
|
+
declare const NLMSG_OVERRUN = 4n;
|
|
46
|
+
declare const NLMSG_MIN_TYPE = 16n;
|
|
47
|
+
export { AF_NETLINK, NETLINK_ROUTE, NETLINK_UNUSED, NETLINK_USERSOCK, NETLINK_FIREWALL, NETLINK_SOCK_DIAG, NETLINK_NFLOG, NETLINK_XFRM, NETLINK_SELINUX, NETLINK_ISCSI, NETLINK_AUDIT, NETLINK_FIB_LOOKUP, NETLINK_CONNECTOR, NETLINK_NETFILTER, NETLINK_IP6_FW, NETLINK_DNRTMSG, NETLINK_KOBJECT_UEVENT, NETLINK_GENERIC, NETLINK_SCSITRANSPORT, NETLINK_ECRYPTFS, NETLINK_RDMA, NETLINK_CRYPTO, NETLINK_SMC, NLM_F_REQUEST, NLM_F_MULTI, NLM_F_ACK, NLM_F_ECHO, NLM_F_DUMP_INTR, NLM_F_DUMP_FILTERED, NLM_F_ROOT, NLM_F_MATCH, NLM_F_ATOMIC, NLM_F_DUMP, NLM_F_REPLACE, NLM_F_EXCL, NLM_F_CREATE, NLM_F_APPEND, NLM_F_NONREC, NLM_F_BULK, NLM_F_CAPPED, NLM_F_ACK_TLVS, NLMSG_NOOP, NLMSG_ERROR, NLMSG_DONE, NLMSG_OVERRUN, NLMSG_MIN_TYPE, };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// values from <linux/netlink.h> and <linux/socket.h>, shared by all Linux architectures
|
|
2
|
+
|
|
3
|
+
const AF_NETLINK = 16n;
|
|
4
|
+
|
|
5
|
+
// netlink protocol families, passed as `family` when opening a socket
|
|
6
|
+
const NETLINK_ROUTE = 0n;
|
|
7
|
+
const NETLINK_UNUSED = 1n;
|
|
8
|
+
const NETLINK_USERSOCK = 2n;
|
|
9
|
+
const NETLINK_FIREWALL = 3n;
|
|
10
|
+
const NETLINK_SOCK_DIAG = 4n;
|
|
11
|
+
const NETLINK_NFLOG = 5n;
|
|
12
|
+
const NETLINK_XFRM = 6n;
|
|
13
|
+
const NETLINK_SELINUX = 7n;
|
|
14
|
+
const NETLINK_ISCSI = 8n;
|
|
15
|
+
const NETLINK_AUDIT = 9n;
|
|
16
|
+
const NETLINK_FIB_LOOKUP = 10n;
|
|
17
|
+
const NETLINK_CONNECTOR = 11n;
|
|
18
|
+
const NETLINK_NETFILTER = 12n;
|
|
19
|
+
const NETLINK_IP6_FW = 13n;
|
|
20
|
+
const NETLINK_DNRTMSG = 14n;
|
|
21
|
+
const NETLINK_KOBJECT_UEVENT = 15n;
|
|
22
|
+
const NETLINK_GENERIC = 16n;
|
|
23
|
+
const NETLINK_SCSITRANSPORT = 18n;
|
|
24
|
+
const NETLINK_ECRYPTFS = 19n;
|
|
25
|
+
const NETLINK_RDMA = 20n;
|
|
26
|
+
const NETLINK_CRYPTO = 21n;
|
|
27
|
+
const NETLINK_SMC = 22n;
|
|
28
|
+
|
|
29
|
+
// flags of struct nlmsghdr
|
|
30
|
+
const NLM_F_REQUEST = 0x01n;
|
|
31
|
+
const NLM_F_MULTI = 0x02n;
|
|
32
|
+
const NLM_F_ACK = 0x04n;
|
|
33
|
+
const NLM_F_ECHO = 0x08n;
|
|
34
|
+
const NLM_F_DUMP_INTR = 0x10n;
|
|
35
|
+
const NLM_F_DUMP_FILTERED = 0x20n;
|
|
36
|
+
|
|
37
|
+
// modifiers to GET requests
|
|
38
|
+
const NLM_F_ROOT = 0x100n;
|
|
39
|
+
const NLM_F_MATCH = 0x200n;
|
|
40
|
+
const NLM_F_ATOMIC = 0x400n;
|
|
41
|
+
const NLM_F_DUMP = 0x300n;
|
|
42
|
+
|
|
43
|
+
// modifiers to NEW requests
|
|
44
|
+
const NLM_F_REPLACE = 0x100n;
|
|
45
|
+
const NLM_F_EXCL = 0x200n;
|
|
46
|
+
const NLM_F_CREATE = 0x400n;
|
|
47
|
+
const NLM_F_APPEND = 0x800n;
|
|
48
|
+
|
|
49
|
+
// modifiers to DELETE requests
|
|
50
|
+
const NLM_F_NONREC = 0x100n;
|
|
51
|
+
const NLM_F_BULK = 0x200n;
|
|
52
|
+
|
|
53
|
+
// flags for ACK messages
|
|
54
|
+
const NLM_F_CAPPED = 0x100n;
|
|
55
|
+
const NLM_F_ACK_TLVS = 0x200n;
|
|
56
|
+
|
|
57
|
+
// reserved control message types
|
|
58
|
+
const NLMSG_NOOP = 0x1n;
|
|
59
|
+
const NLMSG_ERROR = 0x2n;
|
|
60
|
+
const NLMSG_DONE = 0x3n;
|
|
61
|
+
const NLMSG_OVERRUN = 0x4n;
|
|
62
|
+
const NLMSG_MIN_TYPE = 0x10n;
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
AF_NETLINK,
|
|
66
|
+
|
|
67
|
+
NETLINK_ROUTE,
|
|
68
|
+
NETLINK_UNUSED,
|
|
69
|
+
NETLINK_USERSOCK,
|
|
70
|
+
NETLINK_FIREWALL,
|
|
71
|
+
NETLINK_SOCK_DIAG,
|
|
72
|
+
NETLINK_NFLOG,
|
|
73
|
+
NETLINK_XFRM,
|
|
74
|
+
NETLINK_SELINUX,
|
|
75
|
+
NETLINK_ISCSI,
|
|
76
|
+
NETLINK_AUDIT,
|
|
77
|
+
NETLINK_FIB_LOOKUP,
|
|
78
|
+
NETLINK_CONNECTOR,
|
|
79
|
+
NETLINK_NETFILTER,
|
|
80
|
+
NETLINK_IP6_FW,
|
|
81
|
+
NETLINK_DNRTMSG,
|
|
82
|
+
NETLINK_KOBJECT_UEVENT,
|
|
83
|
+
NETLINK_GENERIC,
|
|
84
|
+
NETLINK_SCSITRANSPORT,
|
|
85
|
+
NETLINK_ECRYPTFS,
|
|
86
|
+
NETLINK_RDMA,
|
|
87
|
+
NETLINK_CRYPTO,
|
|
88
|
+
NETLINK_SMC,
|
|
89
|
+
|
|
90
|
+
NLM_F_REQUEST,
|
|
91
|
+
NLM_F_MULTI,
|
|
92
|
+
NLM_F_ACK,
|
|
93
|
+
NLM_F_ECHO,
|
|
94
|
+
NLM_F_DUMP_INTR,
|
|
95
|
+
NLM_F_DUMP_FILTERED,
|
|
96
|
+
|
|
97
|
+
NLM_F_ROOT,
|
|
98
|
+
NLM_F_MATCH,
|
|
99
|
+
NLM_F_ATOMIC,
|
|
100
|
+
NLM_F_DUMP,
|
|
101
|
+
|
|
102
|
+
NLM_F_REPLACE,
|
|
103
|
+
NLM_F_EXCL,
|
|
104
|
+
NLM_F_CREATE,
|
|
105
|
+
NLM_F_APPEND,
|
|
106
|
+
|
|
107
|
+
NLM_F_NONREC,
|
|
108
|
+
NLM_F_BULK,
|
|
109
|
+
|
|
110
|
+
NLM_F_CAPPED,
|
|
111
|
+
NLM_F_ACK_TLVS,
|
|
112
|
+
|
|
113
|
+
NLMSG_NOOP,
|
|
114
|
+
NLMSG_ERROR,
|
|
115
|
+
NLMSG_DONE,
|
|
116
|
+
NLMSG_OVERRUN,
|
|
117
|
+
NLMSG_MIN_TYPE,
|
|
118
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createKernelAbiFor, hostAbi } from "po6";
|
|
2
|
+
|
|
3
|
+
/*type TErrorWithErrno = Error & {
|
|
4
|
+
errno: number;
|
|
5
|
+
};*/
|
|
6
|
+
|
|
7
|
+
const { errnoCodes } = createKernelAbiFor({ machineAbi: hostAbi });
|
|
8
|
+
|
|
9
|
+
// aliases like EWOULDBLOCK follow their canonical name, so the first name of an errno wins
|
|
10
|
+
const errnoNames = Object.entries(errnoCodes).reduce((names, [name, errno]) => {
|
|
11
|
+
return names.has(errno) ? names : new Map([...names, [errno, name]]);
|
|
12
|
+
}, new Map/*<number, string>*/());
|
|
13
|
+
|
|
14
|
+
const createErrorFromErrno = ({ operation, errno }/*: { operation: string, errno: number }*/)/*: TErrorWithErrno*/ => {
|
|
15
|
+
const name = errnoNames.get(errno) ?? `unknown errno ${errno}`;
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line fp/no-mutating-assign
|
|
18
|
+
return Object.assign(Error(`${operation} failed with ${name}`), { errno });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
createErrorFromErrno,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/*export type {
|
|
26
|
+
TErrorWithErrno,
|
|
27
|
+
};*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createErrorFromErrno } from "./errno.js";
|
|
2
|
+
import { createNetlinkSocket } from "./netlink-socket.js";
|
|
3
|
+
import { createPo6NetlinkTransport } from "./po6-transport.js";
|
|
4
|
+
import { errnoOfMessage, formatMessage, nlmsgAlign, parseMessages } from "./message.js";
|
|
5
|
+
import { createNetlinkStructuresFor, formatNetlinkAddress, hostStructures, nlmsgerrDefinition, nlmsghdrDefinition, sockaddrNlDefinition } from "./structures.js";
|
|
6
|
+
export * from "./constants.js";
|
|
7
|
+
export { createNetlinkSocket, createPo6NetlinkTransport, parseMessages, formatMessage, errnoOfMessage, nlmsgAlign, createErrorFromErrno, formatNetlinkAddress, createNetlinkStructuresFor, hostStructures, sockaddrNlDefinition, nlmsghdrDefinition, nlmsgerrDefinition, };
|
|
8
|
+
export type { TNetlinkAddress, TNetlinkTransport, TNetlinkSocket, TCreateNetlinkSocketArgs, TTalkArgs, TTryTalkResult, TRequestHeader, TSendHeader, } from "./netlink-socket.ts";
|
|
9
|
+
export type { TNetlinkHeader, TNetlinkMessage } from "./message.ts";
|
|
10
|
+
export type { TNetlinkStructures } from "./structures.ts";
|
|
11
|
+
export type { TErrorWithErrno } from "./errno.ts";
|
|
12
|
+
export type { TPo6NetlinkSyscalls, TPoller, TCreatePoller, TCreatePo6NetlinkTransportArgs, } from "./po6-transport.ts";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createErrorFromErrno } from "./errno.js";
|
|
2
|
+
import { createNetlinkSocket } from "./netlink-socket.js";
|
|
3
|
+
import { createPo6NetlinkTransport } from "./po6-transport.js";
|
|
4
|
+
import { errnoOfMessage, formatMessage, nlmsgAlign, parseMessages } from "./message.js";
|
|
5
|
+
import {
|
|
6
|
+
createNetlinkStructuresFor,
|
|
7
|
+
formatNetlinkAddress,
|
|
8
|
+
hostStructures,
|
|
9
|
+
nlmsgerrDefinition,
|
|
10
|
+
nlmsghdrDefinition,
|
|
11
|
+
sockaddrNlDefinition
|
|
12
|
+
} from "./structures.js";
|
|
13
|
+
|
|
14
|
+
export * from "./constants.js";
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
createNetlinkSocket,
|
|
18
|
+
createPo6NetlinkTransport,
|
|
19
|
+
|
|
20
|
+
parseMessages,
|
|
21
|
+
formatMessage,
|
|
22
|
+
errnoOfMessage,
|
|
23
|
+
nlmsgAlign,
|
|
24
|
+
createErrorFromErrno,
|
|
25
|
+
|
|
26
|
+
formatNetlinkAddress,
|
|
27
|
+
createNetlinkStructuresFor,
|
|
28
|
+
hostStructures,
|
|
29
|
+
sockaddrNlDefinition,
|
|
30
|
+
nlmsghdrDefinition,
|
|
31
|
+
nlmsgerrDefinition,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/*export type {
|
|
35
|
+
TNetlinkAddress,
|
|
36
|
+
TNetlinkTransport,
|
|
37
|
+
TNetlinkSocket,
|
|
38
|
+
TCreateNetlinkSocketArgs,
|
|
39
|
+
TTalkArgs,
|
|
40
|
+
TTryTalkResult,
|
|
41
|
+
TRequestHeader,
|
|
42
|
+
TSendHeader,
|
|
43
|
+
} from "./netlink-socket.ts";*/
|
|
44
|
+
/*export type { TNetlinkHeader, TNetlinkMessage } from "./message.ts";*/
|
|
45
|
+
/*export type { TNetlinkStructures } from "./structures.ts";*/
|
|
46
|
+
/*export type { TErrorWithErrno } from "./errno.ts";*/
|
|
47
|
+
/*export type {
|
|
48
|
+
TPo6NetlinkSyscalls,
|
|
49
|
+
TPoller,
|
|
50
|
+
TCreatePoller,
|
|
51
|
+
TCreatePo6NetlinkTransportArgs,
|
|
52
|
+
} from "./po6-transport.ts";*/
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TNetlinkStructures } from "./structures.js";
|
|
2
|
+
type TNetlinkHeader = {
|
|
3
|
+
nlmsg_type: bigint;
|
|
4
|
+
nlmsg_flags: bigint;
|
|
5
|
+
nlmsg_seq: bigint;
|
|
6
|
+
nlmsg_pid: bigint;
|
|
7
|
+
};
|
|
8
|
+
type TNetlinkMessage = {
|
|
9
|
+
header: TNetlinkHeader;
|
|
10
|
+
payload: Uint8Array;
|
|
11
|
+
};
|
|
12
|
+
declare const nlmsgAlign: ({ length }: {
|
|
13
|
+
length: number;
|
|
14
|
+
}) => number;
|
|
15
|
+
/**
|
|
16
|
+
* Splits a received datagram into its netlink messages.
|
|
17
|
+
* Throws if the datagram is malformed.
|
|
18
|
+
*/
|
|
19
|
+
declare const parseMessages: ({ data, structures }: {
|
|
20
|
+
data: Uint8Array;
|
|
21
|
+
structures: TNetlinkStructures;
|
|
22
|
+
}) => TNetlinkMessage[];
|
|
23
|
+
/**
|
|
24
|
+
* Formats a netlink message, nlmsg_len is calculated from the payload.
|
|
25
|
+
*/
|
|
26
|
+
declare const formatMessage: ({ message, structures }: {
|
|
27
|
+
message: TNetlinkMessage;
|
|
28
|
+
structures: TNetlinkStructures;
|
|
29
|
+
}) => Uint8Array;
|
|
30
|
+
/**
|
|
31
|
+
* Whether the message terminates a request: NLMSG_ERROR (including acknowledgements) or NLMSG_DONE.
|
|
32
|
+
*/
|
|
33
|
+
declare const isTerminatingMessage: ({ message }: {
|
|
34
|
+
message: TNetlinkMessage;
|
|
35
|
+
}) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Extracts the errno of an NLMSG_ERROR or NLMSG_DONE message.
|
|
38
|
+
* Returns `undefined` for an acknowledgement (error 0) and for other message types.
|
|
39
|
+
*/
|
|
40
|
+
declare const errnoOfMessage: ({ message, structures }: {
|
|
41
|
+
message: TNetlinkMessage;
|
|
42
|
+
structures: TNetlinkStructures;
|
|
43
|
+
}) => number | undefined;
|
|
44
|
+
export { nlmsgAlign, parseMessages, formatMessage, errnoOfMessage, isTerminatingMessage, };
|
|
45
|
+
export type { TNetlinkHeader, TNetlinkMessage, };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { NLMSG_DONE, NLMSG_ERROR } from "./constants.js";
|
|
2
|
+
/*import type { TNetlinkStructures } from "./structures.ts";*/
|
|
3
|
+
|
|
4
|
+
/*type TNetlinkHeader = {
|
|
5
|
+
nlmsg_type: bigint;
|
|
6
|
+
nlmsg_flags: bigint;
|
|
7
|
+
nlmsg_seq: bigint;
|
|
8
|
+
nlmsg_pid: bigint;
|
|
9
|
+
};*/
|
|
10
|
+
|
|
11
|
+
/*type TNetlinkMessage = {
|
|
12
|
+
header: TNetlinkHeader;
|
|
13
|
+
payload: Uint8Array;
|
|
14
|
+
};*/
|
|
15
|
+
|
|
16
|
+
const NLMSG_ALIGNTO = 4;
|
|
17
|
+
|
|
18
|
+
const nlmsgAlign = ({ length }/*: { length: number }*/) => {
|
|
19
|
+
return Math.ceil(length / NLMSG_ALIGNTO) * NLMSG_ALIGNTO;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const parseOneMessage = ({ data, structures }/*: { data: Uint8Array, structures: TNetlinkStructures }*/) => {
|
|
23
|
+
const headerSize = structures.nlmsghdr.size;
|
|
24
|
+
|
|
25
|
+
if (data.length < headerSize) {
|
|
26
|
+
throw Error(`malformed netlink message: ${data.length} bytes left, but a header needs ${headerSize} bytes`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { nlmsg_len, ...header } = structures.nlmsghdr.parse({ data: data.subarray(0, headerSize) });
|
|
30
|
+
const messageLength = Number(nlmsg_len);
|
|
31
|
+
|
|
32
|
+
if (messageLength < headerSize || messageLength > data.length) {
|
|
33
|
+
throw Error(`malformed netlink message: nlmsg_len ${messageLength} is out of range [${headerSize}, ${data.length}]`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// copy, so the payload does not keep the whole receive buffer alive
|
|
37
|
+
const payload = data.slice(headerSize, messageLength);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
message: { header, payload },
|
|
41
|
+
space: nlmsgAlign({ length: messageLength }),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Splits a received datagram into its netlink messages.
|
|
47
|
+
* Throws if the datagram is malformed.
|
|
48
|
+
*/
|
|
49
|
+
const parseMessages = ({ data, structures }/*: { data: Uint8Array, structures: TNetlinkStructures }*/)/*: TNetlinkMessage[]*/ => {
|
|
50
|
+
let messages/*: TNetlinkMessage[]*/ = [];
|
|
51
|
+
let remaining = data;
|
|
52
|
+
|
|
53
|
+
while (remaining.length > 0) {
|
|
54
|
+
const { message, space } = parseOneMessage({ data: remaining, structures });
|
|
55
|
+
messages = [...messages, message];
|
|
56
|
+
remaining = remaining.subarray(space);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return messages;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Formats a netlink message, nlmsg_len is calculated from the payload.
|
|
64
|
+
*/
|
|
65
|
+
const formatMessage = ({ message, structures }/*: { message: TNetlinkMessage, structures: TNetlinkStructures }*/)/*: Uint8Array*/ => {
|
|
66
|
+
const { header, payload } = message;
|
|
67
|
+
|
|
68
|
+
const headerAsBuffer = structures.nlmsghdr.format({
|
|
69
|
+
value: {
|
|
70
|
+
...header,
|
|
71
|
+
nlmsg_len: BigInt(structures.nlmsghdr.size + payload.length),
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const result = new Uint8Array(headerAsBuffer.length + payload.length);
|
|
76
|
+
result.set(headerAsBuffer, 0);
|
|
77
|
+
result.set(payload, headerAsBuffer.length);
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether the message terminates a request: NLMSG_ERROR (including acknowledgements) or NLMSG_DONE.
|
|
83
|
+
*/
|
|
84
|
+
const isTerminatingMessage = ({ message }/*: { message: TNetlinkMessage }*/) => {
|
|
85
|
+
const { nlmsg_type } = message.header;
|
|
86
|
+
return nlmsg_type === NLMSG_ERROR || nlmsg_type === NLMSG_DONE;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const readInt32 = ({ payload, structures }/*: { payload: Uint8Array, structures: TNetlinkStructures }*/) => {
|
|
90
|
+
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
91
|
+
return view.getInt32(0, structures.abi.endianness === "little");
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Extracts the errno of an NLMSG_ERROR or NLMSG_DONE message.
|
|
96
|
+
* Returns `undefined` for an acknowledgement (error 0) and for other message types.
|
|
97
|
+
*/
|
|
98
|
+
const errnoOfMessage = ({ message, structures }/*: { message: TNetlinkMessage, structures: TNetlinkStructures }*/)/*: number | undefined*/ => {
|
|
99
|
+
if (!isTerminatingMessage({ message })) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { header, payload } = message;
|
|
104
|
+
|
|
105
|
+
// NLMSG_DONE carries an int, NLMSG_ERROR a struct nlmsgerr starting with an int
|
|
106
|
+
if (payload.length < 4) {
|
|
107
|
+
throw Error(`malformed netlink message: payload of type ${header.nlmsg_type} too short for an error code`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const error = readInt32({ payload, structures });
|
|
111
|
+
|
|
112
|
+
if (error === 0) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return -error;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
nlmsgAlign,
|
|
121
|
+
parseMessages,
|
|
122
|
+
formatMessage,
|
|
123
|
+
errnoOfMessage,
|
|
124
|
+
isTerminatingMessage,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/*export type {
|
|
128
|
+
TNetlinkHeader,
|
|
129
|
+
TNetlinkMessage,
|
|
130
|
+
};*/
|