@threadedstack/wmd-web 0.1.13 → 0.1.14
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 +11 -0
- package/RELEASE.json +13 -11
- package/guest/arm64/initramfs.cpio.gz +0 -0
- package/package.json +4 -4
- package/relay/engine.ts +103 -61
- package/relay/hosted.ts +437 -392
- package/relay/main.ts +61 -33
- package/relay/relay.ts +37 -25
- package/relay/wisp.ts +111 -51
- package/src/host/ctl.js +45 -4
- package/src/net/wisp-client.js +71 -17
- package/src/net/wisp.js +52 -6
- package/wmdpack.wasm +0 -0
package/src/net/wisp.js
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
// 0xE4 wmd-dns packet 0xE5 LOOKUP qtype u16 | name utf8 client -> server
|
|
25
25
|
// packet 0xE6 ANSWER rcode u8 | ttl u32 | count u8 | addresses | reason utf8
|
|
26
26
|
// 0xE7 wmd-instance (no packet) client metadata: a 16-byte instance id
|
|
27
|
+
// 0xE9 wmd-reason packet 0xE8 REASON kind u8 | until f64 | rule length u8 | rule | words
|
|
28
|
+
// server -> client: why a refusal or slowdown,
|
|
29
|
+
// and when it lifts (stream 0: the session)
|
|
27
30
|
|
|
28
31
|
export const SUBPROTOCOL = 'wisp-v2'
|
|
29
32
|
export const VERSION = { major: 2, minor: 0 }
|
|
@@ -33,14 +36,18 @@ export const INSTANCE_BYTES = 16
|
|
|
33
36
|
|
|
34
37
|
export const PT = {
|
|
35
38
|
CONNECT: 0x01, DATA: 0x02, CONTINUE: 0x03, CLOSE: 0x04, INFO: 0x05,
|
|
36
|
-
CREDIT: 0xe0, SHUTDOWN: 0xe3, LOOKUP: 0xe5, ANSWER: 0xe6,
|
|
39
|
+
CREDIT: 0xe0, SHUTDOWN: 0xe3, LOOKUP: 0xe5, ANSWER: 0xe6, REASON: 0xe8,
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export const EXT = {
|
|
40
43
|
UDP: 0x01, PASSWORD: 0x02, KEY: 0x03, MOTD: 0x04, CONFIRM: 0x05,
|
|
41
|
-
WMD_CREDIT: 0xe1, WMD_HALFCLOSE: 0xe2, WMD_DNS: 0xe4, WMD_INSTANCE: 0xe7,
|
|
44
|
+
WMD_CREDIT: 0xe1, WMD_HALFCLOSE: 0xe2, WMD_DNS: 0xe4, WMD_INSTANCE: 0xe7, WMD_REASON: 0xe9,
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/** REASON kinds: a refusal (a CLOSE follows), a slowdown, and a slowdown that has lifted. */
|
|
48
|
+
export const REASON_KIND = { REFUSED: 1, SLOWED: 2, CLEARED: 3 }
|
|
49
|
+
const RULE_MAX = 64
|
|
50
|
+
|
|
44
51
|
export const STREAM = { TCP: 0x01, UDP: 0x02 }
|
|
45
52
|
|
|
46
53
|
/** CLOSE reasons (spec, plus the extension-specified auth reasons). */
|
|
@@ -121,6 +128,21 @@ export function lookupPacket(id, name, qtype) {
|
|
|
121
128
|
return packet(PT.LOOKUP, id, p)
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
/** until: ms since 1970 UTC when the rule lifts, 0 when it has no time. */
|
|
132
|
+
export function reasonPacket(id, kind, rule, until, words) {
|
|
133
|
+
if (!/^[\x20-\x7e]*$/.test(rule)) throw new RangeError('wisp: a rule name is ASCII')
|
|
134
|
+
if (rule.length > RULE_MAX) throw new RangeError(`wisp: a rule name is at most ${RULE_MAX} characters`)
|
|
135
|
+
const w = enc.encode(words || '')
|
|
136
|
+
const p = new Uint8Array(10 + rule.length + w.length)
|
|
137
|
+
const dv = new DataView(p.buffer)
|
|
138
|
+
p[0] = kind
|
|
139
|
+
dv.setFloat64(1, until || 0, true)
|
|
140
|
+
p[9] = rule.length
|
|
141
|
+
for (let i = 0; i < rule.length; i++) p[10 + i] = rule.charCodeAt(i)
|
|
142
|
+
p.set(w, 10 + rule.length)
|
|
143
|
+
return packet(PT.REASON, id, p)
|
|
144
|
+
}
|
|
145
|
+
|
|
124
146
|
/** addrs: arrays of 4 (A) or 16 (AAAA) bytes. */
|
|
125
147
|
export function answerPacket(id, rcode, ttl, addrs = [], reason = '') {
|
|
126
148
|
const d = enc.encode(reason)
|
|
@@ -181,6 +203,7 @@ export const ext = {
|
|
|
181
203
|
},
|
|
182
204
|
wmdHalfclose: () => ({ id: EXT.WMD_HALFCLOSE, data: Uint8Array.of(WMD_EXT_VERSION) }),
|
|
183
205
|
wmdDns: () => ({ id: EXT.WMD_DNS, data: Uint8Array.of(WMD_EXT_VERSION) }),
|
|
206
|
+
wmdReason: () => ({ id: EXT.WMD_REASON, data: Uint8Array.of(WMD_EXT_VERSION) }),
|
|
184
207
|
/** The client sends its 16-byte id; the server lists the extension with no id. */
|
|
185
208
|
wmdInstance(idBytes) {
|
|
186
209
|
if (!idBytes) return { id: EXT.WMD_INSTANCE, data: Uint8Array.of(WMD_EXT_VERSION) }
|
|
@@ -229,6 +252,7 @@ export function readExt(e, from) {
|
|
|
229
252
|
return { version: d[0] }
|
|
230
253
|
case EXT.WMD_HALFCLOSE:
|
|
231
254
|
case EXT.WMD_DNS:
|
|
255
|
+
case EXT.WMD_REASON:
|
|
232
256
|
if (d.length < 1) return { error: 'wmd extension without a version' }
|
|
233
257
|
return { version: d[0] }
|
|
234
258
|
case EXT.WMD_INSTANCE:
|
|
@@ -307,6 +331,16 @@ export function parse(u8) {
|
|
|
307
331
|
m.count = p[5]
|
|
308
332
|
m.rest = p.subarray(6) // the addresses (their size depends on the query), then the reason
|
|
309
333
|
break
|
|
334
|
+
case PT.REASON: {
|
|
335
|
+
if (p.length < 10 || p.length < 10 + p[9]) return bad('REASON too short')
|
|
336
|
+
m.kind = p[0]
|
|
337
|
+
m.until = dv.getFloat64(6, true)
|
|
338
|
+
m.rule = String.fromCharCode(...p.subarray(10, 10 + p[9]))
|
|
339
|
+
const w = text(p.subarray(10 + p[9]))
|
|
340
|
+
if (w === null) return bad('REASON words are not UTF-8')
|
|
341
|
+
m.words = w
|
|
342
|
+
break
|
|
343
|
+
}
|
|
310
344
|
case PT.INFO: {
|
|
311
345
|
if (p.length < 2) return bad('INFO too short')
|
|
312
346
|
m.version = { major: p[0], minor: p[1] }
|
|
@@ -343,15 +377,15 @@ export function answerAddrs(m, qtype) {
|
|
|
343
377
|
// Both machines are pure: feed packets in, get packets to send and an outcome out. The caller
|
|
344
378
|
// owns the WebSocket and the timers.
|
|
345
379
|
|
|
346
|
-
const WMD_EXTS = new Set([EXT.WMD_CREDIT, EXT.WMD_HALFCLOSE, EXT.WMD_DNS, EXT.WMD_INSTANCE])
|
|
380
|
+
const WMD_EXTS = new Set([EXT.WMD_CREDIT, EXT.WMD_HALFCLOSE, EXT.WMD_DNS, EXT.WMD_INSTANCE, EXT.WMD_REASON])
|
|
347
381
|
|
|
348
382
|
/**
|
|
349
383
|
* The client side. `exts` are the extensions the client offers (built with `ext.*`, without
|
|
350
384
|
* password auth); `password` is {user, pass} or null.
|
|
351
385
|
*
|
|
352
386
|
* step(pkt) returns {send: Uint8Array[], done?: {version: 1|2, buffer, server: Map, common: Set,
|
|
353
|
-
* motd}, fail?: {reason, words, state}}. state is one of 'auth-required', 'auth-refused',
|
|
354
|
-
* 'incompatible', 'protocol'.
|
|
387
|
+
* motd}, fail?: {reason, words, state, rule?, until?}}. state is one of 'auth-required', 'auth-refused',
|
|
388
|
+
* 'incompatible', 'protocol', or 'limited' when the relay said why (wmd-reason).
|
|
355
389
|
*/
|
|
356
390
|
export class ClientHandshake {
|
|
357
391
|
constructor({ exts = [], password = null } = {}) {
|
|
@@ -412,7 +446,19 @@ export class ClientHandshake {
|
|
|
412
446
|
}
|
|
413
447
|
if (this.state === 'wait-continue') {
|
|
414
448
|
if (m.id !== 0) return fail('protocol', CLOSE.UNKNOWN, 'the relay sent a stream packet before the handshake ended')
|
|
415
|
-
if (m.type === PT.
|
|
449
|
+
if (m.type === PT.REASON) {
|
|
450
|
+
this.why = { rule: m.rule, until: m.until, words: m.words }
|
|
451
|
+
return { send: [] }
|
|
452
|
+
}
|
|
453
|
+
if (m.type === PT.CLOSE) {
|
|
454
|
+
if (this.why) {
|
|
455
|
+
const r = fail('limited', m.reason, this.why.words || closeWords(m.reason))
|
|
456
|
+
r.fail.rule = this.why.rule
|
|
457
|
+
r.fail.until = this.why.until
|
|
458
|
+
return r
|
|
459
|
+
}
|
|
460
|
+
return fail(closeState(m.reason), m.reason, closeWords(m.reason))
|
|
461
|
+
}
|
|
416
462
|
if (m.type !== PT.CONTINUE) return fail('protocol', CLOSE.UNKNOWN, 'the relay did not answer INFO with CONTINUE')
|
|
417
463
|
this.state = 'done'
|
|
418
464
|
const common = new Set(this.exts.filter((e) => this.server.has(e.id)).map((e) => e.id))
|
package/wmdpack.wasm
CHANGED
|
Binary file
|