Is there a setsockopt/getsockopt-like socket options manipulation functionality in node.js?
I'm expanding on the comment left by socketpair which shows getsockopt. You can accomplish this by using ffi and ref. I've reformatted this to allow it to be easily manipulated.
I edited my comment because I had to make some changes to make the code work on both Linux and Win32. I had to create a node library for Windows to get the socket handle and pass it to setsockopt. Be aware that Linux and Windows may have different values for socket options
Edit: Here's a cleaned up piece of of production code I'm using:
var net = require("net");
var ffi = require("ffi");
var ref = require("ref");
var getSocketHandleAddress;
var SOL_SOCKET = 0x1;
var SO_OOBINLINE = 0xA;
var _setsockopt;
if (process.platform == "win32") {
SOL_SOCKET = 0xffff;
SO_OOBINLINE = 0x0100;
}
var setSocketOption = function (handle, level, option, value) {
if (!_setsockopt) {
var library;
var paramTypes;
if (process.platform === "win32") {
library = "ws2_32.dll";
paramTypes = [
ref.types.int,
ref.types.int,
ref.types.int,
ref.refType(ref.types.void),
ref.types.int
];
} else {
paramTypes = [
ref.types.int,
ref.types.int,
ref.types.int,
ref.refType(ref.types.void),
ref.refType(ref.types.int)
];
}
var lib = new ffi.DynamicLibrary(library);
_setsockopt = ffi.ForeignFunction(
lib.get("setsockopt"),
ref.types.int,
paramTypes);
}
var refType;
var length;
if (typeof value === "boolean") {
refType = ref.types.bool;
} else {
refType = ref.types.int;
}
if (process.platform !== "win32") {
return _setsockopt(
handle.fd,
level,
option,
ref.alloc(refType, value),
ref.alloc(ref.types.int, refType.size)
);
}
if (!getSocketHandleAddress) {
getSocketHandleAddress = require("getsockethandleaddress");
}
return _setsockopt(
getSocketHandleAddress.getAddress(handle),
level,
option,
ref.alloc(refType, value),
refType.size
);
};
var tcpserver = net.createServer(function (socket) {
var ret = setSocketOption(socket._handle, SOL_SOCKET, SO_OOBINLINE, true);
if (ret !== 0) {
console.error("OOB Inline socket option failed: " + ret);
}
});
This is my getsockopt:
var ffi = require('ffi');
var net = require('net');
var StructType = require('ref-struct');
var ref = require('ref');
var current = ffi.Library(null, {
'getsockopt': [ 'int', [ 'int', 'int', 'int', 'pointer', 'pointer']],
'ntohs': ['uint16', ['uint16']],
// const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
});
var SOL_IP = 0;
var SO_ORIGINAL_DST = 80;
var AF_INET = 2;
var sockaddr_in = StructType([
['int16', 'sin_family'],
['uint16', 'sin_port'],
['uint32', 'sin_addr'],
['uint32', 'trash1'],
['uint32', 'trash2'],
]);
function get_original_dst(client) {
var dst = new sockaddr_in;
var dstlen = ref.alloc(ref.types.int, sockaddr_in.size);
var r = current.getsockopt(client._handle.fd, SOL_IP, SO_ORIGINAL_DST, dst.ref(), dstlen);
if (r === -1)
throw new Error("getsockopt(SO_ORIGINAL_DST) error");
if (dst.sin_family !== AF_INET)
throw new Error("getsockopt(SO_ORIGINAL_DST) returns unknown family: " + dst.sin_family );
// TODO: inet_ntop. inet_ntoa is _UNSAFE_
var ipaddr = dst.ref(); ipaddr = ipaddr[4] + "." + ipaddr[5] + "." + ipaddr[6] + "." + ipaddr[7];
return [ipaddr, current.ntohs(dst.sin_port)];
}
module.exports.get_original_dst = get_original_dst;
Kind of late but here is a pacakge on npm https://www.npmjs.com/package/net-keepalive
Provides high-level access to socket options like TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT
var Net = require('net')
, NetKeepAlive = require('net-keepalive')
;
// Create a TCP Server
var srv = Net.createServer(function(s){>
console.log('Connected %j', s.address())
// Doesn't matter what it does
s.pipe(s)
});
// Start on some port
srv.listen(1337, function(){
console.log('Listening on %j', srv.address())
});
// Connect to that server
var s = Net.createConnection({port:1337}, function(){
console.log('Connected to %j', s.address())
//IMPORTANT: KeepAlive must be enabled for this to work
s.setKeepAlive(true, 1000)
// Set TCP_KEEPINTVL for this specific socket
NetKeepAlive.setKeepAliveInterval(s, 1000)
// and TCP_KEEPCNT
NetKeepAlive.setKeepAliveProbes(s, 1)
});
Related
I'm making an online shell to access my computer from another computer or device. I can run any program, sending and receiving stdio data using socket.io. I run the programs using execFile() from child_process. The problem is that, when I try to run bash, sh, zsh, csh or any other shell it just doesn't work, no error message or anything. How can I run a shell as a child process in node.js(prefer using execFile, but it doesn't matter)? This is the code I'm using:
Server:
let fs = require("fs");
let socket = require("socket.io");
let express = require("express");
let stringio = require("#rauschma/stringio");
let childprocess = require("child_process");
let app = express();
app.use(express.static("public"));
let server = app.listen(3000);
console.log("Server running!");
let io = socket(server);
io.sockets.on("connection", connectionListener);
function connectionListener(socket) {
console.log("New client!");
let child = childprocess.execFile("bash", ["test.sh"], {
stdio: [process.stdin, process.stdout, process.stderr]
});
socket.on("stdin-packet", sendtochild);
function sendtochild(packet) {
child.stdin.write(packet.rawtext);
}
child.stdout.on('data', function(data) {
let packet = {
rawtext: data.toString()
};
socket.emit("stdout-packet", packet);
console.log("Packet sended!");
});
}
Client:
let socket = io.connect("localhost:3000");
function TerminalScreen(width, height) {
this.width = width;
this.height = height;
this.chars = "";
this.print = function(text) {
let isansi = false;
for (let i = 0; i < text.length; i++) {
if (text.charCodeAt(i) == 27) {
isansi = true;
} else if (text.charAt(i) == 'm' || text.charAt(i) == 'K' || text.charAt(i) == 'H' || text.charAt(i) == 'f' || text.charAt(i) == 'J') {
if (isansi) {
isansi = false;
} else {
this.chars += text.charAt(i);
}
} else if (!isansi) {
this.chars += text.charAt(i);
}
}
this.chars = this.chars.replace("\n", "<br>");
}
this.clear = function() {
this.chars = "";
}
this.update = function() {
document.getElementById("terminal-div").innerHTML = this.chars;
}
}
let screen = new TerminalScreen();
socket.on("stdout-packet", packetReceived);
function packetReceived(packet) {
screen.print(packet.rawtext);
screen.update();
}
document.onkeypress = function (e) {
e = e || window.event;
let packet = {
rawtext: String.fromCharCode(e.keyCode)
};
socket.emit("stdin-packet", packet);
};
Most likely, because bash detects it's not running in a pseudo terminal (pty), it assumes it is running non-interactively. You can either create a full pseudo terminal (which things like expect or ssh will do), or you may be able to force it with the -i flag.
Using the node-pty package works for me.
This is not really a question, but I wonder to know if what I did is correct because its working!
So, lets to the question, I`m monitoring many interfaces (PPPoE clients) at same to know its traffic reading the statistics from linux.
I`m using npm packages: express, socket.io and socket.io-stream.
Client:
var sessionsAccel = $('table.accel').DataTable([]);
sessionsAccel.on('preDraw', function() {
$('.interfaceAccel').each(function(i) {
var t = $(this).data();
sockets['socket' + t.id].disconnect();
delete speeds['tx_bytes' + t.id];
delete speeds['rx_bytes' + t.id];
});
})
.on('draw', function() {
$('.interfaceAccel').each(function(i) {
var t = $(this).data();
sockets['socket' + t.id] = io.connect('http://172.16.101.2:3000/status', {
query: 'interface=' + t.interface,
'forceNew': true
});
sockets['socket' + t.id].on("connect", function() {
ss(sockets['socket' + t.id]).on('sendStatus', function(stream, data) {
if (typeof speeds['tx_bytes' + t.id] != 'undefined') {
var speedtx = (data.tx_bytes - speeds['tx_bytes' + t.id]) * 8 / 1000;
var speedrx = (data.rx_bytes - speeds['rx_bytes' + t.id]) * 8 / 1000;
if (speedtx > 1000) {
speedtx = speedtx / 1000;
speedtx = speedtx.toFixed(2);
speedtx_info = speedtx + ' Mbps';
} else {
speedtx = speedtx.toFixed(2);
speedtx_info = speedtx + ' kbps';
}
if (speedrx > 1000) {
speedrx = speedrx / 1000;
speedrx = speedrx.toFixed(2);
speedrx_info = speedrx + ' Mbps';
} else {
speedrx = speedrx.toFixed(2);
speedrx_info = speedrx + ' kbps';
}
$('.tx_' + t.id).html(speedtx_info);
$('.rx_' + t.id).html(speedrx_info);
}
speeds['tx_bytes' + t.id] = data.tx_bytes;
speeds['rx_bytes' + t.id] = data.rx_bytes;
});
});
});
})
Server:
const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
const ss = require('socket.io-stream');
const path = require('path');
const fs = require('fs');
function getIntInfo(interface) {
if(fs.existsSync('/sys/class/net/'+ interface +'/statistics/tx_bytes')) {
var tx_bytes = fs.readFileSync('/sys/class/net/'+ interface +'/statistics/tx_bytes').toString();
var rx_bytes = fs.readFileSync('/sys/class/net/'+ interface +'/statistics/rx_bytes').toString();
var tx_packets = fs.readFileSync('/sys/class/net/'+ interface +'/statistics/tx_packets').toString();
var rx_packets = fs.readFileSync('/sys/class/net/'+ interface +'/statistics/rx_packets').toString();
return {tx_bytes : tx_bytes, rx_bytes : rx_bytes, tx_packets: tx_packets, rx_packets: rx_packets};
}else
return false;
}
io.of('/status').on('connection', function(socket) {
var query = socket.handshake.query['interface'];
var timer = setInterval(function() {
var stream = ss.createStream();
var info = getIntInfo(query);
ss(socket).emit('sendStatus', stream, info);
}, 1000);
socket.on('disconnect', function(){
socket.disconnect(true);
//console.info('disconnected user (id=' + socket.id + ').');
});
})
http.listen(3000, function(){
console.log('listening on *:3000');
});
That's it, every row from Datatable (which is the interface) open a socket connection and retrieve the statistics.
My question is, this will mess up my server with many I/O reading these files?
Since you're doing this every second for every connected client, it seems like you should probably cache this data so it doesn't have to be read from the disk or sent over the wire when it hasn't changed to save both server load and bandwidth usage. But, the details of how to best do that depend upon knowledge about your particular application that you haven't included.
You can at least use asynchronous I/O like this:
const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
function getIntInfo(interface) {
function readInfo(name) {
return readFile('/sys/class/net/'+ interface +'/statistics/' + name).then(data => data.toString());
}
return Promise.all(
readFile('tx_bytes'),
readFile('rx_bytes'),
readFile('tx_packets'),
readFile('rx_packets')
).then(([tx_bytes, rx_bytes, tx_packets, rx_packets]) => {
return {tx_bytes, rx_bytes, tx_packets, rx_packets};
}).catch(err => {
console.log(err);
return false;
});
}
And, you have to stop the interval any time a client disconnects and change how it calls getIntInfo():
io.of('/status').on('connection', function(socket) {
var query = socket.handshake.query['interface'];
var timer = setInterval(function() {
getIntInfo(query).then(info => {
var stream = ss.createStream();
ss(socket).emit('sendStatus', stream, info);
});
}, 1000);
socket.on('disconnect', function(){
// stop the timer for this connection
clearInterval(timer);
});
});
Now that I think about it a bit more, you could improve scalability quite a bit by having just one interval timer that was reading the data and then sending that one set of data to all listening clients that had connected to the /status namespace. You would reduce the file reading from once per second for every client to just once per second for no matter how many clients.
With incomming data like STX(0x02)..Data..ETX(0x03)
I can process data by byte sequence parser:
var SerialPort = require('serialport');
var port = new SerialPort('/dev/tty-usbserial1', {
parser: SerialPort.parsers.byteDelimiter([3])
});
port.on('data', function (data) {
console.log('Data: ' + data);
});
But my actual incomming data is STX(0x02)..Data..ETX(0x03)..XX(plus 2 characters to validate data)
How can I get appropriate data?
Thanks!
Since version 2 or 3 of node-serialport, parsers have to inherit the Stream.Tansform class. In your example, that would become a new class.
Create a file called CustomParser.js :
class CustomParser extends Transform {
constructor() {
super();
this.incommingData = Buffer.alloc(0);
}
_transform(chunk, encoding, cb) {
// chunk is the incoming buffer here
this.incommingData = Buffer.concat([this.incommingData, chunk]);
if (this.incommingData.length > 3 && this.incommingData[this.incommingData.length - 3] == 3) {
this.push(this.incommingData); // this replaces emitter.emit("data", incomingData);
this.incommingData = Buffer.alloc(0);
}
cb();
}
_flush(cb) {
this.push(this.incommingData);
this.incommingData = Buffer.alloc(0);
cb();
}
}
module.exports = CustomParser;
Them use your parser like this:
var SerialPort = require('serialport');
var CustomParser = require('./CustomParser ');
var port = new SerialPort('COM1');
var customParser = new CustomParser();
port.pipe(customParser);
customParser.on('data', function(data) {
console.log(data);
});
Solved!
I write my own parser:
var SerialPort = require('serialport');
var incommingData = new Buffer(0);
var myParser = function(emitter, buffer) {
incommingData = Buffer.concat([incommingData, buffer]);
if (incommingData.length > 3 && incommingData[incommingData.length - 3] == 3) {
emitter.emit("data", incommingData);
incommingData = new Buffer(0);
}
};
var port = new SerialPort('COM1', {parser: myParser});
port.on('data', function(data) {
console.log(data);
});
I wrote following code to set keyboard cursor position. But get garbage. Any clue, what am I missing?
var ffi = require('ffi');
var ref = require('ref');
var Struct = require('ref-struct');
var point = Struct({
'x': 'long',
'y': 'long'
});
var user32 = ffi.Library('user32.dll', {
GetCaretPos:['bool',[locPtr]]
});
var pbuf = new point();
caretpos = user32.GetCaretPos(pbuf);
console.log(":",pbuf.x );
Next, I tried the following, but that doesn't work as well.
var ffi = require('ffi');
var ref = require('ref');
var Struct = require('ref-struct');
var voidPtr = ref.refType(ref.types.void);
var user32 = ffi.Library('user32.dll', {
GetCaretPos:['bool',[voidPtr]]
});
var pbuf = new Buffer(2);
caretpos = user32.GetCaretPos(pbuf);
var cpos =(new Uint8Array(pbuf));
console.log(">",cpos ); //Doesn't work **> Uint8Array [ 0, 0, 0, 0 ]**
As the article heading states "GetCursorPos in Node FFI" and return pointer x,y
The code below works for me, its a hack to get the x,y pointer.
var ffi = require('ffi');
var repbuffer = new Buffer(16); // holder for windows structures
var user32 = ffi.Library('user32.dll', {
'GetCursorPos':['bool',['pointer']]
});
//Show mouse cords at console every sec
setInterval( function() { getmousepos();}, 1000);
function getmousepos(){
var p = user32.GetCursorPos(repbuffer);
var x= repbuffer[0]+ (repbuffer[1]*256);
var y= repbuffer[4]+(repbuffer[5]*256);
console.log(x + " " + y);
}
What worked for me:
var PointStruct = Struct({
x: ffi.types.long,
y: ffi.types.long,
});
var user32 = ffi.Library("user32", {
GetCursorPos: ["bool", [ref.refType(PointStruct)]],
SetCursorPos: ["long", ["long", "long"]],
});
export function GetMousePos() {
let mousePosBuffer = ref.alloc(PointStruct);
let success = user32.GetCursorPos(mousePosBuffer);
let mousePos = mousePosBuffer["deref"]() as {x: number, y: number};
return new Vector2i(mousePos.x, mousePos.y);
}
export function SetMousePos(x: number, y: number) {
user32.SetCursorPos(x, y);
}
I recently stating coding in node.js and might be a very simple question.
Trying to write a XML parser/validator to validate xml schema and values against values/ xpath stored in an excel sheet.
Now once the validation function is complete I want to call a printResult function to print final result. However if I try to call the function immediately after the first function .. its printing variables initial values and if called within the first which is iterating though the number of xpaths present in excel sheet and printing result with increments.
var mocha = require('mocha');
var assert = require('chai').assert;
var fs = require('fs');
var parseString = require('xml2js').parseString;
var xpath = require('xpath');
var dom = require('xmldom').DOMParser;
var XLSX = require('xlsx');
var Excel = require("exceljs");
var should = require('chai').should();
var HashMap = require('hashmap');
var colors = require('colors');
require('/xmlValidator/dbConnect.js');
var map = new HashMap();
var elementMap = new HashMap();
var resultValue;
//console.log('hello'.green);
map.set("PASS", 0);
map.set("FAIL", 0);
map.set("INVALID_PATH", 0);
function computeResult(elementPath, result) {
var pass = map.get("PASS");
var fail = map.get("FAIL");
var invalidPath = map.get("INVALID_PATH");
elementMap.set(elementPath, result);
if (result == "PASS") {
pass++;
map.set("PASS", pass);
} else if (result == "FAIL") {
fail++;
map.set("FAIL", fail);
} else {
invalidPath++;
map.set("INVALID_PATH", invalidPath)
}
printResult();
}
function printResult() {
var pass = map.get("PASS");
var fail = map.get("FAIL");
var invalidPath = map.get("INVALID_PATH");
console.log(("PASS Count :" + pass).green);
console.log(("FAIL Count :" + fail).red);
console.log(("Inavlid Path :" + invalidPath).yellow);
elementMap.forEach(function(value, key) {
if (value == "INVALID_PATH")
console.log((key + ":" + value).yellow);
else if (value == "FAIL")
console.log((key + ":" + value).red);
else
console.log(key + ":" + value);
});
}
var workbook = new Excel.Workbook();
workbook.xlsx.readFile('utils/' + process.argv[2])
.then(function() {
var worksheet = workbook.getWorksheet(1);
worksheet.eachRow(function(row, rowNumber) {
//console.log(rowNumber);
var row = worksheet.getRow(rowNumber);
var dataPath1 = row.getCell("A").value;
var dataPath2 = row.getCell("B").value;
var dataPath = dataPath1 + dataPath2;
//console.log(dataPath);
var dataValue = row.getCell("D").value;
var flag = row.getCell("E").value;
//console.log(flag)
//console.log(dataValue);
if (!flag)
validate(dataPath, dataValue, rowNumber);
//else console.log("NOT EXECUTED" + rowNumber)
});
})
function validate(dataPath, dataValue, rowNumber) {
var fail = 0;
fs.readFile('utils/' + process.argv[3], 'utf8', function(err, data) {
if (err) {
console.log("ERROR ERROR ERROR ERROR ");
return console.log(err);
}
var doc = new dom().parseFromString(data);
var subId = String(xpath.select1(dataPath, doc));
if (subId == "undefined") {
/*console.log('undefined caught');
console.log("row number :" + rowNumber);*/
var resultValue = "INVALID_PATH";
computeResult(dataPath, resultValue);
} else {
var subId = xpath.select1(dataPath, doc);
var value = subId.lastChild.data;
/*console.log("row number :" + rowNumber);
console.log("actual value: " + value);
console.log("expected value:" + dataValue );*/
if (dataValue == null) {
assert.notEqual(value, dataValue, "value not found");
resultValue = "PASS";
computeResult(dataPath, resultValue);
} else {
if (value == dataValue)
resultValue = "PASS";
else resultValue = "FAIL";
computeResult(dataPath, resultValue);
}
}
});
}
In the code above i want to call printResult() function after validate function is completely executed (workbook.xlsx.readFile)
Can some one please help me out how to use done () function or make sync call ?
If fs.readFileAsync('utils/' + process.argv[3], 'utf8') can be executed once, then validate() will be completely synchronous and calling printResult() after the verification loop will be trivial.
In the main routine, you can simply aggregate two promises ...
var promise1 = workbook.xlsx.readFile();
var promise2 = fs.readFileAsync(); // requires `fs` to be promisified.
... before embarking on the verification loop.
Promise.all([promise1, promise2]).spread(/* verify here */);
Also a whole bunch of tidying can be considered, in particular :
establishing "PASS", "FAIL" and "INVALID_PATH" as constants to avoid lots of repetitive string creation,
using js plain objects in lieu of hashmap,
building map from elementMap inside the print function.
having validate() return its result and building elementMap in the main routine
Here's the whole thing, about as tight as I can get it. I may have made a few assumptions but hopefully not too many bad ones ...
var mocha = require('mocha');
var assert = require('chai').assert;
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs")); // allow Bluebird to take the pain out of promisification.
var parseString = require('xml2js').parseString;
var xpath = require('xpath');
var dom = require('xmldom').DOMParser;
var XLSX = require('xlsx');
var Excel = require("exceljs");
var should = require('chai').should();
// var HashMap = require('hashmap');
var colors = require('colors');
require('/xmlValidator/dbConnect.js');
const PASS = "PASS";
const FAIL = "FAIL";
const INVALID_PATH = "INVALID_PATH";
function printResult(elementMap) {
var key, result,
map = { PASS: 0, FAIL: 0, INVALID_PATH: 0 },
colorNames = { PASS: 'black', FAIL: 'red', INVALID_PATH: 'yellow' };
for(key in elementMap) {
result = elementMap[key];
map[(result === PASS || result === FAIL) ? result : INVALID_PATH] += 1;
console.log((key + ": " + result)[colorNames[result] || 'black']); // presumably colors can be applied with associative syntax? If so, then the code can be very concise.
}
console.log(("PASS Count: " + map.PASS)[colorNames.PASS]);
console.log(("FAIL Count: " + map.FAIL)[colorNames.FAIL]);
console.log(("Inavlid Path: " + map.INVALID_PATH)[colorNames.INVALID_PATH]);
}
function validate(doc, dataPath, dataValue) {
var subId = xpath.select1(dataPath, doc),
value = subId.lastChild.data,
result;
if (String(subId) == "undefined") {
result = INVALID_PATH;
} else {
if (dataValue === null) {
assert.notEqual(value, dataValue, "value not found"); // not too sure what this does
result = PASS;
} else {
result = (value === dataValue) ? PASS : FAIL;
}
}
return result;
}
//Main routine
var workbook = new Excel.Workbook();
var promise1 = workbook.xlsx.readFile('utils/' + process.argv[2]); // from the question, workbook.xlsx.readFile() appears to return a promise.
var promise2 = fs.readFileAsync('utils/' + process.argv[3], 'utf8');
Promise.all([promise1, promise2]).spread(function(data2, data3) {
var worksheet = workbook.getWorksheet(1),
doc = new dom().parseFromString(data3),
elementMap = {};
worksheet.eachRow(function(row, rowNumber) {
// var row = worksheet.getRow(rowNumber); // row is already a formal variable ???
var dataPath, dataValue;
if (!row.getCell('E').value)
dataPath = row.getCell('A').value + row.getCell('B').value;
dataValue = row.getCell('D').value;
elementMap[dataPath] = validate(doc, dataPath, dataValue);
});
printResult(elementMap);
});
Untested so may not run but at least you can raid the code for ideas.