EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
amt-lme.js
Go to the documentation of this file.
1/*
2Copyright 2018-2021 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17var MemoryStream = require('MemoryStream');
18var lme_id = 0; // Our next channel identifier
19var lme_port_offset = 0; // Debug: Set this to "-100" to bind to 16892 & 16893 and IN_ADDRANY. This is for LMS debugging.
20var lme_bindany = false; // If true, bind to all network interfaces, not just loopback.
21var xmlParser = null;
22try { xmlParser = require('amt-xml'); } catch (ex) { }
23
24// Documented in: https://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide/HTMLDocuments/MPSDocuments/Intel%20AMT%20Port%20Forwarding%20Protocol%20Reference%20Manual.pdf
25var APF_DISCONNECT = 1;
26var APF_SERVICE_REQUEST = 5;
27var APF_SERVICE_ACCEPT = 6;
28var APF_USERAUTH_REQUEST = 50;
29var APF_USERAUTH_FAILURE = 51;
30var APF_USERAUTH_SUCCESS = 52;
31var APF_GLOBAL_REQUEST = 80;
32var APF_REQUEST_SUCCESS = 81;
33var APF_REQUEST_FAILURE = 82;
34var APF_CHANNEL_OPEN = 90;
35var APF_CHANNEL_OPEN_CONFIRMATION = 91;
36var APF_CHANNEL_OPEN_FAILURE = 92;
37var APF_CHANNEL_WINDOW_ADJUST = 93;
38var APF_CHANNEL_DATA = 94;
39var APF_CHANNEL_CLOSE = 97;
40var APF_PROTOCOLVERSION = 192;
41
42
43function lme_object() {
44 this.ourId = ++lme_id;
45 this.amtId = -1;
46 this.LME_CHANNEL_STATUS = 'LME_CS_FREE';
47 this.txWindow = 0;
48 this.rxWindow = 0;
49 this.localPort = 0;
50 this.errorCount = 0;
51}
52
53function stream_bufferedWrite() {
54 var emitterUtils = require('events').inherits(this);
55 this.buffer = [];
56 this._readCheckImmediate = undefined;
57 this._ObjectID = "bufferedWriteStream";
58 // Writable Events
59 emitterUtils.createEvent('close');
60 emitterUtils.createEvent('drain');
61 emitterUtils.createEvent('error');
62 emitterUtils.createEvent('finish');
63 emitterUtils.createEvent('pipe');
64 emitterUtils.createEvent('unpipe');
65
66 // Readable Events
67 emitterUtils.createEvent('readable');
68 this.isEmpty = function () {
69 return (this.buffer.length == 0);
70 };
71 this.isWaiting = function () {
72 return (this._readCheckImmediate == undefined);
73 };
74 this.write = function (chunk) {
75 for (var args in arguments) { if (typeof (arguments[args]) == 'function') { this.once('drain', arguments[args]); break; } }
76 var tmp = Buffer.alloc(chunk.length);
77 chunk.copy(tmp);
78 this.buffer.push({ offset: 0, data: tmp });
79 this.emit('readable');
80 return (this.buffer.length == 0 ? true : false);
81 };
82 this.read = function () {
83 var size = arguments.length == 0 ? undefined : arguments[0];
84 var bytesRead = 0;
85 var list = [];
86 while ((size == undefined || bytesRead < size) && this.buffer.length > 0) {
87 var len = this.buffer[0].data.length - this.buffer[0].offset;
88 var offset = this.buffer[0].offset;
89
90 if (len > (size - bytesRead)) {
91 // Only reading a subset
92 list.push(this.buffer[0].data.slice(offset, offset + size - bytesRead));
93 this.buffer[0].offset += (size - bytesRead);
94 bytesRead += (size - bytesRead);
95 } else {
96 // Reading the entire thing
97 list.push(this.buffer[0].data.slice(offset));
98 bytesRead += len;
99 this.buffer.shift();
100 }
101 }
102 this._readCheckImmediate = setImmediate(function (buffered) {
103 buffered._readCheckImmediate = undefined;
104 if (buffered.buffer.length == 0) {
105 buffered.emit('drain'); // Drained
106 } else {
107 buffered.emit('readable'); // Not drained
108 }
109 }, this);
110 return (Buffer.concat(list));
111 };
112}
113
114
115function lme_heci(options) {
116 var emitterUtils = require('events').inherits(this);
117 emitterUtils.createEvent('error');
118 emitterUtils.createEvent('connect');
119 emitterUtils.createEvent('notify');
120 emitterUtils.createEvent('bind');
121
122 this.on('newListener', function (name, func)
123 {
124 if (name == 'connect' && this._LME._connected == true) { func.call(this); }
125 if (name == 'error' && this._LME._error !=null) { func.call(this, this._LME._error); }
126 });
127 if (options != null) {
128 if (options.debug == true) { lme_port_offset = -100; } // LMS debug mode
129 if (options.bindany == true) { lme_bindany = true; } // Bind to all ports
130 }
131
132 var heci = require('heci');
133 this.INITIAL_RXWINDOW_SIZE = 4096;
134
135 this._ObjectID = "lme";
136 this._LME = heci.create();
137 this._LME._connected = false;
138 this._LME._error = null;
139 this._LME.descriptorMetadata = "amt-lme";
140 this._LME._binded = {};
141 this._LME.LMS = this;
142 this._LME.on('error', function (e) { this._error = e; this.LMS.emit('error', e); });
143 this._LME.on('connect', function ()
144 {
145 this._connected = true;
146 this._emitConnected = false;
147 this.on('data', function (chunk) {
148 // this = HECI
149 var cmd = chunk.readUInt8(0);
150 //console.log('LME Command ' + cmd + ', ' + chunk.length + ' byte(s).');
151
152 switch (cmd) {
153 default:
154 console.log('Unhandled LME Command ' + cmd + ', ' + chunk.length + ' byte(s).');
155 break;
156 case APF_SERVICE_REQUEST:
157 var nameLen = chunk.readUInt32BE(1);
158 var name = chunk.slice(5, nameLen + 5);
159 //console.log("Service Request for: " + name);
160 if (name == 'pfwd@amt.intel.com' || name == 'auth@amt.intel.com') {
161 var outBuffer = Buffer.alloc(5 + nameLen);
162 outBuffer.writeUInt8(6, 0);
163 outBuffer.writeUInt32BE(nameLen, 1);
164 outBuffer.write(name.toString(), 5);
165 this.write(outBuffer);
166 //console.log('Answering APF_SERVICE_REQUEST');
167 } else {
168 //console.log('UNKNOWN APF_SERVICE_REQUEST');
169 }
170 break;
171 case APF_GLOBAL_REQUEST:
172 var nameLen = chunk.readUInt32BE(1);
173 var name = chunk.slice(5, nameLen + 5).toString();
174
175 switch (name) {
176 case 'tcpip-forward':
177 var len = chunk.readUInt32BE(nameLen + 6);
178 var port = chunk.readUInt32BE(nameLen + 10 + len);
179 //console.log("[" + chunk.length + "/" + len + "] APF_GLOBAL_REQUEST for: " + name + " on port " + port);
180 if (this[name] == undefined) { this[name] = {}; }
181 if (this[name][port] != null) { // Close the existing binding
182 for (var i in this.sockets) {
183 var channel = this.sockets[i];
184 if (channel.localPort == port) { this.sockets[i].end(); delete this.sockets[i]; } // Close this socket
185 }
186 }
187 if (this[name][port] == null)
188 {
189 try
190 {
191 // Bind a new server socket if not already present
192 this[name][port] = require('net').createServer();
193 this[name][port].descriptorMetadata = 'amt-lme (port: ' + port + ')';
194 this[name][port].HECI = this;
195 if (lme_port_offset == 0) {
196 if (lme_bindany) {
197 this[name][port].listen({ port: port }); // Bind all mode
198 } else {
199 this[name][port].listen({ port: port, host: '127.0.0.1' }); // Normal mode
200 }
201 } else {
202 this[name][port].listen({ port: (port + lme_port_offset) }); // Debug mode
203 }
204 this[name][port].on('connection', function (socket) {
205 //console.log('New [' + socket.remoteFamily + '] TCP Connection on: ' + socket.remoteAddress + ' :' + socket.localPort);
206 this.HECI.LMS.bindDuplexStream(socket, socket.remoteFamily, socket.localPort - lme_port_offset);
207 });
208 this._binded[port] = true;
209 if (!this._emitConnected)
210 {
211 this._emitConnected = true;
212 this.LMS.emit('error', 'APF/BIND error');
213 }
214 this.LMS.emit('bind', this._binded);
215 } catch (ex)
216 {
217 console.info1(ex, 'Port ' + port);
218 if(!this._emitConnected)
219 {
220 this._emitConnected = true;
221 this.LMS.emit('error', 'APF/BIND error');
222 }
223 }
224 }
225 var outBuffer = Buffer.alloc(5);
226 outBuffer.writeUInt8(81, 0);
227 outBuffer.writeUInt32BE(port, 1);
228 this.write(outBuffer);
229 break;
230 case 'cancel-tcpip-forward':
231 var outBuffer = Buffer.alloc(1);
232 outBuffer.writeUInt8(APF_REQUEST_SUCCESS, 0);
233 this.write(outBuffer);
234 break;
235 case 'udp-send-to@amt.intel.com':
236 var outBuffer = Buffer.alloc(1);
237 outBuffer.writeUInt8(APF_REQUEST_FAILURE, 0);
238 this.write(outBuffer);
239 break;
240 default:
241 //console.log("Unknown APF_GLOBAL_REQUEST for: " + name);
242 break;
243 }
244 break;
245 case APF_CHANNEL_OPEN_CONFIRMATION:
246 var rChannel = chunk.readUInt32BE(1);
247 var sChannel = chunk.readUInt32BE(5);
248 var wSize = chunk.readUInt32BE(9);
249 //console.log('rChannel/' + rChannel + ', sChannel/' + sChannel + ', wSize/' + wSize);
250 if (this.sockets[rChannel] != undefined) {
251 this.sockets[rChannel].lme.amtId = sChannel;
252 this.sockets[rChannel].lme.rxWindow = wSize;
253 this.sockets[rChannel].lme.txWindow = wSize;
254 this.sockets[rChannel].lme.LME_CHANNEL_STATUS = 'LME_CS_CONNECTED';
255 //console.log('LME_CS_CONNECTED');
256 this.sockets[rChannel].bufferedStream = new stream_bufferedWrite();
257 this.sockets[rChannel].bufferedStream.socket = this.sockets[rChannel];
258 this.sockets[rChannel].bufferedStream.on('readable', function () {
259 if (this.socket.lme.txWindow > 0) {
260 var buffer = this.read(this.socket.lme.txWindow);
261 var packet = Buffer.alloc(9 + buffer.length);
262 packet.writeUInt8(APF_CHANNEL_DATA, 0);
263 packet.writeUInt32BE(this.socket.lme.amtId, 1);
264 packet.writeUInt32BE(buffer.length, 5);
265 buffer.copy(packet, 9);
266 this.socket.lme.txWindow -= buffer.length;
267 this.socket.HECI.write(packet);
268 }
269 });
270 this.sockets[rChannel].bufferedStream.on('drain', function () {
271 this.socket.resume();
272 });
273 this.sockets[rChannel].on('data', function (chunk) {
274 if (!this.bufferedStream.write(chunk)) { this.pause(); }
275 });
276 this.sockets[rChannel].on('end', function () {
277 var outBuffer = Buffer.alloc(5);
278 outBuffer.writeUInt8(APF_CHANNEL_CLOSE, 0);
279 outBuffer.writeUInt32BE(this.lme.amtId, 1);
280 this.HECI.write(outBuffer);
281 });
282 this.sockets[rChannel].resume();
283 }
284
285 break;
286 case APF_PROTOCOLVERSION:
287 var major = chunk.readUInt32BE(1);
288 var minor = chunk.readUInt32BE(5);
289 var reason = chunk.readUInt32BE(9);
290 var outBuffer = Buffer.alloc(93);
291 outBuffer.writeUInt8(192, 0);
292 outBuffer.writeUInt32BE(1, 1);
293 outBuffer.writeUInt32BE(0, 5);
294 outBuffer.writeUInt32BE(reason, 9);
295 //console.log('Answering PROTOCOL_VERSION');
296 this.write(outBuffer);
297 break;
298 case APF_CHANNEL_WINDOW_ADJUST:
299 var rChannelId = chunk.readUInt32BE(1);
300 var bytesToAdd = chunk.readUInt32BE(5);
301 if (this.sockets[rChannelId] != undefined) {
302 this.sockets[rChannelId].lme.txWindow += bytesToAdd;
303 if (!this.sockets[rChannelId].bufferedStream.isEmpty() && this.sockets[rChannelId].bufferedStream.isWaiting()) {
304 this.sockets[rChannelId].bufferedStream.emit('readable');
305 }
306 } else {
307 console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_WINDOW_ADJUST');
308 }
309 break;
310 case APF_CHANNEL_DATA:
311 var rChannelId = chunk.readUInt32BE(1);
312 var dataLen = chunk.readUInt32BE(5);
313 var data = chunk.slice(9, 9 + dataLen);
314 if ((this.sockets != null) && (this.sockets[rChannelId] != undefined)) {
315 this.sockets[rChannelId].pendingBytes.push(data.length);
316 this.sockets[rChannelId].write(data, function () {
317 var written = this.pendingBytes.shift();
318 //console.log('adjust', this.lme.amtId, written);
319 var outBuffer = Buffer.alloc(9);
320 outBuffer.writeUInt8(APF_CHANNEL_WINDOW_ADJUST, 0);
321 outBuffer.writeUInt32BE(this.lme.amtId, 1);
322 outBuffer.writeUInt32BE(written, 5);
323 this.HECI.write(outBuffer);
324 });
325 } else if ((this.insockets != null) && (this.insockets[rChannelId] != undefined)) {
326 var channel = this.insockets[rChannelId];
327 if (channel.data == null) { channel.data = data.toString(); } else { channel.data += data.toString(); }
328 channel.rxWindow += dataLen;
329 //console.log('IN DATA', channel.rxWindow, channel.data.length, dataLen, channel.amtId, data.toString());
330 var httpData = parseHttp(channel.data);
331 if ((httpData != null) || (channel.data.length >= 8000)) {
332 // Parse the WSMAN
333 var notify = null;
334 if (xmlParser != null) { try { notify = xmlParser.ParseWsman(httpData); } catch (e) { } }
335
336 // Event the http data
337 if (notify != null) { this.LMS.emit('notify', notify, channel.options, _lmsNotifyToCode(notify)); }
338
339 // Send channel close
340 var buffer = Buffer.alloc(5);
341 buffer.writeUInt8(APF_CHANNEL_CLOSE, 0);
342 buffer.writeUInt32BE(amtId, 1);
343 this.write(buffer);
344 } else {
345 if (channel.rxWindow > 6000) {
346 // Send window adjust
347 var buffer = Buffer.alloc(9);
348 buffer.writeUInt8(APF_CHANNEL_WINDOW_ADJUST, 0);
349 buffer.writeUInt32BE(channel.amtId, 1);
350 buffer.writeUInt32BE(channel.rxWindow, 5);
351 this.write(buffer);
352 channel.rxWindow = 0;
353 }
354 }
355 } else {
356 console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_DATA');
357 }
358 break;
359 case APF_CHANNEL_OPEN_FAILURE:
360 var rChannelId = chunk.readUInt32BE(1);
361 var reasonCode = chunk.readUInt32BE(5);
362 if ((this.sockets != null) && (this.sockets[rChannelId] != undefined)) {
363 this.sockets[rChannelId].end();
364 delete this.sockets[rChannelId];
365 } else if ((this.insockets != null) && (this.insockets[rChannelId] != undefined)) {
366 delete this.insockets[rChannelId];
367 } else {
368 console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_OPEN_FAILURE');
369 }
370 break;
371 case APF_CHANNEL_CLOSE:
372 var rChannelId = chunk.readUInt32BE(1);
373 if ((this.sockets != null) && (this.sockets[rChannelId] != undefined)) {
374 this.sockets[rChannelId].end();
375 var amtId = this.sockets[rChannelId].lme.amtId;
376 var buffer = Buffer.alloc(5);
377 delete this.sockets[rChannelId];
378
379 buffer.writeUInt8(APF_CHANNEL_CLOSE, 0); // ????????????????????????????
380 buffer.writeUInt32BE(amtId, 1);
381 this.write(buffer);
382 } else if ((this.insockets != null) && (this.insockets[rChannelId] != undefined)) {
383 delete this.insockets[rChannelId];
384 // Should I send a close back????
385 } else {
386 console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_CLOSE');
387 }
388 break;
389 case APF_CHANNEL_OPEN:
390 var nameLen = chunk.readUInt32BE(1);
391 var name = chunk.slice(5, nameLen + 5).toString();
392 var channelSender = chunk.readUInt32BE(nameLen + 5);
393 var initialWindowSize = chunk.readUInt32BE(nameLen + 9);
394 var hostToConnectLen = chunk.readUInt32BE(nameLen + 17);
395 var hostToConnect = chunk.slice(nameLen + 21, nameLen + 21 + hostToConnectLen).toString();
396 var portToConnect = chunk.readUInt32BE(nameLen + 21 + hostToConnectLen);
397 var originatorIpLen = chunk.readUInt32BE(nameLen + 25 + hostToConnectLen);
398 var originatorIp = chunk.slice(nameLen + 29 + hostToConnectLen, nameLen + 29 + hostToConnectLen + originatorIpLen).toString();
399 var originatorPort = chunk.readUInt32BE(nameLen + 29 + hostToConnectLen + originatorIpLen);
400 //console.log('APF_CHANNEL_OPEN', name, channelSender, initialWindowSize, 'From: ' + originatorIp + ':' + originatorPort, 'To: ' + hostToConnect + ':' + portToConnect);
401
402 if (this.insockets == null) { this.insockets = {}; }
403 var ourId = ++lme_id;
404 var insocket = new lme_object();
405 insocket.ourId = ourId;
406 insocket.amtId = channelSender;
407 insocket.txWindow = initialWindowSize;
408 insocket.rxWindow = 0;
409 insocket.options = { target: hostToConnect, targetPort: portToConnect, source: originatorIp, sourcePort: originatorPort };
410 this.insockets[ourId] = insocket;
411
412 var buffer = Buffer.alloc(17);
413 buffer.writeUInt8(APF_CHANNEL_OPEN_CONFIRMATION, 0);
414 buffer.writeUInt32BE(channelSender, 1); // Intel AMT sender channel
415 buffer.writeUInt32BE(ourId, 5); // Our receiver channel id
416 buffer.writeUInt32BE(4000, 9); // Initial Window Size
417 buffer.writeUInt32BE(0xFFFFFFFF, 13); // Reserved
418 this.write(buffer);
419
420 /*
421 var buffer = Buffer.alloc(17);
422 buffer.writeUInt8(APF_CHANNEL_OPEN_FAILURE, 0);
423 buffer.writeUInt32BE(channelSender, 1); // Intel AMT sender channel
424 buffer.writeUInt32BE(2, 5); // Reason code
425 buffer.writeUInt32BE(0, 9); // Reserved
426 buffer.writeUInt32BE(0, 13); // Reserved
427 this.write(buffer);
428 console.log('Sent APF_CHANNEL_OPEN_FAILURE', channelSender);
429 */
430
431 break;
432 }
433 });
434 //
435 // Due to a change in behavior with AMT/11 (and possibly earlier), we are not going to emit 'connect' here, until
436 // we can verify that the first APF/Channel can be bound. Older AMT, like AMT/7 only allowed a single LME connection, so we
437 // used to emit connect here. However, newer AMT's will allow more than 1 LME connection, which will result in APF/Bind failure
438 //
439 //this.LMS.emit('connect');
440 this.resume();
441
442 });
443
444 this.bindDuplexStream = function (duplexStream, remoteFamily, localPort) {
445 var socket = duplexStream;
446 //console.log('New [' + remoteFamily + '] Virtual Connection/' + socket.localPort);
447 socket.pendingBytes = [];
448 socket.HECI = this._LME;
449 socket.LMS = this;
450 socket.lme = new lme_object();
451 socket.lme.Socket = socket;
452 socket.localPort = localPort;
453 var buffer = new MemoryStream();
454 buffer.writeUInt8(0x5A);
455 buffer.writeUInt32BE(15);
456 buffer.write('forwarded-tcpip');
457 buffer.writeUInt32BE(socket.lme.ourId);
458 buffer.writeUInt32BE(this.INITIAL_RXWINDOW_SIZE);
459 buffer.writeUInt32BE(0xFFFFFFFF);
460 for (var i = 0; i < 2; ++i) {
461 if (remoteFamily == 'IPv6') {
462 buffer.writeUInt32BE(3);
463 buffer.write('::1');
464 } else {
465 buffer.writeUInt32BE(9);
466 buffer.write('127.0.0.1');
467 }
468 buffer.writeUInt32BE(localPort);
469 }
470 this._LME.write(buffer.buffer);
471 if (this._LME.sockets == undefined) { this._LME.sockets = {}; }
472 this._LME.sockets[socket.lme.ourId] = socket;
473 socket.pause();
474 };
475
476 this._LME.connect(heci.GUIDS.LME, { noPipeline: 0 });
477}
478
479function parseHttp(httpData) {
480 var i = httpData.indexOf('\r\n\r\n');
481 if ((i == -1) || (httpData.length < (i + 2))) { return null; }
482 var headers = require('http-headers')(httpData.substring(0, i), true);
483 var contentLength = parseInt(headers['content-length']);
484 if (httpData.length >= contentLength + i + 4) { return httpData.substring(i + 4, i + 4 + contentLength); }
485 return null;
486}
487
488function _lmsNotifyToCode(notify) {
489 if ((notify == null) || (notify.Body == null) || (notify.Body.MessageID == null)) return null;
490 var msgid = notify.Body.MessageID;
491 try { msgid += '-' + notify.Body.MessageArguments[0]; } catch (e) { }
492 return msgid;
493}
494
495module.exports = lme_heci;