You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
8.9 KiB
367 lines
8.9 KiB
'use strict'; |
|
|
|
const net = require('net'); |
|
const nbind = require('@mcesystems/nbind') |
|
const ZeroTier = nbind.init().lib.ZeroTier |
|
|
|
|
|
/* |
|
* EXAMPLE USAGE |
|
* Usage: `nc -lv 4444` |
|
*/ |
|
|
|
function example() { |
|
// Start ZeroTier service |
|
ZeroTier.start(".zerotier", 9994); |
|
|
|
// Join virtual network |
|
ZeroTier.join("8056c2e21c000001"); |
|
|
|
// Open the socket |
|
let fd = ZeroTier.connectStream("29.49.7.203", 4444); |
|
|
|
// Send some data |
|
ZeroTier.send(fd, Buffer.from("Name?\n", 'utf8'), 0) |
|
|
|
// Set blocking read mode |
|
// ZeroTier.fcntlSetBlocking(fd, true); |
|
let heartbeat = setInterval(() => process.stderr.write('.'), 100) |
|
|
|
// Receive some data |
|
const _read = () => { |
|
const buf = Buffer.alloc(32) |
|
let bytes = -1 |
|
do { |
|
bytes = ZeroTier.recv(fd, buf, 0) |
|
if (bytes > 0) { process.stdout.write(buf.toString('utf8')) } |
|
} while (bytes > 0); |
|
|
|
if (!ZeroTier.getMyNode().online || buf.toString('utf8').includes("exit")) { |
|
// Close the socket |
|
ZeroTier.close(fd) |
|
// Stop ZeroTier service |
|
ZeroTier.stop() |
|
// Clear the interval |
|
clearInterval(heartbeat) |
|
} else { |
|
setTimeout(_read, 500) |
|
} |
|
} |
|
_read() |
|
} |
|
|
|
|
|
// Target API: |
|
// |
|
// let s = net.connect({port: 80, host: 'google.com'}, function() { |
|
// ... |
|
// }); |
|
// |
|
// There are various forms: |
|
// |
|
// connect(options, [cb]) |
|
// connect(port, [host], [cb]) |
|
// connect(path, [cb]); |
|
// |
|
function connect(...args) { |
|
const normalized = net._normalizeArgs(args); |
|
const options = normalized[0]; |
|
// debug('createConnection', normalized); |
|
const socket = new Socket(options); |
|
|
|
if (options.timeout) { |
|
socket.setTimeout(options.timeout); |
|
} |
|
|
|
return socket.connect(normalized); |
|
} |
|
|
|
/* |
|
* https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L1107 |
|
*/ |
|
function afterConnect(status, self, req, readable, writable) { |
|
// const self = handle[owner_symbol]; |
|
|
|
// Callback may come after call to destroy |
|
if (self.destroyed) { |
|
return; |
|
} |
|
|
|
// debug('afterConnect'); |
|
|
|
// assert(self.connecting); |
|
self.connecting = false; |
|
self._sockname = null; |
|
|
|
if (status === 0) { |
|
self.readable = readable; |
|
if (!self._writableState.ended) |
|
self.writable = writable; |
|
self._unrefTimer(); |
|
|
|
self.emit('connect'); |
|
self.emit('ready'); |
|
|
|
// Start the first read, or get an immediate EOF. |
|
// this doesn't actually consume any bytes, because len=0. |
|
if (readable && !self.isPaused()) |
|
self.read(0); |
|
|
|
} else { |
|
self.connecting = false; |
|
let details; |
|
if (req.localAddress && req.localPort) { |
|
details = req.localAddress + ':' + req.localPort; |
|
} |
|
const ex = new Error(status, |
|
'connect', |
|
req.address, |
|
req.port, |
|
details); |
|
if (details) { |
|
ex.localAddress = req.localAddress; |
|
ex.localPort = req.localPort; |
|
} |
|
self.destroy(ex); |
|
} |
|
} |
|
|
|
function writeGeneric(self, chunk, encoding, callback) { |
|
const decodeStrings = self._writableState && self._writableState.decodeStrings |
|
const buf = (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk |
|
|
|
let bytes |
|
const err = ZeroTier.send(self._fd, buf, 0) |
|
switch (err) { |
|
case -1: |
|
callback(new Error("ZeroTier Socket error")) |
|
break |
|
case -2: |
|
callback(new Error("ZeroTier Service error")) |
|
break |
|
case -3: |
|
callback(new Error("ZeroTier Invalid argument")) |
|
break |
|
default: |
|
bytes = err |
|
callback(null) |
|
} |
|
|
|
return { |
|
async: true, |
|
bytes: bytes, |
|
} |
|
} |
|
|
|
function writevGeneric(self, chunks, callback) { |
|
const decodeStrings = self._writableState && self._writableState.decodeStrings |
|
const bufs = chunks.map(({ chunk, encoding }) => (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk) |
|
|
|
let bytes |
|
const err = ZeroTier.writev(self._fd, bufs) |
|
switch (err) { |
|
case -1: |
|
callback(new Error("ZeroTier Socket error")) |
|
break |
|
case -2: |
|
callback(new Error("ZeroTier Service error")) |
|
break |
|
case -3: |
|
callback(new Error("ZeroTier Invalid argument")) |
|
break |
|
default: |
|
bytes = err |
|
callback(null) |
|
} |
|
|
|
return { |
|
async: true, |
|
bytes: bytes, |
|
} |
|
} |
|
|
|
class Socket extends net.Socket { |
|
/* |
|
* https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L929 |
|
*/ |
|
connect(...args) { |
|
let normalized; |
|
// If passed an array, it's treated as an array of arguments that have |
|
// already been normalized (so we don't normalize more than once). This has |
|
// been solved before in https://github.com/nodejs/node/pull/12342, but was |
|
// reverted as it had unintended side effects. |
|
if (Array.isArray(args[0])) { |
|
normalized = args[0]; |
|
} else { |
|
normalized = net._normalizeArgs(args); |
|
} |
|
const options = normalized[0]; |
|
const cb = normalized[1]; |
|
|
|
// if (this.write !== net.Socket.prototype.write) |
|
// this.write = net.Socket.prototype.write; |
|
|
|
if (this.destroyed) { |
|
this._handle = null; |
|
this._peername = null; |
|
this._sockname = null; |
|
} |
|
|
|
// const { path } = options; |
|
// const pipe = !!path; |
|
// debug('pipe', pipe, path); |
|
|
|
// if (!this._handle) { |
|
// this._handle = pipe ? |
|
// new Pipe(PipeConstants.SOCKET) : |
|
// new TCP(TCPConstants.SOCKET); |
|
// initSocketHandle(this); |
|
// } |
|
|
|
if (cb !== null) { |
|
this.once('connect', cb); |
|
} |
|
|
|
this._unrefTimer(); |
|
|
|
this.connecting = true; |
|
this.writable = true; |
|
|
|
// if (pipe) { |
|
// validateString(path, 'options.path'); |
|
// defaultTriggerAsyncIdScope( |
|
// this[async_id_symbol], internalConnect, this, path |
|
// ); |
|
// } else { |
|
// lookupAndConnect(this, options); |
|
// } |
|
|
|
const { host, port } = options; |
|
// If host is an IP, skip performing a lookup |
|
const addressType = net.isIP(host); |
|
if (addressType) { |
|
this._fd = ZeroTier.connectStream(host, port); |
|
afterConnect(0, this, {}, true, true); |
|
} else { |
|
throw new Error("DNS LOOKUP NOT IMPLEMENTED"); |
|
} |
|
|
|
return this; |
|
} |
|
|
|
/* |
|
* https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_readable_read_size_1 |
|
*/ |
|
_read(size) { |
|
// debug('_read'); |
|
|
|
if (this.connecting) { |
|
// debug('_read wait for connection'); |
|
this.once('connect', () => this._read(size)); |
|
return |
|
} |
|
|
|
if (!this.readChunk || this.readChunk.length < size) { |
|
this.readChunk = Buffer.alloc(size) |
|
} |
|
|
|
let bytes = -1 |
|
let moreData = true |
|
do { |
|
bytes = ZeroTier.recv(this._fd, this.readChunk, 0) |
|
switch (bytes) { |
|
case -2: |
|
throw new Error("ZeroTier Service error") |
|
case -3: |
|
throw new Error("ZeroTier Invalid argument") |
|
default: |
|
if (bytes > 0) { |
|
// this.bytesRead += bytes |
|
moreData = this.push(this.readChunk) |
|
} |
|
} |
|
} while (bytes > 0 && moreData) |
|
|
|
if (moreData) { setTimeout(() => this._read(size), 500) } |
|
} |
|
|
|
/* |
|
* https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_writev_chunks_callback |
|
*/ |
|
_writev(chunks, cb) { |
|
this._writeGeneric(true, chunks, '', cb); |
|
} |
|
|
|
/* |
|
* https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_write_chunk_encoding_callback_1 |
|
*/ |
|
_write(data, encoding, cb) { |
|
this._writeGeneric(false, data, encoding, cb); |
|
} |
|
|
|
/* |
|
* https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_final_callback |
|
*/ |
|
_final(callback) { |
|
const err = ZeroTier.close(this._fd) |
|
|
|
switch (err) { |
|
case -1: |
|
return callback(new Error("ZeroTier Socket error")) |
|
break |
|
case -2: |
|
return callback(new Error("ZeroTier Service error")) |
|
break |
|
default: |
|
return super._final(callback) |
|
} |
|
} |
|
|
|
/* |
|
* https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L760 |
|
*/ |
|
_writeGeneric(writev, data, encoding, cb) { |
|
// If we are still connecting, then buffer this for later. |
|
// The Writable logic will buffer up any more writes while |
|
// waiting for this one to be done. |
|
if (this.connecting) { |
|
this._pendingData = data; |
|
this._pendingEncoding = encoding; |
|
this.once('connect', function connect() { |
|
this._writeGeneric(writev, data, encoding, cb); |
|
}); |
|
return; |
|
} |
|
this._pendingData = null; |
|
this._pendingEncoding = ''; |
|
|
|
// if (!this._handle) { |
|
// cb(new ERR_SOCKET_CLOSED()); |
|
// return false; |
|
// } |
|
|
|
this._unrefTimer(); |
|
|
|
let req; |
|
if (writev) |
|
req = writevGeneric(this, data, cb); |
|
else |
|
req = writeGeneric(this, data, encoding, cb); |
|
if (req.async) { |
|
// this[kLastWriteQueueSize] = req.bytes; |
|
} |
|
} |
|
} |
|
|
|
module.exports = { |
|
example, |
|
start: ZeroTier.start, |
|
join: ZeroTier.join, |
|
connect, |
|
createConnection: connect, |
|
Socket, |
|
Stream: Socket, // Legacy naming |
|
restart: ZeroTier.restart, |
|
stop: ZeroTier.stop, |
|
free: ZeroTier.free, |
|
};
|
|
|