zerotier-root-watcher.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. 'use strict';
  2. const pg = require('pg');
  3. const zlib = require('zlib');
  4. const http = require('http');
  5. const fs = require('fs');
  6. const async = require('async');
  7. const config = JSON.parse(fs.readFileSync('./config.json'));
  8. const roots = config.roots||{};
  9. const db = new pg.Pool(config.db);
  10. process.on('uncaughtException',function(err) {
  11. console.error('ERROR: uncaught exception: '+err);
  12. if (err.stack)
  13. console.error(err.stack);
  14. });
  15. function httpRequest(host,port,authToken,method,path,args,callback)
  16. {
  17. var responseBody = [];
  18. var postData = (args) ? JSON.stringify(args) : null;
  19. var req = http.request({
  20. host: host,
  21. port: port,
  22. path: path,
  23. method: method,
  24. headers: {
  25. 'X-ZT1-Auth': (authToken||''),
  26. 'Content-Length': (postData) ? postData.length : 0
  27. }
  28. },function(res) {
  29. res.on('data',function(chunk) {
  30. if ((chunk)&&(chunk.length > 0))
  31. responseBody.push(chunk);
  32. });
  33. res.on('timeout',function() {
  34. try {
  35. if (typeof callback === 'function') {
  36. var cb = callback;
  37. callback = null;
  38. cb(new Error('connection timed out'),null);
  39. }
  40. req.abort();
  41. } catch (e) {}
  42. });
  43. res.on('error',function(e) {
  44. try {
  45. if (typeof callback === 'function') {
  46. var cb = callback;
  47. callback = null;
  48. cb(new Error('connection timed out'),null);
  49. }
  50. req.abort();
  51. } catch (e) {}
  52. });
  53. res.on('end',function() {
  54. if (typeof callback === 'function') {
  55. var cb = callback;
  56. callback = null;
  57. if (responseBody.length === 0) {
  58. return cb(null,{});
  59. } else {
  60. responseBody = Buffer.concat(responseBody);
  61. if (responseBody.length < 2) {
  62. return cb(null,{});
  63. }
  64. if ((responseBody.readUInt8(0,true) === 0x1f)&&(responseBody.readUInt8(1,true) === 0x8b)) {
  65. try {
  66. responseBody = zlib.gunzipSync(responseBody);
  67. } catch (e) {
  68. return cb(e,null);
  69. }
  70. }
  71. try {
  72. return cb(null,JSON.parse(responseBody));
  73. } catch (e) {
  74. return cb(e,null);
  75. }
  76. }
  77. }
  78. });
  79. }).on('error',function(e) {
  80. try {
  81. if (typeof callback === 'function') {
  82. var cb = callback;
  83. callback = null;
  84. cb(e,null);
  85. }
  86. req.abort();
  87. } catch (e) {}
  88. }).on('timeout',function() {
  89. try {
  90. if (typeof callback === 'function') {
  91. var cb = callback;
  92. callback = null;
  93. cb(new Error('connection timed out'),null);
  94. }
  95. req.abort();
  96. } catch (e) {}
  97. });
  98. req.setTimeout(30000);
  99. req.setNoDelay(true);
  100. if (postData !== null)
  101. req.end(postData);
  102. else req.end();
  103. };
  104. var peerStatus = {};
  105. function saveToDb()
  106. {
  107. db.connect(function(err,client,clientDone) {
  108. if (err) {
  109. console.log('WARNING: database error writing peers: '+err.toString());
  110. clientDone();
  111. return setTimeout(saveToDb,config.dbSaveInterval||60000);
  112. }
  113. client.query('BEGIN',function(err) {
  114. if (err) {
  115. console.log('WARNING: database error writing peers: '+err.toString());
  116. clientDone();
  117. return setTimeout(saveToDb,config.dbSaveInterval||60000);
  118. }
  119. let timeout = Date.now() - (config.peerTimeout||600000);
  120. let wtotal = 0;
  121. async.eachSeries(Object.keys(peerStatus),function(address,nextAddress) {
  122. let s = peerStatus[address];
  123. if (s[1] <= timeout) {
  124. delete peerStatus[address];
  125. return process.nextTick(nextAddress);
  126. } else {
  127. ++wtotal;
  128. client.query('INSERT INTO "Peer" ("ztAddress","timestamp","versionMajor","versionMinor","versionRev","rootId","phyPort","phyLinkQuality","phyLastReceive","phyAddress") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)',s,nextAddress);
  129. }
  130. },function(err) {
  131. if (err)
  132. console.log('WARNING database error writing peers: '+err.toString());
  133. console.log(Date.now().toString()+' '+wtotal);
  134. client.query('COMMIT',function(err,result) {
  135. clientDone();
  136. return setTimeout(saveToDb,config.dbSaveInterval||60000);
  137. });
  138. });
  139. });
  140. });
  141. };
  142. function doRootUpdate(name,id,ip,port,peersPath,authToken,interval)
  143. {
  144. httpRequest(ip,port,authToken,"GET",peersPath,null,function(err,res) {
  145. if (err) {
  146. console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): '+err.toString());
  147. return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000);
  148. }
  149. if (!Array.isArray(res)) {
  150. console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): response is not an array of peers');
  151. return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000);
  152. }
  153. //console.log(name+': '+res.length+' peer entries.');
  154. let now = Date.now();
  155. let count = 0;
  156. for(let pi=0;pi<res.length;++pi) {
  157. let peer = res[pi];
  158. let address = peer.address;
  159. let ztAddress = parseInt(address,16)||0;
  160. if (!ztAddress)
  161. continue;
  162. let paths = peer.paths;
  163. if ((Array.isArray(paths))&&(paths.length > 0)) {
  164. let bestPath = null;
  165. for(let i=0;i<paths.length;++i) {
  166. if (paths[i].active) {
  167. let lr = paths[i].lastReceive;
  168. if ((lr > 0)&&((!bestPath)||(bestPath.lastReceive < lr)))
  169. bestPath = paths[i];
  170. }
  171. }
  172. if (bestPath) {
  173. let a = bestPath.address;
  174. if (typeof a === 'string') {
  175. let a2 = a.split('/');
  176. if (a2.length === 2) {
  177. let vmaj = peer.versionMajor;
  178. if ((typeof vmaj === 'undefined')||(vmaj === null)) vmaj = -1;
  179. let vmin = peer.versionMinor;
  180. if ((typeof vmin === 'undefined')||(vmin === null)) vmin = -1;
  181. let vrev = peer.versionRev;
  182. if ((typeof vrev === 'undefined')||(vrev === null)) vrev = -1;
  183. let lr = parseInt(bestPath.lastReceive)||0;
  184. let s = peerStatus[address];
  185. if ((!s)||(s[8] < lr)) {
  186. peerStatus[address] = [
  187. ztAddress,
  188. now,
  189. vmaj,
  190. vmin,
  191. vrev,
  192. id,
  193. parseInt(a2[1])||0,
  194. parseFloat(bestPath.linkQuality)||1.0,
  195. lr,
  196. a2[0]
  197. ];
  198. }
  199. ++count;
  200. }
  201. }
  202. }
  203. }
  204. }
  205. console.log(name+': '+count+' peers with active direct paths.');
  206. return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },interval);
  207. });
  208. };
  209. for(var r in roots) {
  210. var rr = roots[r];
  211. if (rr.peers)
  212. doRootUpdate(r,rr.id,rr.ip,rr.port,rr.peers,rr.authToken||null,config.interval||60000);
  213. }
  214. return setTimeout(saveToDb,config.dbSaveInterval||60000);