@amritk/nish-x86_64-linux 0.15.0 → 0.17.0
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/INSTALL.md +58 -40
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +67 -10
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +72 -26
- package/runtime/runtime-net.c +72 -19
- package/runtime/runtime-os.c +127 -45
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +26 -2
- package/runtime/runtime.c +86 -14
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +192 -36
- package/std/crypto/aes.ts +104 -21
- package/std/crypto/base64url.ts +43 -17
- package/std/crypto/chacha20poly1305.ts +52 -31
- package/std/crypto/ct.ts +14 -3
- package/std/crypto/hkdf.ts +385 -8
- package/std/crypto/hmac.ts +325 -9
- package/std/crypto/p256.ts +153 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +15 -3
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +90 -37
- package/std/crypto/x509.ts +265 -65
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
package/runtime/nish.mjs
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
* That is enough for a real and useful subset — **f64 mode**, where JavaScript's
|
|
17
17
|
* `+ - * / %` on doubles *are* `fadd/fsub/fmul/fdiv/frem` and `Math.*` are the
|
|
18
18
|
* same IEEE operations, so the arithmetic needs no help. It is not enough for
|
|
19
|
-
* i32 mode, and it never will be: `number` is a
|
|
20
|
-
* and every operator would have to change.
|
|
19
|
+
* i32 mode, and it never will be: `number` is a 32-bit integer there, whose
|
|
20
|
+
* signed overflow panics, and every operator would have to change.
|
|
21
21
|
*
|
|
22
22
|
* `docs/RUN_UNDER_NODE.md` states the overlap and the whole list of what stays
|
|
23
23
|
* divergent. The short version, because it belongs next to the code too:
|
|
24
24
|
*
|
|
25
|
-
* - **i32 mode is out.**
|
|
26
|
-
* panic are all operator-level and cannot be reached from here.
|
|
25
|
+
* - **i32 mode is out.** The overflow panic, truncating `/`, and the
|
|
26
|
+
* divide-by-zero panic are all operator-level and cannot be reached from here.
|
|
27
27
|
* - **`s.length` is UTF-16 units under Node** and UTF-8 bytes natively, and
|
|
28
28
|
* so is every string offset. ASCII agrees; nothing else does.
|
|
29
29
|
* - **`a[i]` is unchecked**: out of range is `undefined` here and an exit-1
|
|
@@ -109,6 +109,8 @@ for (const name of [
|
|
|
109
109
|
"netWrite",
|
|
110
110
|
"netShutdown",
|
|
111
111
|
"netClose",
|
|
112
|
+
"tcpConnect",
|
|
113
|
+
"connectResult",
|
|
112
114
|
"udpBind",
|
|
113
115
|
"udpSendTo",
|
|
114
116
|
"udpRecvFrom",
|
|
@@ -174,6 +176,49 @@ provide("bitsToF64", shim.bitsToF64);
|
|
|
174
176
|
// agree with a native run; the timing does not, and is not claimed here.
|
|
175
177
|
provide("ctSelect", shim.ctSelect);
|
|
176
178
|
provide("ctEq", shim.ctEq);
|
|
179
|
+
// `secureZero` (#385): the bytes are zero afterwards here too, though what a
|
|
180
|
+
// collector may have copied before is out of reach.
|
|
181
|
+
provide("secureZero", shim.secureZero);
|
|
182
|
+
// The RT-9 owner checks (#386): one `lstat`, the effective uid and
|
|
183
|
+
// `access(X_OK)`, answered as `runtime-host.c` answers them.
|
|
184
|
+
provide("lstatOwnerModeSync", shim.lstatOwnerModeSync);
|
|
185
|
+
provide("geteuid", shim.geteuid);
|
|
186
|
+
provide("isExecutableSync", shim.isExecutableSync);
|
|
187
|
+
|
|
188
|
+
// `nish:unsafe`. The element access is JavaScript's own, which never checks
|
|
189
|
+
// anything; the wraps are the two's-complement ones the native instructions
|
|
190
|
+
// perform: `| 0` and `Math.imul` at `i32`, and `BigInt.asIntN(64, ...)` on
|
|
191
|
+
// the BigInt an `i64` is here. Pure functions of their operands, so a program
|
|
192
|
+
// that stays in range agrees with a native run.
|
|
193
|
+
const wrapAt = (a, b, wide, narrow) => (typeof a === "bigint" ? BigInt.asIntN(64, wide(a, b)) : narrow(a, b));
|
|
194
|
+
provide("uncheckedGet", (xs, i) => xs[i]);
|
|
195
|
+
provide("uncheckedSet", (xs, i, v) => {
|
|
196
|
+
xs[i] = v;
|
|
197
|
+
});
|
|
198
|
+
provide("wrappingAdd", (a, b) =>
|
|
199
|
+
wrapAt(
|
|
200
|
+
a,
|
|
201
|
+
b,
|
|
202
|
+
(x, y) => x + y,
|
|
203
|
+
(x, y) => (x + y) | 0
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
provide("wrappingSub", (a, b) =>
|
|
207
|
+
wrapAt(
|
|
208
|
+
a,
|
|
209
|
+
b,
|
|
210
|
+
(x, y) => x - y,
|
|
211
|
+
(x, y) => (x - y) | 0
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
provide("wrappingMul", (a, b) =>
|
|
215
|
+
wrapAt(
|
|
216
|
+
a,
|
|
217
|
+
b,
|
|
218
|
+
(x, y) => x * y,
|
|
219
|
+
(x, y) => Math.imul(x, y)
|
|
220
|
+
)
|
|
221
|
+
);
|
|
177
222
|
|
|
178
223
|
// `Result`. Everything works but `orReturn`, which needs the caller's control
|
|
179
224
|
// flow and therefore the rewriter; see the header.
|
|
@@ -200,15 +245,34 @@ provide("Arena", {
|
|
|
200
245
|
reset: shim.arenaReset,
|
|
201
246
|
used: shim.arenaUsed,
|
|
202
247
|
});
|
|
248
|
+
// `using a = arena()` releases nothing here either: what the block allocated
|
|
249
|
+
// is the garbage collector's, and the disposal is a no-op.
|
|
250
|
+
provide("arena", shim.arena);
|
|
203
251
|
|
|
204
252
|
// The standard library. A program imports it as `nish/<module>`, which the
|
|
205
253
|
// compiler resolves to `std/<module>.ts` beside itself; this does the same for
|
|
206
254
|
// Node, relative to this file rather than to the program, so the same
|
|
207
255
|
// specifier works from any directory. Only the `nish/` package is answered
|
|
208
256
|
// here, and every other specifier goes to Node as it was written.
|
|
257
|
+
//
|
|
258
|
+
// `nish:secret` is answered here as well, because the standard library imports
|
|
259
|
+
// it (`std/crypto/p256.ts` and its neighbours): its names are
|
|
260
|
+
// `runtime/shim.mjs`'s, where a `Secret` is a plain wrapper and `wipe`
|
|
261
|
+
// zero-fills it. `nish:unsafe` is answered by a module that hands back the
|
|
262
|
+
// globals installed above: its functions are globals of this module either
|
|
263
|
+
// way, and the import is what the compiler requires a program to write.
|
|
264
|
+
const UNSAFE_EXPORTS = ["uncheckedGet", "uncheckedSet", "wrappingAdd", "wrappingSub", "wrappingMul"];
|
|
265
|
+
const unsafeSource = UNSAFE_EXPORTS.map((name) => `export const ${name} = globalThis.${name};`).join("\n");
|
|
209
266
|
registerHooks({
|
|
210
|
-
resolve: (specifier, context, next) =>
|
|
211
|
-
specifier
|
|
267
|
+
resolve: (specifier, context, next) => {
|
|
268
|
+
if (specifier === "nish:secret") {
|
|
269
|
+
return next(new URL("./shim.mjs", import.meta.url).href, context);
|
|
270
|
+
}
|
|
271
|
+
if (specifier === "nish:unsafe") {
|
|
272
|
+
return { url: `data:text/javascript,${encodeURIComponent(unsafeSource)}`, shortCircuit: true };
|
|
273
|
+
}
|
|
274
|
+
return specifier.startsWith("nish/")
|
|
212
275
|
? next(new URL(`../std/${specifier.slice("nish/".length)}.ts`, import.meta.url).href, context)
|
|
213
|
-
: next(specifier, context)
|
|
276
|
+
: next(specifier, context);
|
|
277
|
+
},
|
|
214
278
|
});
|
package/runtime/runtime-host.c
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* Nish runtime, the host half: the wall clock, entropy, a file's modification
|
|
2
2
|
* time and a descriptor that becomes readable when SIGTERM or SIGINT arrives
|
|
3
3
|
* (WP34 N3) — the four facts a program that runs for days asks of the machine
|
|
4
|
-
* it runs on
|
|
4
|
+
* it runs on — and who owns a path and whether it runs, which a driver asks
|
|
5
|
+
* before it trusts one.
|
|
5
6
|
*
|
|
6
7
|
* A translation unit of its own for the reason runtime-os.c is one: each file
|
|
7
8
|
* carries its own measured `.text*` ceiling in tests/run.js. runtime-os.c had
|
|
@@ -33,6 +34,7 @@ typedef int nish_host_unused;
|
|
|
33
34
|
#include <signal.h>
|
|
34
35
|
#include <stdint.h>
|
|
35
36
|
#include <stdio.h>
|
|
37
|
+
#include <string.h>
|
|
36
38
|
#include <sys/stat.h>
|
|
37
39
|
#include <time.h>
|
|
38
40
|
#include <unistd.h>
|
|
@@ -98,7 +100,7 @@ void nish_random_fill(nish_array *bytes) {
|
|
|
98
100
|
digits. `stat` follows a symbolic link, as `fs.statSync` does. */
|
|
99
101
|
double nish_stat_mtime(const nish_str *path) {
|
|
100
102
|
struct stat st;
|
|
101
|
-
if (stat(path
|
|
103
|
+
if (stat(nish_cpath(path), &st) != 0) return __builtin_nan("");
|
|
102
104
|
#if defined(__APPLE__)
|
|
103
105
|
struct timespec t = st.st_mtimespec;
|
|
104
106
|
#else
|
|
@@ -107,6 +109,31 @@ double nish_stat_mtime(const nish_str *path) {
|
|
|
107
109
|
return (double)t.tv_sec * 1e3 + (double)t.tv_nsec / 1e6;
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
/* ---- Who owns a path, and whether it runs (docs/security/runtime.md, RT-9)
|
|
113
|
+
*
|
|
114
|
+
* The primitives a driver needs before it trusts a directory or a program it
|
|
115
|
+
* found on its own. `nish_lstat_owner_mode` answers for the path itself, a
|
|
116
|
+
* symbolic link included, rather than for what it names: the owner's uid in
|
|
117
|
+
* the high 32 bits and `st_mode` in the low 32, or exactly -1 when the path
|
|
118
|
+
* does not resolve or holds a NUL. `nish_euid` is the user to compare the
|
|
119
|
+
* owner with, and `nish_is_executable` is `access(X_OK)`: whether the real
|
|
120
|
+
* user may run the file, which is what tells an executable `PATH` entry from
|
|
121
|
+
* one that is only readable. Nothing in the language reaches them yet: `src/` may call
|
|
122
|
+
* a runtime function only once the last release declares it, so they are here
|
|
123
|
+
* for the change after the next release (docs/security/cli.md, CLI-7 and
|
|
124
|
+
* CLI-9). */
|
|
125
|
+
int64_t nish_lstat_owner_mode(const nish_str *path) {
|
|
126
|
+
struct stat st;
|
|
127
|
+
if (lstat(nish_cpath(path), &st) != 0) return -1;
|
|
128
|
+
return (int64_t)((uint64_t)(uint32_t)st.st_uid << 32 | (uint32_t)st.st_mode);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
int64_t nish_euid(void) { return (int64_t)geteuid(); }
|
|
132
|
+
|
|
133
|
+
_Bool nish_is_executable(const nish_str *path) {
|
|
134
|
+
return access(nish_cpath(path), X_OK) == 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
/* ---- Signals: `signalFd()` and `readSignal(fd)`
|
|
111
138
|
*
|
|
112
139
|
* A descriptor rather than a handler in the language, because a handler would
|
|
@@ -129,46 +156,65 @@ double nish_stat_mtime(const nish_str *path) {
|
|
|
129
156
|
* async-signal-safe. The write end is non-blocking, so a thousand unread
|
|
130
157
|
* signals lose the newest rather than wedge the handler, and `errno` is put
|
|
131
158
|
* back for the code the signal interrupted. The descriptor is made once and
|
|
132
|
-
* every later `signalFd()` answers the same one.
|
|
133
|
-
|
|
134
|
-
|
|
159
|
+
* every later `signalFd()` answers the same one.
|
|
160
|
+
*
|
|
161
|
+
* Both ends live in one word, the read end in the high half and the write end
|
|
162
|
+
* in the low, -1 until the pipe exists, so that publishing them is a single
|
|
163
|
+
* compare-and-swap (docs/security/runtime.md, RT-10). Two threads whose first
|
|
164
|
+
* calls overlap can each make a pipe; one swap wins, the other caller closes
|
|
165
|
+
* its own pipe and answers the winner's read end. With two separate words the
|
|
166
|
+
* call that finished last answered its own pipe while the handler wrote to
|
|
167
|
+
* the other, so one caller never heard a signal and a pipe leaked. Each first
|
|
168
|
+
* caller installs the handler itself before it returns, so neither can hand
|
|
169
|
+
* back a descriptor before a signal reaches it. */
|
|
170
|
+
static int64_t nish_signal_pipe = -1;
|
|
135
171
|
|
|
136
172
|
static void nish_on_signal(int sig) {
|
|
137
173
|
int saved = errno;
|
|
138
174
|
unsigned char b = (unsigned char)sig;
|
|
139
|
-
|
|
175
|
+
/* The low half: the write end. */
|
|
176
|
+
(void)!write((int)__atomic_load_n(&nish_signal_pipe, __ATOMIC_RELAXED), &b, 1);
|
|
140
177
|
errno = saved;
|
|
141
178
|
}
|
|
142
179
|
|
|
143
180
|
int32_t nish_signal_fd(void) {
|
|
144
|
-
|
|
145
|
-
|
|
181
|
+
int64_t cur = __atomic_load_n(&nish_signal_pipe, __ATOMIC_ACQUIRE);
|
|
182
|
+
if (cur < 0) {
|
|
183
|
+
int p[2];
|
|
146
184
|
#if defined(__linux__)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
185
|
+
/* Both ends close-on-exec from the moment they exist, so a child a sibling
|
|
186
|
+
thread spawns meanwhile cannot inherit either. */
|
|
187
|
+
if (pipe2(p, O_CLOEXEC) != 0) return -1;
|
|
150
188
|
#else
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
189
|
+
/* Darwin has no `pipe2`, so there is a window between `pipe` and the two
|
|
190
|
+
`fcntl`s in which a child spawned by another thread inherits the ends. */
|
|
191
|
+
if (pipe(p) != 0) return -1;
|
|
192
|
+
fcntl(p[0], F_SETFD, FD_CLOEXEC);
|
|
193
|
+
fcntl(p[1], F_SETFD, FD_CLOEXEC);
|
|
156
194
|
#endif
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
195
|
+
fcntl(p[1], F_SETFL, O_NONBLOCK);
|
|
196
|
+
int64_t mine = (int64_t)p[0] << 32 | (uint32_t)p[1];
|
|
197
|
+
/* A failed swap leaves the winner's pair in `cur`. */
|
|
198
|
+
if (__atomic_compare_exchange_n(&nish_signal_pipe, &cur, mine, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
|
|
199
|
+
cur = mine;
|
|
200
|
+
} else {
|
|
201
|
+
close(p[0]);
|
|
202
|
+
close(p[1]);
|
|
203
|
+
}
|
|
204
|
+
struct sigaction sa;
|
|
205
|
+
sa.sa_handler = nish_on_signal;
|
|
206
|
+
sa.sa_flags = SA_RESTART;
|
|
207
|
+
sigemptyset(&sa.sa_mask);
|
|
208
|
+
sigaction(SIGINT, &sa, 0);
|
|
209
|
+
sigaction(SIGTERM, &sa, 0);
|
|
210
|
+
}
|
|
211
|
+
return (int32_t)(cur >> 32);
|
|
166
212
|
}
|
|
167
213
|
|
|
168
214
|
/* Block until a signal's byte arrives and answer it: 15 or 2, or -1 for any
|
|
169
215
|
`fd` that is not the signal descriptor and for a read that fails. */
|
|
170
216
|
int32_t nish_read_signal(int32_t fd) {
|
|
171
|
-
if (fd !=
|
|
217
|
+
if (fd != (int32_t)(__atomic_load_n(&nish_signal_pipe, __ATOMIC_ACQUIRE) >> 32)) return -1;
|
|
172
218
|
unsigned char b;
|
|
173
219
|
ssize_t n;
|
|
174
220
|
do n = read(fd, &b, 1);
|
package/runtime/runtime-net.c
CHANGED
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
* scripts/build.sh pairs this file with runtime.c like the other halves, so a
|
|
13
13
|
* link line still names one runtime.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Errors take the shape `signalFd` set, an `i32` that is `>= 0` for success
|
|
16
|
+
* (a descriptor, a byte count, 0) and negative for a failure, and add the
|
|
17
|
+
* errno to it: where `signalFd` and `readSignal` answer a plain -1, a call
|
|
18
|
+
* here answers the failure's errno, negated. The codes a loop branches on are Linux's numbers on every platform (-11
|
|
18
19
|
* would block, -95 unsupported, -32 the peer is gone, -104 reset, -98 the
|
|
19
|
-
* address is in use, -22 a bad argument), so
|
|
20
|
-
* Darwin's; any other failure is the host's own
|
|
21
|
-
* allocates: the addresses a call reads or writes are
|
|
20
|
+
* address is in use, -111 refused, -110 timed out, -22 a bad argument), so
|
|
21
|
+
* `nish_net_err` translates Darwin's; any other failure is the host's own
|
|
22
|
+
* `-errno`. Nothing here allocates: the addresses a call reads or writes are
|
|
23
|
+
* the caller's `u8[]`.
|
|
22
24
|
*
|
|
23
25
|
* An address is 18 bytes of that array: the 16 bytes of an IPv6 address, an
|
|
24
26
|
* IPv4 one as `::ffff:a.b.c.d`, then the port, big-endian. An IPv4 address is
|
|
@@ -33,7 +35,10 @@
|
|
|
33
35
|
* both, never SIGPIPE), the errno numbers, UDP's segmentation offload
|
|
34
36
|
* (`UDP_SEGMENT`, `UDP_GRO`) and ECN marks, which Linux has and for which
|
|
35
37
|
* Darwin answers -95, and the readiness loop, which is epoll on Linux and
|
|
36
|
-
* kqueue on Darwin.
|
|
38
|
+
* kqueue on Darwin. Only the sending side and the GRO bind say -95 there: a
|
|
39
|
+
* receive leaves `meta` at 0, 0, and a `meta[1]` of 0 is also what a real
|
|
40
|
+
* Not-ECT datagram reads as, so a program learns that Darwin has no ECN from
|
|
41
|
+
* the send, never from the receive. The Darwin branch is compiled by CI's Darwin bootstrap
|
|
37
42
|
* rows and run by nothing. A WASI build has none of this (the checker refuses
|
|
38
43
|
* every `nish:net` call under a wasm target), so there the file is empty.
|
|
39
44
|
*/
|
|
@@ -91,6 +96,8 @@ static int32_t nish_net_err(int e) {
|
|
|
91
96
|
if (e == EOPNOTSUPP || e == ENOTSUP) return -95;
|
|
92
97
|
if (e == ECONNRESET) return -104;
|
|
93
98
|
if (e == EADDRINUSE) return -98;
|
|
99
|
+
if (e == ECONNREFUSED) return -111;
|
|
100
|
+
if (e == ETIMEDOUT) return -110;
|
|
94
101
|
#endif
|
|
95
102
|
return -e;
|
|
96
103
|
}
|
|
@@ -111,8 +118,11 @@ static int nish_net_parse(const nish_str *s, unsigned char a[16]) {
|
|
|
111
118
|
return inet_pton(AF_INET6, host, a) == 1;
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
/* `a` and `port` as a socket address of the family `v4` names.
|
|
115
|
-
|
|
121
|
+
/* `a` and `port` as a socket address of the family `v4` names. `noinline`:
|
|
122
|
+
three calls share one body, which inlined into each was 42 bytes more of
|
|
123
|
+
`.text` than the calls cost. */
|
|
124
|
+
__attribute__((noinline)) static socklen_t nish_net_sockaddr(nish_sockaddr *s, const unsigned char a[16],
|
|
125
|
+
int32_t port, int v4) {
|
|
116
126
|
memset(s, 0, sizeof *s);
|
|
117
127
|
if (v4) {
|
|
118
128
|
s->v4.sin_family = AF_INET;
|
|
@@ -229,18 +239,24 @@ int32_t nish_tcp_listen(const nish_str *host, int32_t port, int32_t backlog) {
|
|
|
229
239
|
return nish_net_bound(host, port, SOCK_STREAM, backlog);
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
/* A socket address into the caller's 18-byte form. An IPv4 peer
|
|
233
|
-
mapped, and the port is already in network order, which is the
|
|
234
|
-
big-endian.
|
|
235
|
-
|
|
242
|
+
/* A socket address of `n` bytes into the caller's 18-byte form. An IPv4 peer
|
|
243
|
+
is written mapped, and the port is already in network order, which is the
|
|
244
|
+
form's big-endian. Anything else -- no address at all, which is what
|
|
245
|
+
`recvmsg` reports on a connected or Unix socket, or a family that is not
|
|
246
|
+
IP -- is eighteen zeros: those bytes of `s` were never written, and copying
|
|
247
|
+
them handed the caller whatever the stack held (docs/security/runtime.md,
|
|
248
|
+
RT-6). */
|
|
249
|
+
static void nish_net_put(nish_array *out, const nish_sockaddr *s, socklen_t n) {
|
|
236
250
|
unsigned char *p = (unsigned char *)out->data;
|
|
237
|
-
uint16_t port =
|
|
238
|
-
|
|
251
|
+
uint16_t port = 0;
|
|
252
|
+
memset(p, 0, NISH_ADDR_BYTES);
|
|
253
|
+
if (n >= sizeof s->v4 && s->sa.sa_family == AF_INET) {
|
|
239
254
|
memcpy(p, nish_mapped_prefix, 12);
|
|
240
255
|
memcpy(p + 12, &s->v4.sin_addr, 4);
|
|
241
256
|
port = s->v4.sin_port;
|
|
242
|
-
} else {
|
|
257
|
+
} else if (n >= sizeof s->v6 && s->sa.sa_family == AF_INET6) {
|
|
243
258
|
memcpy(p, &s->v6.sin6_addr, 16);
|
|
259
|
+
port = s->v6.sin6_port;
|
|
244
260
|
}
|
|
245
261
|
memcpy(p + 16, &port, 2);
|
|
246
262
|
}
|
|
@@ -259,10 +275,45 @@ int32_t nish_tcp_accept(int32_t fd, nish_array *peer) {
|
|
|
259
275
|
int c = nish_net_setup(accept(fd, &s.sa, &n));
|
|
260
276
|
#endif
|
|
261
277
|
if (c < 0) return nish_net_fail();
|
|
262
|
-
nish_net_put(peer, &s);
|
|
278
|
+
nish_net_put(peer, &s, n);
|
|
263
279
|
return c;
|
|
264
280
|
}
|
|
265
281
|
|
|
282
|
+
/* `tcpConnect(addr)`: a new socket, non-blocking and close-on-exec, connecting
|
|
283
|
+
to the address in `addr`; an IPv4 one is an `AF_INET` socket, any other
|
|
284
|
+
`AF_INET6`. A non-blocking `connect` seldom finishes at once (on Linux not
|
|
285
|
+
even over loopback), and its `EINPROGRESS` (-115) is not a failure: the
|
|
286
|
+
descriptor is answered all the same, and the connection is made once the
|
|
287
|
+
loop reports it writable, when `connectResult` says how it went. Any other
|
|
288
|
+
failure closes the socket and is answered instead of it. An `addr` shorter
|
|
289
|
+
than 18 bytes is -22 before any socket exists. */
|
|
290
|
+
int32_t nish_tcp_connect(const nish_array *addr) {
|
|
291
|
+
if (addr->len < NISH_ADDR_BYTES) return -22;
|
|
292
|
+
const unsigned char *p = (const unsigned char *)addr->data;
|
|
293
|
+
int v4 = memcmp(p, nish_mapped_prefix, 12) == 0;
|
|
294
|
+
int fd = nish_net_socket(v4 ? AF_INET : AF_INET6, SOCK_STREAM);
|
|
295
|
+
if (fd < 0) return nish_net_fail();
|
|
296
|
+
nish_sockaddr s;
|
|
297
|
+
socklen_t n = nish_net_sockaddr(&s, p, (p[16] << 8) | p[17], v4);
|
|
298
|
+
if (connect(fd, &s.sa, n) != 0 && errno != EINPROGRESS) {
|
|
299
|
+
int e = errno;
|
|
300
|
+
close(fd);
|
|
301
|
+
return nish_net_err(e);
|
|
302
|
+
}
|
|
303
|
+
return fd;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* `connectResult(fd)`: how the connection `tcpConnect` started went, once the
|
|
307
|
+
descriptor is writable: 0 when it is made, or its failure (-111 refused,
|
|
308
|
+
-110 timed out) from `SO_ERROR`. The kernel clears `SO_ERROR` as it is
|
|
309
|
+
read, so a failure is answered once. */
|
|
310
|
+
int32_t nish_connect_result(int32_t fd) {
|
|
311
|
+
int e = 0;
|
|
312
|
+
socklen_t n = sizeof e;
|
|
313
|
+
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &n) != 0) return nish_net_fail();
|
|
314
|
+
return e != 0 ? nish_net_err(e) : 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
266
317
|
/* `netRead(fd, buf, off, len)`: at most `len` bytes into `buf` from `off`.
|
|
267
318
|
The compiled call has already checked `0 <= off <= off + len <=
|
|
268
319
|
buf.length`, unless the program was built with `--unchecked-indexing`. */
|
|
@@ -384,7 +435,9 @@ int32_t nish_udp_recv_from(int32_t fd, nish_array *buf, int64_t off, int64_t len
|
|
|
384
435
|
.msg_name = &s, .msg_namelen = sizeof s, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = c.b, .msg_controllen = sizeof c};
|
|
385
436
|
ssize_t got = recvmsg(fd, &m, 0);
|
|
386
437
|
if (got < 0) return nish_net_fail();
|
|
387
|
-
/* Darwin sets neither option, so both stay 0 there
|
|
438
|
+
/* Darwin sets neither option, so both stay 0 there: a `meta[1]` that reads
|
|
439
|
+
as Not-ECT whatever arrived, which is why the -95 of a marked send, not
|
|
440
|
+
this, is how a program learns ECN is unsupported. */
|
|
388
441
|
int32_t info[2] = {0, 0};
|
|
389
442
|
#if defined(__linux__)
|
|
390
443
|
for (struct cmsghdr *h = CMSG_FIRSTHDR(&m); h != NULL; h = CMSG_NXTHDR(&m, h)) {
|
|
@@ -399,7 +452,7 @@ int32_t nish_udp_recv_from(int32_t fd, nish_array *buf, int64_t off, int64_t len
|
|
|
399
452
|
}
|
|
400
453
|
#endif
|
|
401
454
|
memcpy(meta->data, info, sizeof info);
|
|
402
|
-
nish_net_put(from, &s);
|
|
455
|
+
nish_net_put(from, &s, m.msg_namelen);
|
|
403
456
|
return (int32_t)got;
|
|
404
457
|
}
|
|
405
458
|
|