Files
Cue-View/src/search.js
T
2023-06-23 03:23:10 -04:00

268 lines
7.7 KiB
JavaScript

const { ipcRenderer } = require('electron');
const dgram = require('dgram');
const bonjour = require('bonjour')();
const net = require('net');
const os = require('os');
const ip = require('ip');
const { Netmask } = require('netmask');
const DEVICE = require('./device.js');
const PLUGINS = require('./plugins.js');
let searching = false;
let allServers = false;
let validInterfaces = {};
function getServers() {
const interfaces = os.networkInterfaces();
const result = [];
validInterfaces = {};
Object.keys(interfaces).forEach((key) => {
const addresses = interfaces[key];
for (let i = addresses.length; i--; ) {
const address = addresses[i];
if (address.family === 'IPv4' && !address.internal && address.address.substring(0, 3) !== '169') {
let subnet = ip.subnet(address.address, address.netmask);
let current = ip.toLong(subnet.firstAddress);
let last = ip.toLong(subnet.lastAddress) - 1;
address.searchTruncated = false;
if (last - current > 2296) {
subnet = ip.subnet(address.address, '255.255.248.0');
last = ip.toLong(subnet.lastAddress) - 1;
address.searchTruncated = true;
}
// console.log(`range ${subnet.firstAddress} - ${subnet.lastAddress}`);
address.broadcastAddress = subnet.broadcastAddress;
address.firstSearchAddress = subnet.firstAddress;
address.lastSearchAddress = subnet.lastAddress;
if (!validInterfaces[key]) {
validInterfaces[key] = [];
}
validInterfaces[key].push(address);
while (current++ < last) result.push(ip.fromLong(current));
}
}
});
return result;
}
function getNetworkInterfaces() {
getServers();
return validInterfaces;
}
module.exports.getNetworkInterfaces = getNetworkInterfaces;
const searchSockets = [];
function searchAll() {
if (searching) {
return;
}
searching = true;
ipcRenderer.send('disableSearchAll', '');
document.getElementById('search-button').style.opacity = 0.2;
console.log('Searching...');
allServers = getServers();
let TCPFlag = true;
if (allServers.length > 2296) {
alert(
'Unable to search for TCP devices - subnet too large!\n\nCue View requires subnet 255.255.248.0 (/21) or smaller.'
);
TCPFlag = false;
}
Object.keys(PLUGINS.all).forEach((pluginType) => {
const plugin = PLUGINS.all[pluginType];
try {
const searchType = plugin.config.searchOptions.type;
if (searchType === 'TCPport') {
if (TCPFlag) {
searchTCP(pluginType, plugin.config);
}
} else if (searchType === 'Bonjour') {
searchBonjour(pluginType, plugin.config);
} else if (searchType === 'UDPsocket') {
searchUDP(pluginType, plugin.config);
} else if (searchType === 'multicast') {
searchMulticast(pluginType, plugin.config);
} else if (searchType === 'UDPScan') {
searchUDPScan(pluginType, plugin.config);
}
} catch (err) {
console.error(`Unable to search for plugin ${pluginType}`);
}
});
setTimeout(() => {
searching = false;
document.getElementById('search-button').style.opacity = '';
for (let i = 0; i < searchSockets.length; i++) {
try {
searchSockets[i].close();
} catch (err) {
//
}
}
ipcRenderer.send('enableSearchAll', '');
}, 10000);
}
module.exports.searchAll = searchAll;
function searchBonjour(pluginType, pluginConfig) {
bonjour.find({ type: pluginConfig.searchOptions.bonjourName }, (e) => {
const validAddresses = [];
e.addresses.forEach((address) => {
if (!address.includes(':')) {
validAddresses.push(address);
}
});
DEVICE.registerDevice(
{
type: pluginType,
defaultName: e.name,
remotePort: e.port,
addresses: validAddresses,
},
'fromSearch'
);
});
}
function searchTCP(pluginType, pluginConfig) {
for (let i = 0; i < allServers.length; i++) {
TCPtest(allServers[i], pluginType, pluginConfig);
}
}
function TCPtest(ipAddr, pluginType, pluginConfig) {
const client = net.createConnection(pluginConfig.searchOptions.testPort, ipAddr, () => {
client.write(pluginConfig.searchOptions.searchBuffer);
});
client.on('data', (data) => {
if (pluginConfig.searchOptions.validateResponse(data)) {
client.end(
'',
'utf8',
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
remotePort: pluginConfig.remotePort,
addresses: [ipAddr],
},
'fromSearch'
)
);
}
});
client.on('error', (err) => {
// no device here
});
}
function searchUDPScan(pluginType, pluginConfig) {
for (let i = 0; i < Object.keys(validInterfaces).length; i++) {
const interfaceID = Object.keys(validInterfaces)[i];
const interfaceObj = validInterfaces[interfaceID];
interfaceObj.forEach((netInterface) => {
const udpSocket = dgram.createSocket('udp4');
udpSocket.bind(pluginConfig.searchOptions.listenPort, netInterface.address);
udpSocket.on('message', (msg, info) => {
if (pluginConfig.searchOptions.validateResponse(msg, info, DEVICE.all)) {
udpSocket.close();
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
port: pluginConfig.defaultPort,
addresses: [info.address],
},
'fromSearch'
);
}
});
const interfaceBlock = new Netmask(netInterface.cidr);
interfaceBlock.forEach((address, long, index) => {
udpSocket.send(pluginConfig.searchOptions.searchBuffer, pluginConfig.searchOptions.devicePort, address);
});
});
}
}
function searchUDP(pluginType, pluginConfig) {
for (let i = 0; i < Object.keys(validInterfaces).length; i++) {
const interfaceID = Object.keys(validInterfaces)[i];
const interfaceObj = validInterfaces[interfaceID];
const j = searchSockets.push(dgram.createSocket('udp4')) - 1;
searchSockets[j].bind(pluginConfig.searchOptions.listenPort, () => {
searchSockets[j].on('message', (msg, info) => {
if (pluginConfig.searchOptions.validateResponse(msg, info, DEVICE.all)) {
searchSockets[j].close();
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
remotePort: pluginConfig.remotePort,
addresses: [info.address],
},
'fromSearch'
);
}
});
});
searchSockets[j].on('listening', () => {
searchSockets[j].setBroadcast(true);
searchSockets[j].send(
pluginConfig.searchOptions.searchBuffer,
pluginConfig.searchOptions.devicePort,
interfaceObj[0].broadcastAddress,
(err) => {
// console.log(err);
}
);
});
}
}
function searchMulticast(pluginType, pluginConfig) {
const socket = dgram.createSocket('udp4');
socket.on('message', (msg, info) => {
if (pluginConfig.searchOptions.validateResponse(msg, info)) {
socket.close(() => {
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
remotePort: pluginConfig.remotePort,
addresses: [info.address],
},
'fromSearch'
);
});
}
});
socket.bind(pluginConfig.searchOptions.port, () => {
for (let i = 0; i < Object.keys(validInterfaces).length; i++) {
const interfaceID = Object.keys(validInterfaces)[i];
const interfaceObj = validInterfaces[interfaceID];
socket.addMembership(pluginConfig.searchOptions.address, interfaceObj[0].address);
}
});
}