MeshCentral/agents/modules_meshcore/linux-dhcp.js

324 lines
26 KiB
JavaScript
Raw Normal View History

2021-07-23 19:33:40 -04:00
/*
Copyright 2021 Intel Corporation
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
http://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.
* @description Mini DHCP Client Module, to fetch configuration data
* @author Bryan Roe & Ylian Saint-Hilaire
*/
// DHCP Information
if (Function.prototype.internal == null) { Object.defineProperty(Function.prototype, 'internal', { get: function () { return (this); } }); }
if (global._hide == null)
{
global._hide = function _hide(v)
{
if(v==null || (v!=null && typeof(v)=='boolean'))
{
var ret = _hide.currentObject;
if (v) { _hide.currentObject = null; }
return (ret);
}
else
{
_hide.currentObject = v;
}
}
}
addModule('promise2', Buffer.from('LyoNCkNvcHlyaWdodCAyMDE4IEludGVsIENvcnBvcmF0aW9uDQoNCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQoNCiAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjANCg0KVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQ0KZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywNCldJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLg0KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZA0KbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuDQoqLw0KDQp2YXIgcmVmVGFibGUgPSB7fTsNCg0KZnVuY3Rpb24gcHJvbWlzZUluaXRpYWxpemVyKHIsaikNCnsNCiAgICB0aGlzLl9yZXMgPSByOw0KICAgIHRoaXMuX3JlaiA9IGo7DQp9DQoNCmZ1bmN0aW9uIGdldFJvb3RQcm9taXNlKG9iaikNCnsNCiAgICB3aGlsZShvYmoucGFyZW50UHJvbWlzZSkNCiAgICB7DQogICAgICAgIG9iaiA9IG9iai5wYXJlbnRQcm9taXNlOw0KICAgIH0NCiAgICByZXR1cm4gKG9iaik7DQp9DQoNCmZ1bmN0aW9uIGV2ZW50X3N3aXRjaGVyKGRlc2lyZWRfY2FsbGVlLCB0YXJnZXQpDQp7DQogICAgcmV0dXJuICh7IF9PYmplY3RJRDogJ2V2ZW50X3N3aXRjaGVyJywgZnVuYzogdGFyZ2V0LmJpbmQoZGVzaXJlZF9jYWxsZWUpIH0pOw0KfQ0KDQpmdW5jdGlvbiBldmVudF9mb3J3YXJkZXIoc291cmNlT2JqLCBzb3VyY2VOYW1lLCB0YXJnZXRPYmosIHRhcmdldE5hbWUpDQp7DQogICAgc291cmNlT2JqLm9uKHNvdXJjZU5hbWUsIHRhcmdldE9iai5lbWl0LmJpbmQodGFyZ2V0T2JqKSk7DQp9DQoNCg0KZnVuY3Rpb24gcmV0dXJuX3Jlc29sdmVkKCkNCnsNCiAgICB2YXIgcGFybXMgPSBbJ3Jlc29sdmVkJ107DQogICAgZm9yICh2YXIgYWkgaW4gYXJndW1lbnRzKQ0KICAgIHsNCiAgICAgICAgcGFybXMucHVzaChhcmd1bWVudHNbYWldKTsNCiAgICB9DQogICAgdGhpcy5fWFNMRi5lbWl0LmFwcGx5KHRoaXMuX1hTTEYsIHBhcm1zKTsNCn0NCmZ1bmN0aW9uIHJldHVybl9yZWplY3RlZCgpDQp7DQogICAgdGhpcy5fWFNMRi5wcm9taXNlLl9fY2hpbGRQcm9taXNlLl9yZWooZSk7DQp9DQpmdW5jdGlvbiBlbWl0cmVqZWN0KGEpDQp7DQogICAgcHJvY2Vzcy5lbWl0KCd1bmNhdWdodEV4Y2VwdGlvbicsICdwcm9taXNlLnVuY2F1Z2h0UmVqZWN0aW9uOiAnICsgSlNPTi5zdHJpbmdpZnkoYSkpOw0KfQ0KZnVuY3Rpb24gUHJvbWlzZShwcm9taXNlRnVuYykNCnsNCiAgICB0aGlzLl9PYmplY3RJRCA9ICdwcm9taXNlJzsNCiAgICB0aGlzLnByb21pc2UgPSB0aGlzOw0KICAgIHRoaXMuX2ludGVybmFsID0geyBfT2JqZWN0SUQ6ICdwcm9taXNlLmludGVybmFsJywgcHJvbWlzZTogdGhpcywgY29tcGxldGVkOiBmYWxzZSwgZXJyb3JzOiBmYWxzZSwgY29tcGxldGVkQXJnczogW10sIGludGVybmFsQ291bnQ6IDAsIF91cDogbnVsbCB9Ow0KICAgIHJlcXVpcmUoJ2V2ZW50cycpLkV2ZW50RW1pdHRlci5jYWxsKHRoaXMuX2ludGVybmFsKTsNCiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywgInBhcmVudFByb21pc2UiLA0KICAgICAgICB7DQogICAgICAgICAgICBnZXQ6IGZ1bmN0aW9uICgpIHsgcmV0dXJuICh0aGlzLl91cCk7IH0sDQogICAgICAgICAgICBzZXQ6IGZ1bmN0aW9uICh2YWx1ZSkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICBpZiAodmFsdWUgIT0gbnVsbCAmJiB0aGlzLl91cCA9PSBudWxsKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgLy8gV2UgYXJlIG5vIGxvbmdlciBhbiBvcnBoYW4NCiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXMuX2ludGVybmFsLnVuY2F1Z2h0ICE9IG51bGwpDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgIGNsZWFySW1tZWRpYXRlKHRoaXMuX2ludGVybmFsLnVuY2F1Z2h0KTsNCiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuX2ludGVybmFsLnVuY2F1Z2h0ID0gbnVsbDsNCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICB0aGlzLl91cCA9IHZhbHVlOw0KICAgICAgICAgICAgfQ0KICAgICAgICB9KTsNCiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcywgImRlc2NyaXB0b3JNZXRhZGF0YSIsDQogICAgICAgIHsNCiAgICAgICAgICAgIGdldDogZnVuY3Rpb24gKCkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICByZXR1cm4gKHJlcXVpcmUoJ2V2ZW50cycpLmdldFByb3BlcnR5LmNhbGwodGhpcy5faW50ZXJuYWwsICc/X0ZpbmFsaXplckRlYnVnTWVzc2FnZScpKTsNCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICBzZXQ6IGZ1bmN0aW9uICh2YWx1ZSkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICByZXF1aXJlKCdldmVudHMnKS5zZXRQcm9wZXJ0eS5jYWxsKHRoaXMuX2ludGVybmFsLCAnP19GaW5hbGl6ZXJEZWJ1Z01lc3NhZ2UnLCB2YWx1ZSk7DQogICAgICAgICAgICB9DQogICAgICAgIH0pOw0KICAgIHRoaXMuX2ludGVybmFsLm9uKCd+JywgZnVuY3Rpb24gKCkNCiAgICB7DQogICAgICAgIHRoaXMuY29tcGxldGVkQXJncyA9IFtdOw0KICAgIH0pOw0KICAgIHRoaXMuX2ludGVybmFsLm9uKCduZXdMaXN0ZW5lcjInLCAoZnVuY3Rpb24gKGV2ZW50TmFtZSwgZXZlbnRDYWxsYmFjaykNCiAgICB7DQogICAgICAgIC8vY29uc29sZS5sb2coJ25ld0xpc3RlbmVyJywgZXZlbnROYW1lLCAnZXJyb3Jz
var promise = require('promise2');
function promise_default(res, rej)
{
this._res = res;
this._rej = rej;
}
function buf2addr(buf)
{
return (buf[0] + '.' + buf[1] + '.' + buf[2] + '.' + buf[3]);
}
function parseDHCP(buffer)
{
var i;
var packet = Buffer.alloc(buffer.length);
for (i = 0; i < buffer.length; ++i) { packet[i] = buffer[i]; }
var ret = { op: packet[0] == 0 ? 'REQ' : 'RES', hlen: packet[2] }; // OP Code
ret.xid = packet.readUInt32BE(4); // Transaction ID
ret.ciaddr = buf2addr(packet.slice(12, 16));
ret.yiaddr = buf2addr(packet.slice(16, 20));
ret.siaddr = buf2addr(packet.slice(20, 24));
ret.giaddr = buf2addr(packet.slice(24, 28));
ret.chaddr = packet.slice(28, 28 + ret.hlen).toString('hex:');
if (packet[236] == 99 && packet[237] == 130 && packet[238] == 83 && packet[239] == 99)
{
// Magic Cookie Validated
ret.magic = true;
ret.options = {};
i = 240;
while(i<packet.length)
{
switch(packet[i])
{
case 0:
i += 1;
break;
case 255:
ret.options[255] = true;
i += 2;
break;
default:
ret.options[packet[i]] = packet.slice(i + 2, i + 2 + packet[i + 1]);
switch(packet[i])
{
case 1: // Subnet Mask
ret.options.subnetmask = buf2addr(ret.options[1]);
delete ret.options[1];
break;
case 3: // Router
ret.options.router = [];
var ti = 0;
while (ti < ret.options[3].length)
{
ret.options.router.push(buf2addr(ret.options[3].slice(ti, ti + 4)));
ti += 4;
}
delete ret.options[3];
break;
case 6: // DNS
ret.options.dns = buf2addr(ret.options[6]);
delete ret.options[6];
break;
case 15: // Domain Name
ret.options.domainname = ret.options[15].toString();
delete ret.options[15];
break;
case 28: // Broadcast Address
ret.options.broadcastaddr = buf2addr(ret.options[28]);
delete ret.options[28];
break;
case 51: // Lease Time
ret.options.lease = { raw: ret.options[51].readInt32BE() };
delete ret.options[51];
ret.options.lease.hours = Math.floor(ret.options.lease.raw / 3600);
ret.options.lease.minutes = Math.floor((ret.options.lease.raw % 3600) / 60);
ret.options.lease.seconds = (ret.options.lease.raw % 3600) % 60;
break;
case 53: // Message Type
ret.options.messageType = ret.options[53][0];
delete ret.options[53];
break;
case 54: // Server
ret.options.server = buf2addr(ret.options[54]);
delete ret.options[54];
break;
}
i += (2 + packet[i + 1]);
break;
}
}
}
return (ret);
}
function createPacket(messageType, data)
{
var b = Buffer.alloc(245);
switch(messageType)
{
//case 0x02:
//case 0x04:
//case 0x05:
//case 0x06:
// b[0] = 0x00; // Reply
// break;
//case 0x01:
//case 0x03:
//case 0x07:
case 0x08:
b[0] = 0x01; // Request
break;
default:
throw ('DHCP(' + messageType + ') NOT SUPPORTED');
break;
}
// Headers
b[1] = 0x01; // Ethernet
b[2] = 0x06; // HW Address Length
b[3] = 0x00; // HOPS
// Transaction ID
var r = Buffer.alloc(4); r.randomFill();
b.writeUInt32BE(r.readUInt32BE(), 4);
b.writeUInt16BE(0x8000, 10);
// Magic Cookie
b[236] = 99;
b[237] = 130;
b[238] = 83;
b[239] = 99;
// DHCP Message Type
b[240] = 53;
b[241] = 1;
b[242] = messageType;
b[243] = 255;
switch(messageType)
{
case 0x08:
if (data.ciaddress == null) { throw ('ciadress missing'); }
if (data.chaddress == null) { throw ('chaddress missing'); }
// ciaddress
var a = data.ciaddress.split('.');
var ci = parseInt(a[0]);
ci = ci << 8;
ci = ci | parseInt(a[1]);
ci = ci << 8;
ci = ci | parseInt(a[2]);
ci = ci << 8;
ci = ci | parseInt(a[3]);
b.writeInt32BE(ci, 12);
// chaddress
var y = data.chaddress.split(':').join('');
y = Buffer.from(y, 'hex');
y.copy(b, 28);
break;
}
return (b);
}
function raw(localAddress, port, buffer, handler)
{
var ret = new promise(promise_default);
ret.socket = require('dgram').createSocket({ type: 'udp4' });
try
{
ret.socket.bind({ address: localAddress, port: (port != null && port != 0) ? port : null });
}
catch (e)
{
ret._rej('Unable to bind to ' + localAddress);
return (ret);
}
ret.socket.setBroadcast(true);
ret.socket.setMulticastInterface(localAddress);
ret.socket.setMulticastTTL(1);
ret.socket.descriptorMetadata = 'DHCP (' + localAddress + ')';
ret.socket.on('message', handler.bind(ret));
ret.socket.send(buffer, 67, '255.255.255.255');
return (ret);
}
function info(interfaceName, port)
{
var f = require('os').networkInterfaces();
if (interfaceName.split(':').length == 6)
{
var newname = null;
for(var n in f)
{
for (var nx in f[n])
{
if(f[n][nx].mac.toUpperCase() == interfaceName.toUpperCase())
{
newname = n;
break;
}
}
if(newname)
{
interfaceName = newname;
break;
}
}
}
if (f[interfaceName] != null)
{
var i;
for(i=0;i<f[interfaceName].length;++i)
{
if(f[interfaceName][i].family == 'IPv4' && f[interfaceName][i].mac != '00:00:00:00:00:00')
{
try
{
var b = createPacket(8, { ciaddress: f[interfaceName][i].address, chaddress: f[interfaceName][i].mac });
_hide(raw(f[interfaceName][i].address, port, b, function infoHandler(msg)
{
try
{
var res = parseDHCP(msg);
if (res.chaddr.toUpperCase() == this.hwaddr.toUpperCase() && res.options != null && res.options.lease != null)
{
clearTimeout(this.timeout);
setImmediate(function (s) { try { s.removeAllListeners('message'); } catch (x) { } }, this.socket); // Works around bug in older dgram.js
this._res(res);
}
}
catch(z)
{
}
}));
_hide().hwaddr = f[interfaceName][i].mac;
_hide().timeout = setTimeout(function (x)
{
x.socket.removeAllListeners('message');
x._rej('timeout');
}, 2000, _hide());
return (_hide(true));
}
catch(e)
{
var ret = new promise(promise_default);
ret._rej(e);
return (ret);
}
}
}
}
var ret = new promise(promise_default);
ret._rej('interface (' + interfaceName + ') not found');
return (ret);
}
module.exports =
{
client: { info: info, raw: raw },
MESSAGE_TYPES:
{
DISCOVER: 1,
OFFER: 2,
REQUEST: 3,
DECLINE: 4,
ACK: 5,
NACK: 6,
RELEASE: 7,
INFO: 8
}
};