From d4011aaf6d34b66302a0996d3556e81e3f1299b1 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Sun, 17 May 2020 20:11:27 -0700 Subject: [PATCH] add adnOnionRequestCounter for sendViaOnion, use lokiRpcUtils.sendOnionRequestLsrpcDest --- js/modules/loki_app_dot_net_api.js | 183 ++++++++++++----------------- 1 file changed, 77 insertions(+), 106 deletions(-) diff --git a/js/modules/loki_app_dot_net_api.js b/js/modules/loki_app_dot_net_api.js index 8d6e2ff59..defb14ebd 100644 --- a/js/modules/loki_app_dot_net_api.js +++ b/js/modules/loki_app_dot_net_api.js @@ -36,12 +36,8 @@ const snodeHttpsAgent = new https.Agent({ const timeoutDelay = ms => new Promise(resolve => setTimeout(resolve, ms)); -const sendViaOnion = async ( - srvPubKey, - url, - pFetchOptions, - options = {} -) => { +let adnOnionRequestCounter = 0; +const sendViaOnion = async (srvPubKey, url, pFetchOptions, options = {}) => { if (!srvPubKey) { log.error( 'loki_app_dot_net:::sendViaOnion - called without a server public key' @@ -49,7 +45,19 @@ const sendViaOnion = async ( return {}; } + // set retry count + let adnOnionRequestCount; + if (options.retry === undefined) { + // eslint-disable-next-line no-param-reassign + options.retry = 0; + adnOnionRequestCounter += 1; // increment counter + adnOnionRequestCount = 0 + adnOnionRequestCounter; // copy value + } else { + adnOnionRequestCount = options.counter; + } + const fetchOptions = pFetchOptions; // make lint happy + // safety issue with file server, just safer to have this if (fetchOptions.headers === undefined) { fetchOptions.headers = {}; @@ -57,16 +65,16 @@ const sendViaOnion = async ( const payloadObj = { method: fetchOptions.method, - headers: {...fetchOptions.headers, bob: "kob" }, + body: fetchOptions.body, + // no initial / + endpoint: url.pathname.replace(/^\//, ''), + headers: { ...fetchOptions.headers }, }; - //console.log('payloadObj', payloadObj) - //if (fetchOptions.body === undefined) fetchOptions.body = ''; - payloadObj.body = fetchOptions.body; // might need to b64 if binary... - //console.log('body', payloadObj.body) - console.log('loki_app_dot_net:::sendViaOnion - payloadObj', payloadObj) + if (url.search) { + payloadObj.endpoint += `?${url.search}`; + } // from https://github.com/sindresorhus/is-stream/blob/master/index.js - let fileUpload = false; if ( payloadObj.body && typeof payloadObj.body === 'object' && @@ -80,116 +88,76 @@ const sendViaOnion = async ( payloadObj.body = { fileUpload: fData.toString('base64'), }; - fileUpload = true; } const pathNodes = await lokiSnodeAPI.getOnionPath(); if (!pathNodes || !pathNodes.length) { log.warn( - 'loki_app_dot_net:::sendViaOnion - failing, no path available' + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - failing, no path available` ); return {}; } - //console.log('loki_app_dot_net:::sendViaOnion - ourPubKey', StringView.arrayBufferToHex(srvPubKey).substr(0,32), '...', StringView.arrayBufferToHex(srvPubKey).substr(32)) - //console.log('loki_app_dot_net:::sendViaOnion - pathNodes', pathNodes) - // pathNodes = [''] - const guardUrl = `https://${pathNodes[0].ip}:${pathNodes[0].port}/onion_req`; - // first parameter takes an arrayBuffer - const destCtx = await lokiRpcUtils.encryptForPubKey(srvPubKey, payloadObj); - - const tPayload = destCtx.ciphertext; - const reqJson = { - ciphertext: dcodeIO.ByteBuffer.wrap(tPayload).toString('base64'), - ephemeral_key: StringView.arrayBufferToHex(destCtx), - }; - const reqStr = JSON.stringify(reqJson); - - const snPubkey = StringView.hexToArrayBuffer(pathNodes[0].pubkey_x25519); - const guardCtx = await lokiRpcUtils.encryptForPubKey(snPubkey, reqStr); - //const guardCtx = await lokiRpcUtils.encryptForRelay(pathNodes[0], StringView.arrayBufferToHex(srvPubKey), destCtx); - // we don't want a destination so don't need a relay at all - // const guardPayloadObj = await lokiRpcUtils.makeOnionRequest(pathNodes, destCtx, StringView.arrayBufferToHex(srvPubKey)); - - //const guardCtx = destCtx; - - const ciphertextBase64 = dcodeIO.ByteBuffer.wrap( - guardCtx.ciphertext - ).toString('base64'); - - const guardPayloadObj = { - ciphertext: ciphertextBase64, - ephemeral_key: StringView.arrayBufferToHex(guardCtx.ephemeral_key), - host: url.host, - target: '/loki/v1/lsrpc', - }; - - const firstHopOptions = { - method: 'POST', - body: JSON.stringify(guardPayloadObj), - // we are talking to a snode... - agent: snodeHttpsAgent, - }; - const encryptedResult = await nodeFetch(guardUrl, firstHopOptions); - // weird this doesn't need NODE_TLS_REJECT_UNAUTHORIZED = '0' - - const result = await lokiRpcUtils.processOnionResponse(0, encryptedResult, destCtx.symmetricKey, true); - console.log('result', result) - let response = {}; - let txtResponse = ''; - -/* - const txtResponse = await result.text(); - if (txtResponse.match(/^Service node is not ready: not in any swarm/i)) { - // mark snode bad - const randomPoolRemainingCount = lokiSnodeAPI.markRandomNodeUnreachable( - randSnode + // do the request + let result; + try { + result = await lokiRpcUtils.sendOnionRequestLsrpcDest( + 0, + pathNodes, + srvPubKey, + url.host, + payloadObj, + adnOnionRequestCount ); - log.warn( - `loki_app_dot_net:::sendViaOnion - Marking random snode bad, internet address ${ - randSnode.ip - }:${ - randSnode.port - }. ${randomPoolRemainingCount} snodes remaining in randomPool` + } catch (e) { + log.error( + 'loki_app_dot_net:::sendViaOnion - lokiRpcUtils error', + e.code, + e.message ); - // retry (hopefully with new snode) - // FIXME: max number of retries... - return sendViaOnion(srvPubKey, url, fetchOptions, options); + return {}; } - let response = {}; - try { - // it's no longer JSON - response = txtResponse; - } catch (e) { - log.warn( - `loki_app_dot_net:::sendViaOnion - Could not parse outer JSON [${txtResponse}]`, - url, + // handle error/retries + if (!result.status) { + log.error( + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - Retry #${ + options.retry + } Couldnt handle onion request, retrying`, + payloadObj ); + // eslint-disable-next-line no-param-reassign + options.retry += 1; + // eslint-disable-next-line no-param-reassign + options.counter = adnOnionRequestCount; + return sendViaOnion(srvPubKey, url, pFetchOptions, options); } - // convert base64 in response to binary - const ivAndCiphertextResponse = dcodeIO.ByteBuffer.wrap( - response, - 'base64' - ).toArrayBuffer(); - const decrypted = await libloki.crypto.DHDecrypt( - symKey, - ivAndCiphertextResponse - ); - const textDecoder = new TextDecoder(); - const respStr = textDecoder.decode(decrypted); - - // replace response + // get the return variables we need + let response = {}; + let txtResponse = ''; + let body = ''; try { - response = options.textResponse ? respStr : JSON.parse(respStr); + body = JSON.parse(result.body); } catch (e) { - log.warn( - `loki_app_dot_net:::sendViaOnion - Could not parse inner JSON [${respStr}]`, - url, + log.error( + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - Cant decode JSON body`, + result.body ); } -*/ + const code = result.status; + txtResponse = JSON.stringify(body); + response = body; + response.headers = result.headers; + log.debug( + `loki_app_dot_net:::sendViaOnion #${adnOnionRequestCount} - ADN GCM body length`, + txtResponse.length, + 'code', + code, + 'headers', + response.headers + ); + return { result, txtResponse, response }; }; @@ -257,7 +225,11 @@ const sendToProxy = async ( payloadObj.body = false; // free memory // make temporary key for this request/response - const ephemeralKey = await libsignal.Curve.async.generateKeyPair(); + // const ephemeralKey = await libsignal.Curve.generateKeyPair(); // sync + // async maybe preferable to avoid cpu spikes + // tho I think sync might be more apt in certain cases here... + // like sending + const ephemeralKey = await libloki.crypto.generateEphemeralKeyPair(); // mix server pub key with our priv key const symKey = await libsignal.Curve.async.calculateAgreement( @@ -430,8 +402,7 @@ const serverRequest = async (endpoint, options = {}) => { fetchOptions, options )); - } else - if ( + } else if ( window.lokiFeatureFlags.useSnodeProxy && FILESERVER_HOSTS.includes(host) ) {