610 lines
18 KiB
JavaScript
610 lines
18 KiB
JavaScript
|
|
// Copyright (c) 2022, 2025, Oracle and/or its affiliates.
|
||
|
|
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
//
|
||
|
|
// This software is dual-licensed to you under the Universal Permissive License
|
||
|
|
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||
|
|
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||
|
|
// either license.
|
||
|
|
//
|
||
|
|
// If you elect to accept the software under the Apache License, Version 2.0,
|
||
|
|
// the following applies:
|
||
|
|
//
|
||
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
// you may not use this file except in compliance with the License.
|
||
|
|
// You may obtain a copy of the License at
|
||
|
|
//
|
||
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
//
|
||
|
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
// See the License for the specific language governing permissions and
|
||
|
|
// limitations under the License.
|
||
|
|
//
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
const { Buffer } = require('buffer');
|
||
|
|
const net = require("net");
|
||
|
|
const process = require("process");
|
||
|
|
const tls = require("tls");
|
||
|
|
const http = require("http");
|
||
|
|
const Timers = require('timers');
|
||
|
|
const constants = require("./constants.js");
|
||
|
|
const errors = require("../../errors.js");
|
||
|
|
const { findValue, findNVPair } = require("./nvStrToNvPair.js");
|
||
|
|
|
||
|
|
const PACKET_HEADER_SIZE = 8;
|
||
|
|
const DEFAULT_PORT = 1521;
|
||
|
|
const DEFAULT_HTTPS_PROXY_PORT = 80;
|
||
|
|
|
||
|
|
/* Protocol characteristics */
|
||
|
|
const TCPCHA = 1 << 1 | /* ASYNC support */
|
||
|
|
1 << 2 | /* Callback support */
|
||
|
|
1 << 3 | /* More Data support */
|
||
|
|
1 << 8 | /* Read/Write Readiness support */
|
||
|
|
1 << 9 | /* Full Duplex support */
|
||
|
|
1 << 12; /* SIGPIPE Support */
|
||
|
|
|
||
|
|
const sniAllowedCDParams = ["SERVICE_NAME", "INSTANCE_NAME", "SERVER", "COLOCATION_TAG", "CONNECTION_ID", "POOL_BOUNDARY",
|
||
|
|
"POOL_PURITY", "POOL_CONNECTION_CLASS", "POOL_NAME", "SERVICE_TAG", "CID"];
|
||
|
|
const sniParams = ["SERVICE_NAME", "INSTANCE_NAME", "SERVER", "COLOCATION_TAG"];
|
||
|
|
const sniMap = ['S', 'I', 'T', 'C'];
|
||
|
|
const SNI_MAX_BYTES = 256;
|
||
|
|
|
||
|
|
let streamNum = 1;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Network Transport TCP/TCPS adapter
|
||
|
|
* @param {Address} address Destination Address
|
||
|
|
* @param {Object} atts Transport Attributes
|
||
|
|
*/
|
||
|
|
class NTTCP {
|
||
|
|
|
||
|
|
constructor(atts) {
|
||
|
|
this.atts = atts;
|
||
|
|
this.cha = TCPCHA;
|
||
|
|
this.connected = false;
|
||
|
|
this.err = false;
|
||
|
|
this.needsDrain = false;
|
||
|
|
this.numPacketsSinceLastWait = 0;
|
||
|
|
this.secure = false;
|
||
|
|
this.largeSDU = false;
|
||
|
|
this.streamNum = streamNum++;
|
||
|
|
this.packetNum = 1;
|
||
|
|
this.doDNMatch = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* DN matching function(used with TLS)
|
||
|
|
*/
|
||
|
|
dnMatch(serverName, cert) {
|
||
|
|
if (this.atts.sslServerDNMatch && this.doDNMatch) {
|
||
|
|
const toObject = str =>str .split(',').map(x => x.split('=').map(y => y.trim())).reduce((a, x) => {
|
||
|
|
a[x[0]] = x[1];
|
||
|
|
return a;
|
||
|
|
}, {});
|
||
|
|
if (this.atts.sslServerCertDN) { /* Full DN Match */
|
||
|
|
const obj = toObject(this.atts.sslServerCertDN);
|
||
|
|
if (Object.keys(obj).length == Object.keys(cert.subject).length) {
|
||
|
|
for (const key in obj) {
|
||
|
|
if (obj[key] != cert.subject[key]) {
|
||
|
|
return (errors.getErr(errors.ERR_TLS_DNMATCH_FAILURE));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return (errors.getErr(errors.ERR_TLS_DNMATCH_FAILURE));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (tls.checkServerIdentity(this.hostName, cert) && (!this.originHost || tls.checkServerIdentity(this.originHost, cert))) { /* Hostname match */
|
||
|
|
if (this.atts.sslAllowWeakDNMatch) {
|
||
|
|
const serviceName = findValue(this.atts.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", "SERVICE_NAME"]); /* Service Name match */
|
||
|
|
if (serviceName != cert.subject.CN) {
|
||
|
|
return (errors.getErr(errors.ERR_TLS_DNMATCH_FAILURE));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const hostName = this.hostName + " " + (this.originHost ? "or " + this.originHost : "");
|
||
|
|
return (errors.getErr(errors.ERR_TLS_HOSTMATCH_FAILURE, hostName));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* TLS connection establishment
|
||
|
|
* @returns Promise
|
||
|
|
*/
|
||
|
|
async tlsConnect(secureContext, connStream) {
|
||
|
|
this.stream.removeAllListeners();
|
||
|
|
let connectErrCause, sni = null;
|
||
|
|
if (this.atts.useSNI)
|
||
|
|
sni = this.generateSNI();
|
||
|
|
const tlsOptions = {
|
||
|
|
host: this.host,
|
||
|
|
socket: connStream,
|
||
|
|
servername: sni,
|
||
|
|
rejectUnauthorized: true,
|
||
|
|
secureContext: secureContext,
|
||
|
|
enableTrace: false,
|
||
|
|
checkServerIdentity: this.dnMatch.bind(this)
|
||
|
|
};
|
||
|
|
|
||
|
|
await new Promise((resolve) => {
|
||
|
|
this.stream = tls.connect(tlsOptions, () => {
|
||
|
|
if (!this.stream.authorized) {
|
||
|
|
connectErrCause = "server certificate unauthorized";
|
||
|
|
}
|
||
|
|
resolve();
|
||
|
|
}).on('error', (err) => {
|
||
|
|
connectErrCause = err.message;
|
||
|
|
resolve();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (connectErrCause)
|
||
|
|
errors.throwErr(errors.ERR_TLS_AUTH_FAILURE, this.host, this.port, this.atts.connectionId, connectErrCause);
|
||
|
|
this.connStream = connStream;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* TCP connection establishment
|
||
|
|
* @returns Promise
|
||
|
|
*/
|
||
|
|
async ntConnect(address) {
|
||
|
|
if (!address.port) {
|
||
|
|
address.port = DEFAULT_PORT;
|
||
|
|
}
|
||
|
|
|
||
|
|
let connectErrCause, proxyConnectErrCause, req;
|
||
|
|
const httpsProxy = address.httpsProxy || this.atts.httpsProxy;
|
||
|
|
let httpsProxyPort = address.httpsProxyPort || this.atts.httpsProxyPort;
|
||
|
|
|
||
|
|
await new Promise((resolve) => {
|
||
|
|
if (httpsProxy) {
|
||
|
|
if (!httpsProxyPort) {
|
||
|
|
httpsProxyPort = DEFAULT_HTTPS_PROXY_PORT;
|
||
|
|
}
|
||
|
|
req = http.request({
|
||
|
|
host: httpsProxy,
|
||
|
|
port: httpsProxyPort,
|
||
|
|
method: 'CONNECT',
|
||
|
|
path: address.host + ':' + address.port,
|
||
|
|
});
|
||
|
|
req.once('connect', (res, socket) => {
|
||
|
|
if (res.statusCode == 200) {
|
||
|
|
this.connected = true;
|
||
|
|
this.stream = socket;
|
||
|
|
} else {
|
||
|
|
proxyConnectErrCause = res.statusCode;
|
||
|
|
}
|
||
|
|
resolve();
|
||
|
|
});
|
||
|
|
req.once('error', (err) => {
|
||
|
|
proxyConnectErrCause = err.message;
|
||
|
|
resolve();
|
||
|
|
});
|
||
|
|
req.end();
|
||
|
|
} else {
|
||
|
|
this.stream = net.connect(address.port, address.host, () => {
|
||
|
|
this.connected = true;
|
||
|
|
resolve();
|
||
|
|
});
|
||
|
|
this.stream.once('error', (err) => {
|
||
|
|
connectErrCause = err.message;
|
||
|
|
resolve();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (req)
|
||
|
|
req.removeAllListeners();
|
||
|
|
if (!this.connected) {
|
||
|
|
if (proxyConnectErrCause) {
|
||
|
|
errors.throwErr(errors.ERR_PROXY_CONNECTION_FAILURE, httpsProxy, httpsProxyPort, this.atts.connectionId, proxyConnectErrCause);
|
||
|
|
} else {
|
||
|
|
errors.throwErr(errors.ERR_CONNECTION_INCOMPLETE, this.host, this.port, this.atts.connectionId, connectErrCause);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Network Transport connection establishment
|
||
|
|
* @returns Promise
|
||
|
|
*/
|
||
|
|
async connect(address) { /* Connect function for TCP sockets */
|
||
|
|
this.originHost = address.originHost;
|
||
|
|
this.host = address.host;
|
||
|
|
this.hostName = address.hostname;
|
||
|
|
this.port = address.port;
|
||
|
|
|
||
|
|
try {
|
||
|
|
await this.ntConnect(address);
|
||
|
|
if (this.atts.expireTime || this.atts.enableDCD) { /* Set KeepAlives */
|
||
|
|
if (this.atts.expireTime) {
|
||
|
|
this.stream.setKeepAlive(true, this.atts.expireTime);
|
||
|
|
} else {
|
||
|
|
this.stream.setKeepAlive(true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (this.atts.tcpNoDelay) { /* Turn off Nagle's unless explicitly enabled by user */
|
||
|
|
this.stream.setNoDelay(true);
|
||
|
|
}
|
||
|
|
if (address.protocol.toUpperCase() == "TCPS") {
|
||
|
|
let secureContext;
|
||
|
|
this.secure = true;
|
||
|
|
if (this.atts.sslAllowWeakDNMatch)
|
||
|
|
this.doDNMatch = false; //Don't match initial connect
|
||
|
|
try {
|
||
|
|
secureContext = tls.createSecureContext({
|
||
|
|
cert: this.atts.wallet,
|
||
|
|
key: this.atts.wallet,
|
||
|
|
passphrase: this.atts.walletPassword,
|
||
|
|
ca: this.atts.wallet,
|
||
|
|
});
|
||
|
|
} catch (err) {
|
||
|
|
errors.throwErr(errors.ERR_TLS_INIT_FAILURE, err.message);
|
||
|
|
}
|
||
|
|
await this.tlsConnect(secureContext, this.stream);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
if (this.stream) {
|
||
|
|
this.setupEventHandlers();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Disconnect Network Transoprt
|
||
|
|
* @param {int} type
|
||
|
|
* @returns Promise
|
||
|
|
*/
|
||
|
|
disconnect(type) { /* Disconnect function for TCP sockets */
|
||
|
|
if (this.connected && !this.err) {
|
||
|
|
if (type == constants.NSFIMM)
|
||
|
|
this.stream.destroy();
|
||
|
|
else
|
||
|
|
this.stream.end();
|
||
|
|
}
|
||
|
|
this.stream = null;
|
||
|
|
this.connected = false;
|
||
|
|
this.drainWaiter = null;
|
||
|
|
this.readWaiter = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the string containing a packet dump.
|
||
|
|
* @param {Buffer} buffer containing packet data
|
||
|
|
*/
|
||
|
|
getPacketDump(buffer) {
|
||
|
|
const lines = [];
|
||
|
|
for (let i = 0; i < buffer.length; i += 8) {
|
||
|
|
const address = i.toString().padStart(4, '0');
|
||
|
|
const block = buffer.slice(i, i + 8);
|
||
|
|
const hexDumpValues = [];
|
||
|
|
const printableValues = [];
|
||
|
|
for (const hexByte of block) {
|
||
|
|
hexDumpValues.push(hexByte.toString(16).toUpperCase().padStart(2, '0'));
|
||
|
|
if (hexByte > 0x20 && hexByte < 0x7f) {
|
||
|
|
printableValues.push(String.fromCharCode(hexByte));
|
||
|
|
} else {
|
||
|
|
printableValues.push(".");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
while (hexDumpValues.length < 8) {
|
||
|
|
hexDumpValues.push(" ");
|
||
|
|
printableValues.push(" ");
|
||
|
|
}
|
||
|
|
const hexValuesBlock = hexDumpValues.join(" ");
|
||
|
|
const printableBlock = printableValues.join("");
|
||
|
|
lines.push(`${address} : ${hexValuesBlock} |${printableBlock}|`);
|
||
|
|
}
|
||
|
|
return lines.join("\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Print the packet to the console.
|
||
|
|
* @param {String} operation which was performed
|
||
|
|
* @param {Buffer} buffer containing packet data
|
||
|
|
*/
|
||
|
|
printPacket(operation, buffer) {
|
||
|
|
const now = new Date();
|
||
|
|
const formattedDate =
|
||
|
|
`${now.getFullYear()}-${now.getMonth().toString().padStart(2, '0')}-` +
|
||
|
|
`${now.getDay().toString().padStart(2, '0')} ` +
|
||
|
|
`${now.getHours().toString().padStart(2, '0')}:` +
|
||
|
|
`${now.getMinutes().toString().padStart(2, '0')}:` +
|
||
|
|
`${now.getSeconds().toString().padStart(2, '0')}.` +
|
||
|
|
`${now.getMilliseconds().toString().padStart(3, '0')}`;
|
||
|
|
const packetDump = this.getPacketDump(buffer);
|
||
|
|
console.log(`${formattedDate} ${operation}:\n${packetDump}\n`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check for errors
|
||
|
|
*/
|
||
|
|
checkErr() {
|
||
|
|
if (!this.connected || this.err) {
|
||
|
|
let err;
|
||
|
|
if (this.savedErr) {
|
||
|
|
err = errors.getErr(errors.ERR_CONNECTION_LOSTCONTACT,
|
||
|
|
this.host, this.port, this.atts.connectionId, this.savedErr.message);
|
||
|
|
} else {
|
||
|
|
err = errors.getErr(errors.ERR_CONNECTION_EOF, this.host, this.port, this.atts.connectionId,);
|
||
|
|
}
|
||
|
|
/* Wrap around NJS-500 */
|
||
|
|
const newErr = errors.getErr(errors.ERR_CONNECTION_CLOSED);
|
||
|
|
newErr.message = newErr.message + "\n" + err.message;
|
||
|
|
throw (newErr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Transport Send
|
||
|
|
* @param {Buffer} buf Buffer to send
|
||
|
|
* @returns Promise
|
||
|
|
*/
|
||
|
|
send(buf) {
|
||
|
|
this.checkErr();
|
||
|
|
if (process.env.NODE_ORACLEDB_DEBUG_PACKETS)
|
||
|
|
this.printPacket(`Sending packet ${this.packetNum} on stream ${this.streamNum}`, buf);
|
||
|
|
const result = this.stream.write(buf, (err) => {
|
||
|
|
if (err) {
|
||
|
|
this.savedErr = err;
|
||
|
|
this.err = true;
|
||
|
|
this._notifyWaiters();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (!result) {
|
||
|
|
this.needsDrain = true;
|
||
|
|
}
|
||
|
|
this.numPacketsSinceLastWait++;
|
||
|
|
this.packetNum++;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Should writing to the transport be paused? This occurs if draining is
|
||
|
|
* required or if the number of packets written since the last pause exceeds
|
||
|
|
* 100 (in order to avoid starvation of the event loop during large writes).
|
||
|
|
*/
|
||
|
|
shouldPauseWrite() {
|
||
|
|
return (this.needsDrain || this.numPacketsSinceLastWait >= 100);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Perform a wait -- if draining is required, then until the drain event is
|
||
|
|
* emitted or if draining is not required, then a simple setImmediate() that
|
||
|
|
* ensures that the event loop is not starved.
|
||
|
|
*/
|
||
|
|
async pauseWrite() {
|
||
|
|
this.checkErr();
|
||
|
|
if (this.needsDrain) {
|
||
|
|
await new Promise((resolve) => {
|
||
|
|
this.drainWaiter = resolve;
|
||
|
|
});
|
||
|
|
this.checkErr();
|
||
|
|
} else {
|
||
|
|
await new Promise((resolve) => Timers.setImmediate(resolve));
|
||
|
|
}
|
||
|
|
this.numPacketsSinceLastWait = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Start Async reads
|
||
|
|
*/
|
||
|
|
startRead() {
|
||
|
|
let tempBuf;
|
||
|
|
this.packets = [];
|
||
|
|
this.stream.on('data', (chunk) => {
|
||
|
|
|
||
|
|
// append buffer if previous chunk(s) were insufficient for a full packet
|
||
|
|
if (tempBuf) {
|
||
|
|
tempBuf = Buffer.concat([tempBuf, chunk]);
|
||
|
|
} else {
|
||
|
|
tempBuf = chunk;
|
||
|
|
}
|
||
|
|
|
||
|
|
while (tempBuf.length >= PACKET_HEADER_SIZE) {
|
||
|
|
|
||
|
|
// determine the length of the packet
|
||
|
|
let len;
|
||
|
|
if (this.largeSDU) {
|
||
|
|
len = tempBuf.readUInt32BE();
|
||
|
|
} else {
|
||
|
|
len = tempBuf.readUInt16BE();
|
||
|
|
}
|
||
|
|
|
||
|
|
// not enough for a full packet so wait for more data to arrive
|
||
|
|
if (len > tempBuf.length)
|
||
|
|
break;
|
||
|
|
|
||
|
|
// enough for a full packet, extract details from the packet header
|
||
|
|
// and pass them along for processing
|
||
|
|
const packet = {
|
||
|
|
buf: tempBuf.subarray(0, len),
|
||
|
|
type: tempBuf[4],
|
||
|
|
flags: tempBuf[5],
|
||
|
|
num: this.packetNum++
|
||
|
|
};
|
||
|
|
this.packets.push(packet);
|
||
|
|
if (this.readWaiter) {
|
||
|
|
this.readWaiter();
|
||
|
|
this.readWaiter = null;
|
||
|
|
}
|
||
|
|
if (process.env.NODE_ORACLEDB_DEBUG_PACKETS)
|
||
|
|
this.printPacket(`Receiving packet ${packet.num} on stream ${this.streamNum}`, packet.buf);
|
||
|
|
|
||
|
|
// if the packet consumed all of the bytes (most common scenario), then
|
||
|
|
// simply clear the temporary buffer; otherwise, retain whatever bytes
|
||
|
|
// are unused and see if sufficient data is available for another
|
||
|
|
// packet
|
||
|
|
if (len === tempBuf.length) {
|
||
|
|
tempBuf = null;
|
||
|
|
break;
|
||
|
|
} else {
|
||
|
|
tempBuf = tempBuf.subarray(len);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Synchronous receive
|
||
|
|
* @returns a single packet or undefined if no packets are available
|
||
|
|
*/
|
||
|
|
syncReceive() {
|
||
|
|
return this.packets.shift();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Asynchronous receive
|
||
|
|
* @returns a single packet
|
||
|
|
*/
|
||
|
|
async receive() {
|
||
|
|
if (this.packets.length === 0) {
|
||
|
|
this.checkErr();
|
||
|
|
await new Promise((resolve) => {
|
||
|
|
this.readWaiter = resolve;
|
||
|
|
this.numPacketsSinceLastWait = 0;
|
||
|
|
});
|
||
|
|
this.checkErr();
|
||
|
|
}
|
||
|
|
return this.packets.shift();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* TLS renegotiate
|
||
|
|
* @returns Promise
|
||
|
|
*/
|
||
|
|
async renegTLS() {
|
||
|
|
let initTLSDone = false;
|
||
|
|
this.checkErr();
|
||
|
|
try {
|
||
|
|
this.doDNMatch = true;
|
||
|
|
const secureContext = tls.createSecureContext({
|
||
|
|
cert: this.atts.wallet,
|
||
|
|
key: this.atts.wallet,
|
||
|
|
passphrase: this.atts.walletPassword,
|
||
|
|
ca: this.atts.wallet,
|
||
|
|
});
|
||
|
|
initTLSDone = true;
|
||
|
|
await this.tlsConnect(secureContext, this.connStream);
|
||
|
|
} catch (err) {
|
||
|
|
if (!initTLSDone)
|
||
|
|
errors.throwErr(errors.ERR_TLS_INIT_FAILURE, err.message);
|
||
|
|
else throw err;
|
||
|
|
} finally {
|
||
|
|
this.setupEventHandlers();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup handling of events
|
||
|
|
*/
|
||
|
|
setupEventHandlers() {
|
||
|
|
this.stream.removeAllListeners();
|
||
|
|
|
||
|
|
this.stream.on('error', (err) => {
|
||
|
|
this.savedErr = err;
|
||
|
|
this.err = true;
|
||
|
|
this._notifyWaiters();
|
||
|
|
});
|
||
|
|
|
||
|
|
this.stream.on('end', () => {
|
||
|
|
this.err = true;
|
||
|
|
this._notifyWaiters();
|
||
|
|
});
|
||
|
|
|
||
|
|
this.stream.on('close', () => {
|
||
|
|
this.connected = false;
|
||
|
|
this._notifyWaiters();
|
||
|
|
});
|
||
|
|
|
||
|
|
this.stream.on('drain', () => {
|
||
|
|
this.needsDrain = false;
|
||
|
|
if (this.drainWaiter) {
|
||
|
|
this.drainWaiter();
|
||
|
|
this.drainWaiter = null;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get Transport Attributes
|
||
|
|
* @param {int} opcode type of attribute
|
||
|
|
* @returns attribute value
|
||
|
|
*/
|
||
|
|
getOption(opcode) {
|
||
|
|
this.checkErr();
|
||
|
|
switch (opcode) {
|
||
|
|
case constants.NT_MOREDATA: /* More data available to read */
|
||
|
|
return (this.packets.length > 0);
|
||
|
|
case constants.REMOTEADDR: /* Remote Address */
|
||
|
|
{
|
||
|
|
const socket = this.secure ? this.connStream : this.stream;
|
||
|
|
return (socket.remoteAddress + ":" + socket.remotePort);
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
errors.throwErr(errors.ERR_INTERNAL, "getOption not supported for opcode " + opcode);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Notify the waiters (drain and read) and reset them, if applicable.
|
||
|
|
*/
|
||
|
|
_notifyWaiters() {
|
||
|
|
if (this.drainWaiter) {
|
||
|
|
this.drainWaiter();
|
||
|
|
this.drainWaiter = null;
|
||
|
|
}
|
||
|
|
if (this.readWaiter) {
|
||
|
|
this.readWaiter();
|
||
|
|
this.readWaiter = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate SNI data.
|
||
|
|
*/
|
||
|
|
generateSNI() {
|
||
|
|
/* No SNI if source route is set */
|
||
|
|
if ((findValue(this.atts.cDataNVPair, ["DESCRIPTION", "SOURCE_ROUTE"]) == "yes") ||
|
||
|
|
(findValue(this.atts.cDataNVPair, ["DESCRIPTION", "ADDRESS_LIST", "SOURCE_ROUTE"]) == "yes"))
|
||
|
|
return null;
|
||
|
|
|
||
|
|
const cdnvp = findNVPair(this.atts.cDataNVPair, "CONNECT_DATA");
|
||
|
|
/* Loop through the list of params */
|
||
|
|
for (let i = 0; i < cdnvp.getListSize(); i++) {
|
||
|
|
const child = cdnvp.getListElement(i);
|
||
|
|
if (!sniAllowedCDParams.includes(child.name.toUpperCase()))
|
||
|
|
return null; /* No SNI for unsupported Connect Data params */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Generate SNI */
|
||
|
|
let value, sni = "";
|
||
|
|
for (let i = 0; i < sniParams.length; i++) {
|
||
|
|
if ((value = findValue(this.atts.cDataNVPair, ["DESCRIPTION", "CONNECT_DATA", sniParams[i]]))) {
|
||
|
|
if (sniParams[i] == 'SERVER') /* For server type just pick the first letter */
|
||
|
|
sni += sniMap[i] + "1" + "." + value[0] + ".";
|
||
|
|
else
|
||
|
|
sni += sniMap[i] + value.length + "." + value + ".";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sni += "V3." + constants.TNS_VERSION_DESIRED; /* Version */
|
||
|
|
|
||
|
|
const match_pattern = new RegExp("^[A-Za-z0-9._-]+$");
|
||
|
|
if (!(sni.match(match_pattern)))
|
||
|
|
return null; /* No SNI if special characters are present */
|
||
|
|
|
||
|
|
if (sni.length > SNI_MAX_BYTES)
|
||
|
|
return null; /* Max allowed length */
|
||
|
|
|
||
|
|
return sni;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = NTTCP;
|