netconf-master.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. //
  2. // ZeroTier One - Global Peer to Peer Ethernet
  3. // Copyright (C) 2011-2015 ZeroTier Networks
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. //
  18. // --
  19. //
  20. // ZeroTier may be used and distributed under the terms of the GPLv3, which
  21. // are available at: http://www.gnu.org/licenses/gpl-3.0.html
  22. //
  23. // If you would like to embed ZeroTier into a commercial application or
  24. // redistribute it in a modified binary form, please contact ZeroTier Networks
  25. // LLC. Start here: http://www.zerotier.com/
  26. //
  27. var config = require('./config.js');
  28. // Fields in netconf response dictionary
  29. var ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES = "et";
  30. var ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID = "nwid";
  31. var ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP = "ts";
  32. var ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO = "id";
  33. var ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT = "ml";
  34. var ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES = "mr";
  35. var ZT_NETWORKCONFIG_DICT_KEY_PRIVATE = "p";
  36. var ZT_NETWORKCONFIG_DICT_KEY_NAME = "n";
  37. var ZT_NETWORKCONFIG_DICT_KEY_DESC = "d";
  38. var ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC = "v4s";
  39. var ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC = "v6s";
  40. var ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP = "com";
  41. var ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST = "eb";
  42. var ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING = "pb";
  43. var ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES = "ab";
  44. // Path to zerotier-idtool binary, invoked to enerate certificates of membership
  45. var ZEROTIER_IDTOOL = '/usr/local/bin/zerotier-idtool';
  46. // From Constants.hpp in node/
  47. var ZT_NETWORK_AUTOCONF_DELAY = 60000;
  48. var ZT_NETWORK_CERTIFICATE_TTL_WINDOW = (ZT_NETWORK_AUTOCONF_DELAY * 4);
  49. // Connect to redis, assuming database 0 and no auth (for now)
  50. var async = require('async');
  51. var redis = require('redis');
  52. var DB = redis.createClient();
  53. DB.on("error",function(err) { console.error('redis query error: '+err); });
  54. DB.select(config.redisDb,function() {});
  55. // Global variables -- these are initialized on startup or netconf-init message
  56. var netconfSigningIdentity = null; // identity of netconf master, with private key portion
  57. // spawn() function to launch sub-processes
  58. var spawn = require('child_process').spawn;
  59. // Returns true for fields that are "true" according to ZT redis schema
  60. function ztDbTrue(v) { return ((v === '1')||(v === 'true')||(v > 0)); }
  61. //
  62. // ZeroTier One Dictionary -- encoding-compatible with Dictionary in C++ code base
  63. //
  64. function Dictionary(fromStr)
  65. {
  66. var self = this;
  67. this.data = {};
  68. this._esc = function(data) {
  69. var es = '';
  70. for(var i=0;i<data.length;++i) {
  71. var c = data.charAt(i);
  72. switch(c) {
  73. case '\0': es += '\\0'; break;
  74. case '\r': es += '\\r'; break;
  75. case '\n': es += '\\n'; break;
  76. case '\\': es += '\\\\'; break;
  77. case '=': es += '\\='; break;
  78. default: es += c; break;
  79. }
  80. }
  81. return es;
  82. };
  83. this._unesc = function(s) {
  84. if (typeof s !== 'string')
  85. return '';
  86. var uns = '';
  87. var escapeState = false;
  88. for(var i=0;i<s.length;++i) {
  89. var c = s.charAt(i);
  90. if (escapeState) {
  91. escapeState = false;
  92. switch(c) {
  93. case '0': uns += '\0'; break;
  94. case 'r': uns += '\r'; break;
  95. case 'n': uns += '\n'; break;
  96. default: uns += c; break;
  97. }
  98. } else{
  99. if ((c !== '\r')&&(c !== '\n')&&(c !== '\0')) {
  100. if (c === '\\')
  101. escapeState = true;
  102. else uns += c;
  103. }
  104. }
  105. }
  106. return uns;
  107. };
  108. this.toString = function() {
  109. var str = '';
  110. for(var key in self.data) {
  111. str += self._esc(key);
  112. str += '=';
  113. var value = self.data[key];
  114. if (value)
  115. str += self._esc(value.toString());
  116. str += '\n';
  117. }
  118. return str;
  119. };
  120. this.fromString = function(str) {
  121. self.data = {};
  122. if (typeof str !== 'string')
  123. return self;
  124. var lines = str.split('\n');
  125. for(var l=0;l<lines.length;++l) {
  126. var escapeState = false;
  127. var eqAt = 0;
  128. for(;eqAt<lines[l].length;++eqAt) {
  129. var c = lines[l].charAt(eqAt);
  130. if (escapeState)
  131. escapeState = false;
  132. else if (c === '\\')
  133. escapeState = true;
  134. else if (c === '=')
  135. break;
  136. }
  137. var k = self._unesc(lines[l].substr(0,eqAt));
  138. ++eqAt;
  139. if ((k)&&(k.length > 0))
  140. self.data[k] = self._unesc((eqAt < lines[l].length) ? lines[l].substr(eqAt) : '');
  141. }
  142. return self;
  143. };
  144. if ((typeof fromStr === 'string')&&(fromStr.length > 0))
  145. self.fromString(fromStr);
  146. };
  147. //
  148. // Identity implementation using zerotier-idtool as subprocess to do actual crypto work
  149. //
  150. function Identity(idstr)
  151. {
  152. var self = this;
  153. this.str = '';
  154. this.fields = [];
  155. this.toString = function() {
  156. return self.str;
  157. };
  158. this.address = function() {
  159. return ((self.fields.length > 0) ? self.fields[0] : '0000000000');
  160. };
  161. this.fromString = function(str) {
  162. self.str = '';
  163. self.fields = [];
  164. if (typeof str !== 'string')
  165. return;
  166. for(var i=0;i<str.length;++i) {
  167. if ("0123456789abcdef:".indexOf(str.charAt(i)) < 0)
  168. return; // invalid character in identity
  169. }
  170. var fields = str.split(':');
  171. if ((fields.length < 3)||(fields[0].length !== 10)||(fields[1] !== '0'))
  172. return;
  173. self.str = str;
  174. self.fields = fields;
  175. };
  176. this.isValid = function() {
  177. return (! ((self.fields.length < 3)||(self.fields[0].length !== 10)||(self.fields[1] !== '0')) );
  178. };
  179. this.hasPrivate = function() {
  180. return ((self.isValid())&&(self.fields.length >= 4));
  181. };
  182. if (typeof idstr === 'string')
  183. self.fromString(idstr);
  184. };
  185. //
  186. // Invokes zerotier-idtool to generate certificates for private networks
  187. //
  188. function generateCertificateOfMembership(nwid,peerAddress,callback)
  189. {
  190. // The first fields of these COM tuples come from
  191. // CertificateOfMembership.hpp's enum of required
  192. // certificate default fields.
  193. var comTimestamp = '0,' + Date.now().toString(16) + ',' + ZT_NETWORK_CERTIFICATE_TTL_WINDOW.toString(16);
  194. var comNwid = '1,' + nwid + ',0';
  195. var comIssuedTo = '2,' + peerAddress + ',ffffffffffffffff';
  196. var cert = '';
  197. var certErr = '';
  198. var idtool = spawn(ZEROTIER_IDTOOL,[ 'mkcom',netconfSigningIdentity,comTimestamp,comNwid,comIssuedTo ]);
  199. idtool.stdout.on('data',function(data) {
  200. cert += data;
  201. });
  202. idtool.stderr.on('data',function(data) {
  203. certErr += data;
  204. });
  205. idtool.on('close',function(exitCode) {
  206. if (certErr.length > 0)
  207. console.error('zerotier-idtool stderr returned: '+certErr);
  208. return callback((cert.length > 0) ? cert : null,exitCode);
  209. });
  210. }
  211. //
  212. // Message handler for messages over ZeroTier One service bus
  213. //
  214. function doNetconfInit(message)
  215. {
  216. netconfSigningIdentity = new Identity(message.data['netconfId']);
  217. if (!netconfSigningIdentity.hasPrivate()) {
  218. netconfSigningIdentity = null;
  219. console.error('got invalid netconf signing identity in netconf-init');
  220. } // else console.error('got netconf-init, running! id: '+netconfSigningIdentity.address());
  221. }
  222. function doNetconfRequest(message)
  223. {
  224. if ((netconfSigningIdentity === null)||(!netconfSigningIdentity.hasPrivate())) {
  225. console.error('got netconf-request before netconf-init, ignored');
  226. return;
  227. }
  228. var peerId = new Identity(message.data['peerId']);
  229. var nwid = message.data['nwid'];
  230. var requestId = message.data['requestId'];
  231. if ((!peerId)||(!peerId.isValid())||(!nwid)||(nwid.length !== 16)||(!requestId)) {
  232. console.error('missing one or more required fields in netconf-request');
  233. return;
  234. }
  235. var networkKey = 'zt1:network:'+nwid+':~';
  236. var memberKey = 'zt1:network:'+nwid+':member:'+peerId.address()+':~';
  237. var ipAssignmentsKey = 'zt1:network:'+nwid+':ipAssignments';
  238. var network = null;
  239. var member = null;
  240. var authorized = false;
  241. var v4NeedAssign = false;
  242. var v6NeedAssign = false;
  243. var v4Assignments = [];
  244. var v6Assignments = [];
  245. var ipAssignments = []; // both v4 and v6
  246. var activeBridges = '';
  247. async.series([function(next) {
  248. // network lookup
  249. DB.hgetall(networkKey,function(err,obj) {
  250. if ((!err)&&(obj)&&(obj.id === nwid))
  251. network = obj;
  252. return next(null);
  253. });
  254. },function(next) {
  255. // member lookup
  256. if (!network)
  257. return next(null);
  258. DB.hgetall(memberKey,function(err,obj) {
  259. if (err)
  260. return next(err);
  261. if (obj) {
  262. // Update existing member record with new last seen time, etc.
  263. member = obj;
  264. authorized = ((!ztDbTrue(network['private'])) || ztDbTrue(member['authorized']));
  265. var updatedFields = {
  266. 'lastSeen': Date.now(),
  267. 'authorized': authorized ? '1' : '0' // reset authorized to unhide in UI, since UI uses -1 to hide
  268. };
  269. if (!('identity' in member))
  270. updatedFields['identity'] = peerId.toString();
  271. if (!('firstSeen' in member))
  272. updatedFields['firstSeen'] = Date.now();
  273. if (message.data['from'])
  274. updatedFields['lastAt'] = message.data['from'];
  275. if (message.data['clientVersion'])
  276. updatedFields['clientVersion'] = message.data['clientVersion'];
  277. if (message.data['clientOs'])
  278. updatedFields['clientOs'] = message.data['clientOs'];
  279. DB.hmset(memberKey,updatedFields,next);
  280. } else {
  281. // Add member record to network for newly seen peer
  282. authorized = ztDbTrue(network['private']) ? false : true; // public networks authorize everyone by default
  283. var now = Date.now().toString();
  284. member = {
  285. 'id': peerId.address(),
  286. 'nwid': nwid,
  287. 'authorized': authorized ? '1' : '0',
  288. 'identity': peerId.toString(),
  289. 'firstSeen': now,
  290. 'lastSeen': now
  291. };
  292. if (message.data['from'])
  293. member['lastAt'] = message.data['from'];
  294. if (message.data['clientVersion'])
  295. member['clientVersion'] = message.data['clientVersion'];
  296. if (message.data['clientOs'])
  297. member['clientOs'] = message.data['clientOs'];
  298. DB.hmset(memberKey,member,next);
  299. }
  300. });
  301. },function(next) {
  302. // Figure out which IP address auto-assignments we need to look up or make
  303. if ((!network)||(!authorized))
  304. return next(null);
  305. v4NeedAssign = (network['v4AssignMode'] === 'zt');
  306. v6NeedAssign = (network['v6AssignMode'] === 'zt');
  307. var ipacsv = member['ipAssignments'];
  308. if (ipacsv) {
  309. var ipa = ipacsv.split(',');
  310. for(var i=0;i<ipa.length;++i) {
  311. if (ipa[i]) {
  312. ipAssignments.push(ipa[i]);
  313. if ((ipa[i].indexOf('.') > 0)&&(v4NeedAssign))
  314. v4Assignments.push(ipa[i]);
  315. else if ((ipa[i].indexOf(':') > 0)&&(v6NeedAssign))
  316. v6Assignments.push(ipa[i]);
  317. }
  318. }
  319. }
  320. return next(null);
  321. },function(next) {
  322. // assign IPv4 if needed
  323. if ((!network)||(!authorized)||(!v4NeedAssign)||(v4Assignments.length > 0))
  324. return next(null);
  325. var peerAddress = peerId.address();
  326. var ipnetwork = 0;
  327. var netmask = 0;
  328. var netmaskBits = 0;
  329. var v4pool = network['v4AssignPool']; // technically csv but only one netblock currently supported
  330. if (v4pool) {
  331. var v4poolSplit = v4pool.split('/');
  332. if (v4poolSplit.length === 2) {
  333. var networkSplit = v4poolSplit[0].split('.');
  334. if (networkSplit.length === 4) {
  335. ipnetwork |= (parseInt(networkSplit[0],10) << 24) & 0xff000000;
  336. ipnetwork |= (parseInt(networkSplit[1],10) << 16) & 0x00ff0000;
  337. ipnetwork |= (parseInt(networkSplit[2],10) << 8) & 0x0000ff00;
  338. ipnetwork |= parseInt(networkSplit[3],10) & 0x000000ff;
  339. netmaskBits = parseInt(v4poolSplit[1],10);
  340. if (netmaskBits > 32)
  341. netmaskBits = 32; // sanity check
  342. for(var i=0;i<netmaskBits;++i)
  343. netmask |= (0x80000000 >> i);
  344. netmask &= 0xffffffff;
  345. }
  346. }
  347. }
  348. if ((ipnetwork === 0)||(netmask === 0xffffffff))
  349. return next(null);
  350. var invmask = netmask ^ 0xffffffff;
  351. var abcd = 0;
  352. var ipAssignmentAttempts = 0;
  353. async.whilst(
  354. function() { return ((v4Assignments.length === 0)&&(ipAssignmentAttempts < 1000)); },
  355. function(next2) {
  356. ++ipAssignmentAttempts;
  357. // Generate or increment IP address source bits
  358. if (abcd === 0) {
  359. var a = parseInt(peerAddress.substr(2,2),16) & 0xff;
  360. var b = parseInt(peerAddress.substr(4,2),16) & 0xff;
  361. var c = parseInt(peerAddress.substr(6,2),16) & 0xff;
  362. var d = parseInt(peerAddress.substr(8,2),16) & 0xff;
  363. abcd = (a << 24) | (b << 16) | (c << 8) | d;
  364. } else ++abcd;
  365. if ((abcd & 0xff) === 0)
  366. abcd |= 1;
  367. abcd &= 0xffffffff;
  368. // Derive an IP to test and generate assignment ip/bits string
  369. var ip = (abcd & invmask) | (ipnetwork & netmask);
  370. var assignment = ((ip >> 24) & 0xff).toString(10) + '.' + ((ip >> 16) & 0xff).toString(10) + '.' + ((ip >> 8) & 0xff).toString(10) + '.' + (ip & 0xff).toString(10) + '/' + netmaskBits.toString(10);
  371. // Check :ipAssignments to see if this IP is already taken
  372. DB.hget(ipAssignmentsKey,assignment,function(err,value) {
  373. if (err)
  374. return next2(err);
  375. // IP is already taken, try again via async.whilst()
  376. if ((value)&&(value !== peerAddress))
  377. return next2(null); // if someone's already got this IP, keep looking
  378. v4Assignments.push(assignment);
  379. ipAssignments.push(assignment);
  380. // Save assignment to :ipAssignments hash
  381. DB.hset(ipAssignmentsKey,assignment,peerAddress,function(err) {
  382. if (err)
  383. return next2(err);
  384. // Save updated CSV list of assignments to member record
  385. var ipacsv = ipAssignments.join(',');
  386. member['ipAssignments'] = ipacsv;
  387. DB.hset(memberKey,'ipAssignments',ipacsv,next2);
  388. });
  389. });
  390. },
  391. next
  392. );
  393. },function(next) {
  394. // assign IPv6 if needed -- TODO
  395. if ((!network)||(!authorized)||(!v6NeedAssign)||(v6Assignments.length > 0))
  396. return next(null);
  397. return next(null);
  398. },function(next) {
  399. // Get active bridges
  400. if ((!network)||(!authorized))
  401. return next(null);
  402. DB.keys('zt1:network:'+nwid+':member:*:~',function(err,keys) {
  403. if (keys) {
  404. async.eachSeries(keys,function(key,nextKey) {
  405. DB.hgetall(key,function(err,abr) {
  406. if ( (abr) &&
  407. (abr.id) &&
  408. (abr.id.length === 10) &&
  409. ( (!ztDbTrue(network['private'])) || ztDbTrue(abr['authorized']) ) &&
  410. (ztDbTrue(abr['activeBridge'])) ) {
  411. if (activeBridges.length)
  412. activeBridges += ',';
  413. activeBridges += abr.id;
  414. }
  415. return nextKey(null);
  416. });
  417. },next);
  418. } else return next(null);
  419. });
  420. }],function(err) {
  421. if (err) {
  422. console.error('error answering netconf-request for '+peerId.address()+': '+err);
  423. return;
  424. }
  425. var response = new Dictionary();
  426. response.data['peer'] = peerId.address();
  427. response.data['nwid'] = nwid;
  428. response.data['type'] = 'netconf-response';
  429. response.data['requestId'] = requestId;
  430. if ((network)&&(authorized)) {
  431. var certificateOfMembership = null;
  432. var privateNetwork = ztDbTrue(network['private']);
  433. async.series([function(next) {
  434. // Generate certificate of membership if necessary
  435. if (privateNetwork) {
  436. generateCertificateOfMembership(nwid,peerId.address(),function(cert,exitCode) {
  437. if (cert) {
  438. certificateOfMembership = cert;
  439. return next(null);
  440. } else return next(new Error('zerotier-idtool returned '+exitCode));
  441. });
  442. } else return next(null);
  443. }],function(err) {
  444. // Send response to parent process
  445. if (err) {
  446. console.error('unable to generate certificate for peer '+peerId.address()+' on network '+nwid+': '+err);
  447. response.data['error'] = 'ACCESS_DENIED'; // unable to generate certificate
  448. } else {
  449. var netconf = new Dictionary();
  450. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES] = network['etherTypes'];
  451. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID] = nwid;
  452. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP] = Date.now().toString(16);
  453. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO] = peerId.address();
  454. if (network['multicastLimit'])
  455. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT] = network['multicastLimit'];
  456. if (network['multicastRates']) {
  457. var ratesD = new Dictionary();
  458. var ratesJ = JSON.parse(network['multicastRates']);
  459. for(var k in ratesJ) {
  460. if ((k)&&(ratesJ[k]))
  461. ratesD.data[k] = ratesJ[k];
  462. }
  463. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES] = ratesD.toString();
  464. }
  465. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_PRIVATE] = privateNetwork ? '1' : '0';
  466. if (network['name'])
  467. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_NAME] = network['name'];
  468. if (network['desc'])
  469. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_DESC] = network['desc'];
  470. if ((v4NeedAssign)&&(v4Assignments.length > 0))
  471. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = v4Assignments.join(',');
  472. if ((v6NeedAssign)&&(v6Assignments.length > 0))
  473. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC] = v6Assignments.join(',');
  474. if (certificateOfMembership !== null)
  475. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = certificateOfMembership;
  476. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST] = ztDbTrue(network['enableBroadcast']) ? '1' : '0';
  477. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING] = ztDbTrue(network['allowPassiveBridging']) ? '1' : '0';
  478. if ((activeBridges)&&(activeBridges.length > 0))
  479. netconf.data[ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES] = activeBridges; // comma-delimited list
  480. response.data['netconf'] = netconf.toString();
  481. }
  482. process.stdout.write(response.toString()+'\n');
  483. });
  484. } else {
  485. // Peer not authorized to join network or network not found (right now we always send ACCESS_DENIED)
  486. response.data['error'] = 'ACCESS_DENIED';
  487. process.stdout.write(response.toString()+'\n');
  488. }
  489. });
  490. }
  491. function handleMessage(dictStr)
  492. {
  493. var message = new Dictionary(dictStr);
  494. if (!('type' in message.data)) {
  495. console.error('ignored message without request type field');
  496. return;
  497. } else if (message.data['type'] === 'netconf-init') {
  498. doNetconfInit(message);
  499. } else if (message.data['type'] === 'netconf-request') {
  500. doNetconfRequest(message);
  501. } else {
  502. console.error('ignored unrecognized message type: '+message.data['type']);
  503. }
  504. };
  505. //
  506. // Read stream of double-CR-terminated dictionaries from stdin until close/EOF
  507. //
  508. var stdinReadBuffer = '';
  509. process.stdin.on('readable',function() {
  510. var chunk = process.stdin.read();
  511. if (chunk)
  512. stdinReadBuffer += chunk;
  513. for(;;) {
  514. var boundary = stdinReadBuffer.indexOf('\n\n');
  515. if (boundary >= 0) {
  516. handleMessage(stdinReadBuffer.substr(0,boundary + 1));
  517. stdinReadBuffer = stdinReadBuffer.substr(boundary + 2);
  518. } else break;
  519. }
  520. });
  521. process.stdin.on('end',function() {
  522. process.exit(0);
  523. });
  524. process.stdin.on('close',function() {
  525. process.exit(0);
  526. });
  527. process.stdin.on('error',function() {
  528. process.exit(0);
  529. });
  530. // Tell ZeroTier One that the service is running, solicit netconf-init
  531. process.stdout.write('type=ready\n\n');