-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
socket.js
99 lines (80 loc) · 2.71 KB
/
socket.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import sys from 'syscall-napi'
import ip from 'ipaddr.js'
import { Socket } from 'node:net'
import dns from 'node:dns/promises'
import { strict as assert } from 'node:assert'
import { generateRandomIP } from './ip.js'
async function initSocket(protocol, type) {
assert(['ipv4', 'ipv6'].includes(protocol));
assert(['tcp', 'udp'].includes(type));
const AF_INET = 2n, AF_INET6 = 10n;
const SOCK_STREAM = 1n, SOCK_DGRAM = 2n;
const fd = await sys.syscall(
sys.__NR_socket,
({ ipv4: AF_INET, ipv6: AF_INET6 })[ protocol ],
({ tcp: SOCK_STREAM, udp: SOCK_DGRAM })[ type ],
0n
);
if (fd < 0n) throw 'failed creating socket';
return fd;
}
async function enableFreebind(fd) {
const SOL_IP = 0n, IP_FREEBIND = 15n;
const res = await sys.syscall(
sys.__NR_setsockopt,
fd, SOL_IP, IP_FREEBIND,
new Uint8Array([ 1 ]),
1n
);
if (res != 0n)
throw 'setting freebind failed';
}
// todo: createDgram
export async function createSocket(host, port, localAddress, connectOptions) {
const addrFamily = ip.parse(host).kind()
if (connectOptions.strict !== false) {
assert(addrFamily == ip.parse(localAddress).kind())
}
const fd = await initSocket(addrFamily, 'tcp');
await enableFreebind(fd);
const sock = new Socket({ fd: Number(fd) })
sock.connect({
...connectOptions,
host, port,
localAddress: connectOptions.familyMatching
? localAddress
: undefined
})
return sock
}
async function lookup(hostname, family) {
const OTHER_FAMILY = {4: 6, 6: 4};
let host;
try {
host = await dns.lookup(hostname, { family });
} catch {
host = await dns.lookup(hostname, { family: OTHER_FAMILY[ family ] });
}
return host;
}
export async function createSocketFromHostname(hostname, port, sourceIP, options) {
const { strict, ...connectOptions } = options;
const kind = ip.parse(sourceIP).kind();
const family = ({ 'ipv4': 4, 'ipv6': 6 })[ kind ];
const host = await lookup(hostname, family);
const familyMatching = family === host.family;
if (!familyMatching && strict !== false) {
throw 'family mismatch for addr ' + host.address;
}
return createSocket(
host.address, port, sourceIP,
{ ...connectOptions, strict, familyMatching }
);
}
// `bits` defines how many bits to fill from the MSB to the LSB
// needs to be <= length of the prefix
export function createRandomSocket(hostname, port, localCIDR, options) {
const { bits, ...rest } = options;
const addr = generateRandomIP(localCIDR, bits);
return createSocketFromHostname(hostname, port, addr, rest);
}