2019-05-03 22:11:08 +00:00
// no npm!
2019-05-21 08:58:43 +00:00
const os = require ( 'os' )
const fs = require ( 'fs' )
const dns = require ( 'dns' )
const net = require ( 'net' )
const ini = require ( './ini' )
const path = require ( 'path' )
const http = require ( 'http' )
const https = require ( 'https' )
const {
spawn ,
exec
} = require ( 'child_process' )
2019-05-03 22:11:08 +00:00
// FIXME: disable rpc if desired
const VERSION = 0.6
console . log ( 'lokinet launcher version' , VERSION , 'registered' )
function log ( ) {
var args = [ ]
2019-05-21 08:58:43 +00:00
for ( var i in arguments ) {
2019-05-03 22:11:08 +00:00
var arg = arguments [ i ]
//console.log('arg type', arg, 'is', typeof(arg))
2019-05-21 08:58:43 +00:00
if ( typeof ( arg ) == 'object' ) {
2019-05-03 22:11:08 +00:00
arg = JSON . stringify ( arg )
}
args . push ( arg )
}
console . log ( 'NETWORK:' , args . join ( ' ' ) )
}
function getBoundIPv4s ( ) {
var nics = os . networkInterfaces ( )
var ipv4s = [ ]
2019-05-21 08:58:43 +00:00
for ( var adapter in nics ) {
2019-05-03 22:11:08 +00:00
var ips = nics [ adapter ]
2019-05-21 08:58:43 +00:00
for ( var ipIdx in ips ) {
2019-05-03 22:11:08 +00:00
var ipMap = ips [ ipIdx ]
if ( ipMap . address . match ( /\./ ) ) {
ipv4s . push ( ipMap . address )
}
}
}
return ipv4s
}
var auto _config _test _port , auto _config _test _host , auto _config _test _ips
// this doesn't need to connect completely to get our ip
// it won't get our IP if DNS doesn't work
function getNetworkIP ( callback ) {
// randomly select an ip
//log('getNetworkIP', auto_config_test_ips)
var ip = auto _config _test _ips [ Math . floor ( Math . random ( ) * auto _config _test _ips . length ) ]
//log('getNetworkIP from', ip, auto_config_test_port)
var socket = net . createConnection ( auto _config _test _port , ip )
socket . setTimeout ( 5000 )
2019-05-21 08:58:43 +00:00
socket . on ( 'connect' , function ( ) {
2019-05-03 22:11:08 +00:00
callback ( undefined , socket . address ( ) . address )
socket . end ( )
} )
var abort = false
2019-05-21 08:58:43 +00:00
socket . on ( 'timeout' , function ( ) {
2019-05-03 22:11:08 +00:00
if ( socket . address ( ) . address ) {
abort = true
var resultIp = socket . address ( ) . address
log ( 'getNetworkIP timeout but still got outgoing IP:' , resultIp )
socket . destroy ( )
callback ( undefined , resultIp )
} else {
// don't have what we need, just wait it out, maybe we'll get lucky
//log('getNetworkIP timeout')
//callback('timeout', 'error')
}
} )
2019-05-21 08:58:43 +00:00
socket . on ( 'error' , function ( e ) {
2019-05-03 22:11:08 +00:00
console . error ( 'NETWORK: getNetworkIP error' , e )
// FIXME: maybe a retry here
log ( 'getNetworkIP failure test' , socket . address ( ) . address )
log ( 'getNetworkIP failure, retry?' )
if ( abort ) {
log ( 'getNetworkIP already timed out' )
return
}
callback ( e , 'error' )
} )
}
function getIfNameFromIP ( ip ) {
var nics = os . networkInterfaces ( )
2019-05-21 08:58:43 +00:00
for ( var adapter in nics ) {
2019-05-03 22:11:08 +00:00
var ips = nics [ adapter ]
2019-05-21 08:58:43 +00:00
for ( var ipIdx in ips ) {
2019-05-03 22:11:08 +00:00
var ipMap = ips [ ipIdx ]
if ( ipMap . address == ip ) {
return adapter
}
}
}
return ''
}
const urlparser = require ( 'url' )
function httpGet ( url , cb ) {
const urlDetails = urlparser . parse ( url )
//console.log('httpGet url', urlDetails)
//console.log('httpGet', url)
//console.trace('who started dis', url)
var protoClient = http
if ( urlDetails . protocol == 'https:' ) {
protoClient = https
}
// well somehow this can get hung on macos
var abort = false
2019-05-21 08:58:43 +00:00
var watchdog = setInterval ( function ( ) {
2019-05-03 22:11:08 +00:00
if ( shuttingDown ) {
//if (cb) cb()
// [', url, ']
log ( 'hung httpGet but have shutdown request, calling back early and setting abort flag' )
clearInterval ( watchdog )
abort = true
cb ( )
return
}
} , 5000 )
protoClient . get ( {
hostname : urlDetails . hostname ,
protocol : urlDetails . protocol ,
port : urlDetails . port ,
path : urlDetails . path ,
timeout : 5000 ,
} , ( resp ) => {
//log('httpGet setting up handlers')
clearInterval ( watchdog )
resp . setEncoding ( 'binary' )
let data = ''
// A chunk of data has been recieved.
resp . on ( 'data' , ( chunk ) => {
data += chunk
} )
// The whole response has been received. Print out the result.
resp . on ( 'end' , ( ) => {
log ( 'result code' , resp . statusCode )
if ( abort ) {
// we already called back
return
}
if ( resp . statusCode == 404 ) {
console . error ( 'NETWORK:' , url , 'is not found' )
cb ( )
return
}
cb ( data )
} )
} ) . on ( "error" , ( err ) => {
2019-05-21 08:58:43 +00:00
console . error ( "NETWORK: httpGet Error: " , err . message , 'port' , urlDetails . port )
2019-05-03 22:11:08 +00:00
cb ( )
} )
}
function dynDNSHandler ( data , cb ) {
}
function getPublicIPv6 ( cb ) {
2019-05-21 08:58:43 +00:00
//v6.ident.me
2019-05-03 22:11:08 +00:00
}
var getPublicIPv4 _retries = 0
2019-05-21 08:58:43 +00:00
2019-05-03 22:11:08 +00:00
function getPublicIPv4 ( cb ) {
// trust more than one source
// randomly find 2 matching sources
// dns is faster than http
// dig +short myip.opendns.com @resolver1.opendns.com
// httpGet doesn't support https yet...
2019-05-21 08:58:43 +00:00
var publicIpServices = [ {
url : 'https://api.ipify.org'
} ,
{
url : 'https://ipinfo.io/ip'
} ,
{
url : 'https://ipecho.net/plain'
} ,
2019-05-03 22:11:08 +00:00
//{ url: 'http://api.ipify.org' },
//{ url: 'http://ipinfo.io/ip' },
//{ url: 'http://ipecho.net/plain' },
2019-05-21 08:58:43 +00:00
{
url : 'http://ifconfig.me'
} ,
{
url : 'http://ipv4.icanhazip.com'
} ,
{
url : 'http://v4.ident.me'
} ,
{
url : 'http://checkip.amazonaws.com'
} ,
2019-05-03 22:11:08 +00:00
//{ url: 'https://checkip.dyndns.org', handler: dynDNSHandler },
]
var service = [ ]
service [ 0 ] = Math . floor ( Math . random ( ) * publicIpServices . length )
service [ 1 ] = Math . floor ( Math . random ( ) * publicIpServices . length )
2019-05-21 08:58:43 +00:00
var done = [ false , false ]
2019-05-03 22:11:08 +00:00
function markDone ( idx , value ) {
if ( value === undefined ) value = ''
done [ idx ] = value . trim ( )
let ready = true
//log('done', done)
2019-05-21 08:58:43 +00:00
for ( var i in done ) {
2019-05-03 22:11:08 +00:00
if ( done [ i ] === false ) {
ready = false
log ( 'getPublicIPv4' , i , 'is not ready' )
break
}
}
if ( ! ready ) return
log ( 'getPublicIPv4 look ups are done' , done )
if ( done [ 0 ] != done [ 1 ] ) {
// try 2 random services again
getPublicIPv4 _retries ++
if ( getPublicIPv4 _retries > 10 ) {
console . error ( 'NAT detection: Can\'t determine public IP address' )
process . exit ( )
}
getPublicIPv4 ( cb )
} else {
// return
//log("found public IP", done[0])
cb ( done [ 0 ] )
}
}
function doCall ( number ) {
2019-05-21 08:58:43 +00:00
httpGet ( publicIpServices [ service [ number ] ] . url , function ( ip ) {
2019-05-03 22:11:08 +00:00
if ( ip === false ) {
service [ number ] = ( Math . random ( ) * publicIpServices . length )
// retry
console . warn ( publicIpServices [ service [ number ] ] . url , 'failed, retrying' )
doCall ( number )
return
}
console . log ( number , publicIpServices [ service [ number ] ] . url , ip )
markDone ( number , ip )
} )
}
doCall ( 0 )
doCall ( 1 )
}
// used for generating temp filenames
function randomString ( len ) {
var text = ""
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for ( var i = 0 ; i < len ; i ++ )
text += possible . charAt ( Math . floor ( Math . random ( ) * possible . length ) )
return text
}
function isDnsPort ( ip , port , cb ) {
const resolver = new dns . Resolver ( )
resolver . setServers ( [ ip + ':' + port ] )
2019-05-21 08:58:43 +00:00
resolver . resolve ( auto _config _test _host , function ( err , records ) {
2019-05-03 22:11:08 +00:00
if ( err ) console . error ( 'resolve error:' , err )
log ( auto _config _test _host , records )
cb ( records !== undefined )
} )
}
function testDNSForLokinet ( server , cb ) {
const resolver = new dns . Resolver ( )
resolver . setServers ( [ server ] )
// incase server is 127.0.0.1:undefined
try {
2019-05-21 08:58:43 +00:00
resolver . resolve ( 'localhost.loki' , function ( err , records ) {
2019-05-03 22:11:08 +00:00
if ( err ) console . error ( 'NETWORK: localhost.loki resolve err:' , err )
//log(server, 'localhost.loki test results', records)
cb ( records )
} )
2019-05-21 08:58:43 +00:00
} catch ( e ) {
2019-05-03 22:11:08 +00:00
console . error ( 'NETWORK: testDNSForLokinet error, incorrect server?' , server )
cb ( )
}
}
function lookup ( host , cb ) {
var resolver = new dns . Resolver ( )
//console.log('lokinet lookup servers', runningConfig.dns.bind)
resolver . setServers ( [ runningConfig . dns . bind ] )
2019-05-21 08:58:43 +00:00
resolver . resolve ( host , function ( err , records ) {
2019-05-03 22:11:08 +00:00
if ( err ) {
// not as bad... that's at least a properly formatted response
if ( err . code == 'ENOTFOUND' ) {
records = null
} else
2019-05-21 08:58:43 +00:00
// leave bad
if ( err . code == 'ETIMEOUT' ) {
records = undefined
} else {
console . error ( 'lokinet lookup unknown err' , err )
}
2019-05-03 22:11:08 +00:00
}
//console.log(host, 'lokinet dns test results', records)
cb ( records )
} )
}
function findLokiNetDNS ( cb ) {
const localIPs = getBoundIPv4s ( )
var checksLeft = 0
2019-05-21 08:58:43 +00:00
var servers = [ ]
2019-05-03 22:11:08 +00:00
function checkDone ( ) {
if ( shuttingDown ) {
//if (cb) cb()
log ( 'not going to start lokinet, shutting down' )
return
}
checksLeft --
2019-05-21 08:58:43 +00:00
if ( checksLeft <= 0 ) {
2019-05-03 22:11:08 +00:00
log ( 'readResolv done' )
cb ( servers )
}
}
/ *
var resolvers = dns . getServers ( )
console . log ( 'Current resolvers' , resolvers )
// check local DNS servers in resolv config
for ( var i in resolvers ) {
const server = resolvers [ i ]
var idx = localIPs . indexOf ( server )
if ( idx != - 1 ) {
// local DNS server
console . log ( 'local DNS server detected' , server )
checksLeft ++
testDNSForLokinet ( server , function ( isLokinet ) {
if ( isLokinet ) {
// lokinet
console . log ( server , 'is a lokinet DNS server' )
servers . push ( server )
}
checkDone ( )
} )
}
}
* /
// maybe check all local ips too
2019-05-21 08:58:43 +00:00
for ( var i in localIPs ) {
2019-05-03 22:11:08 +00:00
const server = localIPs [ i ]
checksLeft ++
2019-05-21 08:58:43 +00:00
testDNSForLokinet ( server , function ( isLokinet ) {
2019-05-03 22:11:08 +00:00
if ( isLokinet !== undefined ) {
// lokinet
log ( server , 'is a lokinet DNS server' )
servers . push ( server )
}
checkDone ( )
} )
}
}
function readResolv ( dns _ip , cb ) {
const localIPs = getBoundIPv4s ( )
var servers = [ ]
var checksLeft = 0
//log('make sure we exclude?', dns_ip)
function checkDone ( ) {
if ( shuttingDown ) {
//if (cb) cb()
log ( 'not going to start lokinet, shutting down' )
return
}
checksLeft --
//log('readResolv reports left', checksLeft)
2019-05-21 08:58:43 +00:00
if ( checksLeft <= 0 ) {
2019-05-03 22:11:08 +00:00
log ( 'readResolv done' )
cb ( servers )
}
}
var resolvers = dns . getServers ( )
log ( 'Current resolvers' , resolvers )
2019-05-21 08:58:43 +00:00
for ( var i in resolvers ) {
2019-05-03 22:11:08 +00:00
const server = resolvers [ i ]
if ( server == dns _ip ) {
log ( 'preventing DNS loop on' , dns _ip )
continue
}
var idx = localIPs . indexOf ( server )
if ( idx != - 1 ) {
log ( 'local DNS server detected' , server )
checksLeft ++ // wait for it
2019-05-21 08:58:43 +00:00
testDNSForLokinet ( server , function ( isLokinet ) {
2019-05-03 22:11:08 +00:00
if ( isLokinet === undefined ) {
// not lokinet
log ( server , 'is not a lokinet DNS server' )
servers . push ( server )
}
checkDone ( )
} )
} else {
// non-local
log ( 'found remote DNS server' , server )
servers . push ( server )
}
}
checksLeft ++
checkDone ( )
/ *
const data = fs . readFileSync ( '/etc/resolv.conf' , 'utf-8' )
const lines = data . split ( /\n/ )
for ( var i in lines ) {
var line = lines [ i ] . trim ( )
if ( line . match ( /#/ ) ) {
var parts = line . split ( /#/ )
line = parts [ 0 ] . trim ( )
}
// done reducing
if ( ! line ) continue
if ( line . match ( /^nameserver / ) ) {
const server = line . replace ( /^nameserver / , '' )
var idx = localIPs . indexOf ( server )
if ( idx != - 1 ) {
console . log ( 'local DNS server detected' , server )
const resolver = new dns . Resolver ( )
resolver . setServers ( [ server ] )
checksLeft ++
resolver . resolve ( 'localhost.loki' , function ( err , records ) {
//if (err) console.error(err)
//console.log('local dns test results', records)
if ( records === undefined ) {
// not lokinet
console . log ( server , 'is not a lokinet DNS server' )
servers . push ( server )
}
checkDone ( )
} )
} else {
// non-local
console . log ( 'found remote DNS server' , server )
servers . push ( server )
}
continue
}
checkDone ( )
console . error ( 'readResolv unknown' , line )
}
return servers
* /
}
// this can really delay the start of lokinet
function findFreePort53 ( ips , index , cb ) {
log ( 'testing' , ips [ index ] , 'port 53' )
2019-05-21 08:58:43 +00:00
isDnsPort ( ips [ index ] , 53 , function ( res ) {
2019-05-03 22:11:08 +00:00
//console.log('isDnsPort res', res)
// false
if ( ! res ) {
log ( 'Found free port 53 on' , ips [ index ] , index )
cb ( ips [ index ] )
return
}
log ( 'Port 53 is not free on' , ips [ index ] , index )
if ( index + 1 == ips . length ) {
cb ( )
return
}
findFreePort53 ( ips , index + 1 , cb )
} )
}
// https://stackoverflow.com/a/40686853
2019-05-21 08:58:43 +00:00
function mkDirByPathSync ( targetDir , {
isRelativeToScript = false
} = { } ) {
2019-05-03 22:11:08 +00:00
const sep = path . sep
const initDir = path . isAbsolute ( targetDir ) ? sep : ''
const baseDir = isRelativeToScript ? _ _dirname : '.'
return targetDir . split ( sep ) . reduce ( ( parentDir , childDir ) => {
const curDir = path . resolve ( baseDir , parentDir , childDir )
try {
fs . mkdirSync ( curDir )
} catch ( err ) {
if ( err . code === 'EEXIST' ) { // curDir already exists!
return curDir
}
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
if ( err . code === 'ENOENT' ) { // Throw the original parentDir error on curDir `ENOENT` failure.
throw new Error ( ` EACCES: permission denied, mkdir ' ${ parentDir } ' ` )
}
const caughtErr = [ 'EACCES' , 'EPERM' , 'EISDIR' ] . indexOf ( err . code ) > - 1
if ( ! caughtErr || caughtErr && curDir === path . resolve ( targetDir ) ) {
throw err // Throw if it's just the last created dir.
}
}
return curDir
} , initDir )
}
function makeMultiplatformPath ( path ) {
return path
}
var cleanUpBootstrap = false
2019-05-21 08:58:43 +00:00
var cleanUpIni = false
2019-05-03 22:11:08 +00:00
function generateINI ( config , need , markDone , cb ) {
const homeDir = os . homedir ( )
//console.log('homeDir', homeDir)
//const data = fs.readFileSync(homeDir + '/.lokinet/lokinet.ini', 'utf-8')
//const jConfig = iniToJSON(data)
//console.dir(jConfig)
//const iConfig = jsonToINI(jConfig)
//console.log(iConfig)
var upstreams , lokinet _free53Ip , lokinet _nic
var use _lokinet _rpc _port = config . rpc _port
var lokinet _bootstrap _path = homeDir + '/.lokinet/bootstrap.signed'
var lokinet _nodedb = homeDir + '/.lokinet/netdb'
if ( config . netid ) {
lokinet _nodedb += '-' + config . netid
}
if ( ! fs . existsSync ( lokinet _nodedb ) ) {
log ( 'making' , lokinet _nodedb )
mkDirByPathSync ( lokinet _nodedb )
}
var upstreamDNS _servers = [ ]
var params = {
upstreamDNS _servers : upstreamDNS _servers ,
lokinet _free53Ip : lokinet _free53Ip ,
lokinet _nodedb : lokinet _nodedb ,
lokinet _bootstrap _path : lokinet _bootstrap _path ,
lokinet _nic : lokinet _nic ,
use _lokinet _rpc _port : use _lokinet _rpc _port ,
}
if ( config . bootstrap _url ) {
2019-05-21 08:58:43 +00:00
httpGet ( config . bootstrap _url , function ( bootstrapData ) {
2019-05-03 22:11:08 +00:00
if ( bootstrapData ) {
cleanUpBootstrap = true
const tmpRcPath = os . tmpdir ( ) + '/' + randomString ( 8 ) + '.lokinet_signed'
fs . writeFileSync ( tmpRcPath , bootstrapData , 'binary' )
log ( 'boostrap wrote' , bootstrapData . length , 'bytes to' , tmpRcPath )
//lokinet_bootstrap_path = tmpRcPath
params . lokinet _bootstrap _path = tmpRcPath
config . bootstrap _path = tmpRcPath
}
markDone ( 'bootstrap' , params )
} )
} else {
// seed version
//params.lokinet_bootstrap_path = ''
markDone ( 'bootstrap' , params )
}
2019-05-21 08:58:43 +00:00
readResolv ( config . dns _ip , function ( servers ) {
2019-05-03 22:11:08 +00:00
upstreamDNS _servers = servers
params . upstreamDNS _servers = servers
2019-05-21 08:58:43 +00:00
upstreams = 'upstream=' + servers . join ( '\nupstream=' )
2019-05-03 22:11:08 +00:00
markDone ( 'upstream' , params )
} )
2019-05-21 08:58:43 +00:00
log ( 'trying' , 'http://' + config . rpc _ip + ':' + config . rpc _port )
httpGet ( 'http://' + config . rpc _ip + ':' + config . rpc _port , function ( testData ) {
2019-05-03 22:11:08 +00:00
//log('rpc has', testData)
if ( testData !== undefined ) {
log ( 'Bumping RPC port' , testData )
// FIXME: retest new port
use _lokinet _rpc _port = use _lokinet _rpc _port + 1
params . use _lokinet _rpc _port = use _lokinet _rpc _port
}
markDone ( 'rpcCheck' , params )
} )
var skipDNS = false
if ( config . dns _ip || config . dns _port ) {
skipDNS = true
markDone ( 'dnsBind' , params )
}
2019-05-21 08:58:43 +00:00
getNetworkIP ( function ( e , ip ) {
2019-05-03 22:11:08 +00:00
if ( ip == 'error' || ! ip ) {
console . error ( 'NETWORK: can\'t detect default adapter IP address' )
// can't handle the exits here because we don't know if it's an actual requirements
// if we need netIf or dnsBind
if ( done . netIf !== undefined || done . dnsBind !== undefined ) {
process . exit ( )
}
}
log ( 'detected outgoing interface ip' , ip )
lokinet _nic = getIfNameFromIP ( ip )
params . lokinet _nic = lokinet _nic
params . interfaceIP = ip
log ( 'detected outgoing interface' , lokinet _nic )
markDone ( 'netIf' , params )
if ( skipDNS ) return
var tryIps = [ '127.0.0.1' ]
if ( os . platform ( ) == 'linux' ) {
tryIps . push ( '127.3.2.1' )
}
tryIps . push ( ip )
2019-05-21 08:58:43 +00:00
findFreePort53 ( tryIps , 0 , function ( free53Ip ) {
2019-05-03 22:11:08 +00:00
if ( free53Ip === undefined ) {
console . error ( 'NETWORK: Cant automatically find an IP to put a lokinet DNS server on' )
// can't handle the exits here because we don't know if it's an actual requirements
if ( done . dnsBind !== undefined ) {
process . exit ( )
}
}
lokinet _free53Ip = free53Ip
params . lokinet _free53Ip = free53Ip
log ( 'binding DNS port 53 to' , free53Ip )
markDone ( 'dnsBind' , params )
} )
} )
2019-05-21 08:58:43 +00:00
getPublicIPv4 ( function ( ip ) {
2019-05-03 22:11:08 +00:00
//log('generateINI - ip', ip)
params . publicIP = ip
markDone ( 'publicIP' , params )
} )
}
// unified post auto-config adjustments
// disk to running
function applyConfig ( file _config , config _obj ) {
// bootstrap section
// router mode: bootstrap is optional (might be a seed if not)
// client mode: bootstrap is required, can't have a seed client
if ( file _config . bootstrap _path ) {
config _obj . bootstrap = {
'add-node' : file _config . bootstrap _path
}
}
// router section
if ( file _config . nickname ) {
config _obj . router . nickname = file _config . nickname
}
// set default netid based on testnet
if ( file _config . lokid && file _config . lokid . network == "test" ) {
config _obj . router . netid = 'service'
//runningConfig.network['ifaddr'] = '10.254.0.1/24' // hack for Ryan's box
}
if ( file _config . netid ) {
config _obj . router . netid = file _config . netid
}
// network section
if ( file _config . ifname ) {
config _obj . network . ifname = file _config . ifname
}
if ( file _config . ifaddr ) {
config _obj . network . ifaddr = file _config . ifaddr
}
// dns section
if ( file _config . dns _ip || file _config . dns _port ) {
// FIXME: dynamic dns ip
// we'd have to move the DNS autodetection here
// detect free port 53 on ip
// for now just make sure we have sane defaults
var ip = file _config . dns _ip
if ( ! ip ) ip = '127.0.0.1'
var dnsPort = file _config . dns _port
if ( dnsPort === undefined ) dnsPort = 53
config _obj . dns . bind = ip + ':' + dnsPort
}
}
var runningConfig = { }
var genSnCallbackFired
2019-05-21 08:58:43 +00:00
2019-05-03 22:11:08 +00:00
function generateSerivceNodeINI ( config , cb ) {
const homeDir = os . homedir ( )
var done = {
bootstrap : false ,
2019-05-21 08:58:43 +00:00
upstream : false ,
rpcCheck : false ,
dnsBind : false ,
netIf : false ,
publicIP : false ,
2019-05-03 22:11:08 +00:00
}
if ( config . publicIP ) {
done . publicIP = undefined
}
genSnCallbackFired = false
2019-05-21 08:58:43 +00:00
2019-05-03 22:11:08 +00:00
function markDone ( completeProcess , params ) {
done [ completeProcess ] = true
let ready = true
2019-05-21 08:58:43 +00:00
for ( var i in done ) {
2019-05-03 22:11:08 +00:00
if ( ! done [ i ] ) {
ready = false
log ( i , 'is not ready' )
break
}
}
if ( shuttingDown ) {
//if (cb) cb()
log ( 'not going to start lokinet, shutting down' )
return
}
if ( ! ready ) return
// we may have un-required proceses call markDone after we started
if ( genSnCallbackFired ) return
genSnCallbackFired = true
var keyPath = homeDir + '/.loki/'
//
if ( config . lokid . data _dir ) {
keyPath = config . lokid . data _dir
// make sure it has a trailing slash
2019-05-21 08:58:43 +00:00
if ( keyPath [ keyPath . length - 1 ] != '/' ) {
2019-05-03 22:11:08 +00:00
keyPath += '/'
}
}
if ( config . lokid . network == "test" || config . lokid . network == "demo" ) {
keyPath += 'testnet/'
}
keyPath += 'key'
log ( 'markDone params' , JSON . stringify ( params ) )
log ( 'PUBLIC' , params . publicIP , 'IFACE' , params . interfaceIP )
var useNAT = false
if ( params . publicIP != params . interfaceIP ) {
log ( 'NAT DETECTED MAKE SURE YOU FORWARD UDP PORT' , config . public _port , 'on' , params . publicIP , 'to' , params . interfaceIP )
useNAT = true
}
log ( 'Drafting lokinet service node config' )
// FIXME: lock down identity.private for storage server
runningConfig = {
router : {
nickname : 'ldl' ,
} ,
dns : {
upstream : params . upstreamDNS _servers ,
bind : params . lokinet _free53Ip + ':53' ,
} ,
netdb : {
dir : params . lokinet _nodedb ,
} ,
bind : {
// will be set after
} ,
2019-05-21 08:58:43 +00:00
network : { } ,
2019-05-03 22:11:08 +00:00
api : {
enabled : true ,
bind : config . rpc _ip + ':' + params . use _lokinet _rpc _port
} ,
lokid : {
enabled : true ,
jsonrpc : config . lokid . rpc _ip + ':' + config . lokid . rpc _port ,
username : config . lokid . rpc _user ,
password : config . lokid . rpc _pass ,
'service-node-seed' : keyPath
}
}
if ( useNAT ) {
2019-05-21 08:58:43 +00:00
runningConfig . router [ 'public-ip' ] = params . publicIP
2019-05-03 22:11:08 +00:00
runningConfig . router [ 'public-port' ] = config . public _port
}
// inject manual NAT config?
if ( config . public _ip ) {
2019-05-21 08:58:43 +00:00
runningConfig . router [ 'public-ip' ] = config . public _ip
2019-05-03 22:11:08 +00:00
runningConfig . router [ 'public-port' ] = config . public _port
}
runningConfig . bind [ params . lokinet _nic ] = config . public _port
if ( config . internal _port ) {
runningConfig . bind [ params . lokinet _nic ] = config . internal _port
}
applyConfig ( config , runningConfig )
// optional bootstrap (might be a seed if not)
// doesn't work
//runningConfig.network['type'] = 'null' // disable exit
//runningConfig.network['enabled'] = true;
cb ( ini . jsonToINI ( runningConfig ) )
}
generateINI ( config , done , markDone , cb )
}
var genClientCallbackFired
2019-05-21 08:58:43 +00:00
2019-05-03 22:11:08 +00:00
function generateClientINI ( config , cb ) {
var done = {
bootstrap : false ,
2019-05-21 08:58:43 +00:00
upstream : false ,
rpcCheck : false ,
dnsBind : false ,
2019-05-03 22:11:08 +00:00
}
genClientCallbackFired = false
2019-05-21 08:58:43 +00:00
2019-05-03 22:11:08 +00:00
function markDone ( completeProcess , params ) {
done [ completeProcess ] = true
let ready = true
2019-05-21 08:58:43 +00:00
for ( var i in done ) {
2019-05-03 22:11:08 +00:00
if ( ! done [ i ] ) {
ready = false
log ( i , 'is not ready' )
break
}
}
if ( ! ready ) return
// make sure we didn't already start
if ( genClientCallbackFired ) return
genClientCallbackFired = true
if ( ! params . use _lokinet _rpc _port ) {
// use default because we enable it
params . use _lokinet _rpc _port = 1190
}
log ( 'Drafting lokinet client config' )
runningConfig = {
router : {
nickname : 'ldl' ,
} ,
dns : {
upstream : params . upstreamDNS _servers ,
bind : params . lokinet _free53Ip + ':53' ,
} ,
netdb : {
dir : params . lokinet _nodedb ,
} ,
2019-05-21 08:58:43 +00:00
network : { } ,
2019-05-03 22:11:08 +00:00
api : {
enabled : true ,
bind : config . rpc _ip + ':' + params . use _lokinet _rpc _port
} ,
}
applyConfig ( config , runningConfig )
// a bootstrap is required, can't have a seed client
if ( ! runningConfig . bootstrap ) {
console . error ( 'no bootstrap for client' )
process . exit ( )
}
cb ( ini . jsonToINI ( runningConfig ) )
}
generateINI ( config , done , markDone , cb )
}
var shuttingDown
var lokinet
var lokinetLogging = true
2019-05-21 08:58:43 +00:00
2019-05-03 22:11:08 +00:00
function preLaunchLokinet ( config , cb ) {
//console.log('userInfo', os.userInfo('utf8'))
//console.log('started as', process.getuid(), process.geteuid())
// check user permissions
if ( os . platform ( ) == 'darwin' ) {
if ( process . getuid ( ) != 0 ) {
console . error ( 'MacOS requires you start this with sudo' )
process . exit ( )
}
2019-05-21 08:58:43 +00:00
// leave the linux commentary for later
/ *
} else {
if ( process . getuid ( ) == 0 ) {
console . error ( 'Its not recommended you run this as root' )
} * /
2019-05-03 22:11:08 +00:00
}
if ( os . platform ( ) == 'linux' ) {
// not root-like
exec ( 'getcap ' + config . binary _path , function ( error , stdout , stderr ) {
//console.log('stdout', stdout)
// src/loki-network/lokinet = cap_net_bind_service,cap_net_admin+eip
if ( ! ( stdout . match ( /cap_net_bind_service/ ) && stdout . match ( /cap_net_admin/ ) ) ) {
if ( process . getgid ( ) != 0 ) {
conole . log ( config . binary _path , 'does not have setcap. Please setcap the binary (make install usually does this) or run launcher root one time, so we can' )
process . exit ( )
} else {
// are root
log ( 'going to try to setcap your binary, so you dont need root' )
exec ( 'setcap cap_net_admin,cap_net_bind_service=+eip ' + config . binary _path , function ( error , stdout , stderr ) {
log ( 'binary permissions upgraded' )
} )
}
}
} )
}
// lokinet will crash if this file is zero bytes
if ( fs . existsSync ( 'profiles.dat' ) ) {
var stats = fs . statSync ( 'profiles.dat' )
if ( ! stats . size ) {
log ( 'cleaning router profiles' )
fs . unlinkSync ( 'profiles.dat' )
}
}
const tmpDir = os . tmpdir ( )
const tmpPath = tmpDir + '/' + randomString ( 8 ) + '.lokinet_ini'
cleanUpIni = true
config . ini _writer ( config , function ( iniData ) {
if ( shuttingDown ) {
//if (cb) cb()
log ( 'not going to write lokinet config, shutting down' )
return
}
log ( iniData , 'as' , tmpPath )
fs . writeFileSync ( tmpPath , iniData )
config . ini _file = tmpPath
cb ( )
} )
}
function launchLokinet ( config , cb ) {
if ( shuttingDown ) {
//if (cb) cb()
log ( 'not going to start lokinet, shutting down' )
return
}
if ( ! fs . existsSync ( config . ini _file ) ) {
log ( 'lokinet config file' , config . ini _file , 'does not exist' )
process . exit ( )
}
// command line options
var cli _options = [ config . ini _file ]
if ( config . verbose ) {
cli _options . push ( '-v' )
}
console . log ( 'network: launching' , config . binary _path , cli _options . join ( ' ' ) )
lokinet = spawn ( config . binary _path , cli _options )
if ( ! lokinet ) {
console . error ( 'failed to start lokinet, exiting...' )
// proper shutdown?
process . exit ( )
}
lokinet . killed = false
lokinet . stdout . on ( 'data' , ( data ) => {
var parts = data . toString ( ) . split ( /\n/ )
parts . pop ( )
data = parts . join ( '\n' )
if ( module . exports . onMessage ) {
module . exports . onMessage ( data )
}
} )
lokinet . stderr . on ( 'data' , ( data ) => {
if ( module . exports . onError ) {
module . exports . onError ( data )
}
} )
lokinet . on ( 'close' , ( code ) => {
log ( ` lokinet process exited with code ${ code } ` )
// code 0 means clean shutdown
lokinet . killed = true
// clean up
// if we have a temp bootstrap, clean it
if ( cleanUpBootstrap && runningConfig . bootstrap [ 'add-node' ] && fs . existsSync ( runningConfig . bootstrap [ 'add-node' ] ) ) {
fs . unlinkSync ( runningConfig . bootstrap [ 'add-node' ] )
}
if ( cleanUpIni && fs . existsSync ( config . ini _file ) ) {
fs . unlinkSync ( config . ini _file )
}
if ( ! shuttingDown ) {
if ( config . restart ) {
// restart it in 30 seconds to avoid pegging the cpu
2019-05-21 08:58:43 +00:00
setTimeout ( function ( ) {
2019-05-03 22:11:08 +00:00
log ( 'loki_daemon is still running, restarting lokinet' )
launchLokinet ( config )
} , 30 * 1000 )
} else {
// don't restart...
}
}
// else we're shutting down
} )
if ( cb ) cb ( )
}
function checkConfig ( config ) {
if ( config === undefined ) config = { }
if ( config . auto _config _test _ips === undefined ) config . auto _config _test _ips = [ '1.1.1.1' , '8.8.8.8' ]
2019-05-21 08:58:43 +00:00
if ( config . auto _config _test _host === undefined ) config . auto _config _test _host = 'www.imdb.com'
if ( config . auto _config _test _port === undefined ) config . auto _config _test _port = 80
2019-05-03 22:11:08 +00:00
auto _config _test _port = config . auto _config _test _port
auto _config _test _host = config . auto _config _test _host
2019-05-21 08:58:43 +00:00
auto _config _test _ips = config . auto _config _test _ips
2019-05-03 22:11:08 +00:00
2019-05-21 08:58:43 +00:00
if ( config . binary _path === undefined ) config . binary _path = '/usr/local/bin/lokinet'
2019-05-03 22:11:08 +00:00
// we don't always want a bootstrap (seed mode)
// maybe if no port we shouldn't configure it
2019-05-21 08:58:43 +00:00
if ( config . rpc _ip === undefined ) config . rpc _ip = '127.0.0.1'
if ( config . rpc _port === undefined ) config . rpc _port = 0
2019-05-03 22:11:08 +00:00
// set public_port ?
}
function waitForUrl ( url , cb ) {
2019-05-21 08:58:43 +00:00
httpGet ( url , function ( data ) {
2019-05-03 22:11:08 +00:00
//console.log('rpc data', data)
// will be undefined if down (ECONNREFUSED)
// if success
// <html><head><title>Unauthorized Access</title></head><body><h1>401 Unauthorized</h1></body></html>
if ( data ) {
cb ( )
} else {
// no data could me 404
if ( shuttingDown ) {
//if (cb) cb()
log ( 'not going to start lokinet, shutting down' )
return
}
2019-05-21 08:58:43 +00:00
setTimeout ( function ( ) {
2019-05-03 22:11:08 +00:00
waitForUrl ( url , cb )
} , 1000 )
}
} )
}
function startServiceNode ( config , cb ) {
// FIXME: if no bootstrap stomp it
// but allow for seed nodes (no bootstrap)?
checkConfig ( config )
config . ini _writer = generateSerivceNodeINI
config . restart = true
// FIXME: check for bootstrap stomp and strip it
// only us lokinet devs will need to make our own seed node
2019-05-21 08:58:43 +00:00
preLaunchLokinet ( config , function ( ) {
2019-05-03 22:11:08 +00:00
// test lokid rpc port first
// also this makes sure the service key file exists
2019-05-21 08:58:43 +00:00
var url = 'http://' + config . lokid . rpc _user + ':' + config . lokid . rpc _pass + '@' + config . lokid . rpc _ip + ':' + config . lokid . rpc _port
2019-05-03 22:11:08 +00:00
log ( 'lokinet waiting for lokid RPC server' )
2019-05-21 08:58:43 +00:00
waitForUrl ( url , function ( ) {
2019-05-03 22:11:08 +00:00
launchLokinet ( config , cb )
} )
} )
}
function startClient ( config , cb ) {
checkConfig ( config )
2019-05-21 08:58:43 +00:00
if ( config . bootstrap _path === undefined && config . bootstrap _url === undefined ) config . bootstrap _url = 'https://i2p.rocks/bootstrap.signed'
2019-05-03 22:11:08 +00:00
config . ini _writer = generateClientINI
2019-05-21 08:58:43 +00:00
preLaunchLokinet ( config , function ( ) {
2019-05-03 22:11:08 +00:00
launchLokinet ( config , cb )
} )
}
// return a truish value if so
function isRunning ( ) {
// should we block until port is responding?
return lokinet
}
// copied from lib
function isPidRunning ( pid ) {
if ( pid === undefined ) {
console . trace ( 'isPidRunning was passed undefined, reporting not running' )
return false
}
try {
process . kill ( pid , 0 )
//console.log('able to kill', pid)
return true
2019-05-21 08:58:43 +00:00
} catch ( e ) {
2019-05-03 22:11:08 +00:00
if ( e . code == 'ERR_INVALID_ARG_TYPE' ) {
// means pid was undefined
return true
}
if ( e . code == 'ESRCH' ) {
// not running
return false
}
if ( e . code == 'EPERM' ) {
// we're don't have enough permissions to signal this process
return true
}
console . log ( pid , 'isRunning' , e . code )
return false
}
}
// intent to stop lokinet and don't restart it
var retries = 0
2019-05-21 08:58:43 +00:00
2019-05-03 22:11:08 +00:00
function stop ( ) {
shuttingDown = true
if ( lokinet && lokinet . killed ) {
console . warn ( 'lokinet already stopped' )
retries ++
if ( retries > 3 ) {
// 3 exits in a row, something isn't dying
// just quit out
process . exit ( )
}
return
}
log ( 'requesting lokinet be shutdown' )
if ( lokinet && ! lokinet . killed ) {
log ( 'sending SIGINT to lokinet' , lokinet . pid )
process . kill ( lokinet . pid , 'SIGINT' )
lokinet . killed = true
// HACK: lokinet on macos can not be killed if rpc port is in use
var monitorTimerStart = Date . now ( )
2019-05-21 08:58:43 +00:00
var monitorTimer = setInterval ( function ( ) {
2019-05-03 22:11:08 +00:00
if ( ! isPidRunning ( lokinet . pid ) ) {
// launcher can't exit until this interval is cleared
clearInterval ( monitorTimer )
} else {
var diff = Date . now ( ) - monitorTimerStart
if ( diff > 15 * 1000 ) {
// reach 15 secs and lokinet is still running
// escalate it
console . error ( 'Lokinet is still running 15s after we intentionally stopped lokinet?' )
process . kill ( lokinet . pid , 'SIGKILL' )
} else
if ( diff > 30 * 1000 ) {
// reach 30 secs and lokinet is still running
// escalate it
console . error ( 'Lokinet is still running 30s after we intentionally killed lokinet?' )
var handles = process . _getActiveHandles ( )
console . log ( 'handles' , handles . length )
2019-05-21 08:58:43 +00:00
for ( var i in handles ) {
2019-05-03 22:11:08 +00:00
var handle = handles [ i ]
console . log ( i , 'type' , handle . _type , handle )
}
console . log ( 'requests' , process . _getActiveRequests ( ) . length )
console . log ( 'forcing exit' )
process . exit ( )
}
}
} , 1000 )
// this timer will stop the system from shutting down
/ *
setTimeout ( function ( ) {
try {
// check to see if still running
process . kill ( lokinet . pid , 0 )
log ( 'sending SIGKILL to lokinet' )
process . kill ( lokinet . pid , 'SIGKILL' )
} catch ( e ) {
console . error ( 'Launcher is still running 15s after we intentionally stopped lokinet?' )
var handles = process . _getActiveHandles ( )
console . log ( 'handles' , handles . length )
for ( var i in handles ) {
var handle = handles [ i ]
console . log ( i , 'type' , handle . _type , handle )
}
console . log ( 'requests' , process . _getActiveRequests ( ) . length )
process . exit ( )
}
} , 15 * 1000 )
* /
}
}
// isRunning covers this too well
function getPID ( ) {
2019-05-21 08:58:43 +00:00
return ( lokinet && ! lokinet . killed && lokinet . pid ) ? lokinet . pid : 0
2019-05-03 22:11:08 +00:00
}
function enableLogging ( ) {
lokinetLogging = true
}
function disableLogging ( ) {
console . log ( 'Disabling lokinet logging' )
lokinetLogging = false
}
function getLokiNetIP ( cb ) {
function checkDNS ( ) {
log ( 'lokinet seems to be running, querying' , runningConfig . dns . bind )
// where's our DNS server?
//log('RunningConfig says our lokinet\'s DNS is on', runningConfig.dns.bind)
2019-05-21 08:58:43 +00:00
testDNSForLokinet ( runningConfig . dns . bind , function ( ips ) {
2019-05-03 22:11:08 +00:00
//log('lokinet test', ips)
if ( ips && ips . length ) {
cb ( ips [ 0 ] )
} else {
// , retrying
console . error ( 'cant communicate with lokinet DNS' )
/ *
//process.exit()
setTimeout ( function ( ) {
getLokiNetIP ( cb )
} , 1000 )
* /
cb ( )
}
} )
}
if ( runningConfig . api . enabled ) {
log ( 'wait for lokinet startup' , runningConfig . api )
2019-05-21 08:58:43 +00:00
var url = 'http://' + runningConfig . api . bind + '/'
waitForUrl ( url , function ( ) {
2019-05-03 22:11:08 +00:00
checkDNS ( )
} )
} else {
checkDNS ( )
}
}
module . exports = {
2019-05-21 08:58:43 +00:00
startServiceNode : startServiceNode ,
startClient : startClient ,
checkConfig : checkConfig ,
findLokiNetDNS : findLokiNetDNS ,
lookup : lookup ,
isRunning : isRunning ,
stop : stop ,
getLokiNetIP : getLokiNetIP ,
enableLogging : enableLogging ,
disableLogging : disableLogging ,
getPID : getPID ,
2019-05-03 22:11:08 +00:00
// FIXME: should we allow hooking of log() too?
2019-05-21 08:58:43 +00:00
onMessage : function ( data ) {
2019-05-03 22:11:08 +00:00
if ( lokinetLogging ) {
console . log ( ` lokinet: ${ data } ` )
}
} ,
2019-05-21 08:58:43 +00:00
onError : function ( data ) {
2019-05-03 22:11:08 +00:00
console . log ( ` lokineterr: ${ data } ` )
} ,
}