From f231daf660e08430b029cbd93744332a0f0e7de0 Mon Sep 17 00:00:00 2001
From: librelois <elois@ifee.fr>
Date: Fri, 3 Nov 2017 03:34:12 +0100
Subject: [PATCH] [enh] improving tor config and timeout management

---
 app/cli.ts                             |  8 +--
 app/lib/dal/fileDAL.ts                 |  2 +-
 app/lib/dto/ConfDTO.ts                 |  4 +-
 app/lib/dto/PeerDTO.ts                 | 22 ++++----
 app/lib/proxy.ts                       | 48 ++++++++++-------
 app/modules/bma/lib/network.ts         |  2 +-
 app/modules/ws2p/lib/WS2PClient.ts     |  2 +-
 app/modules/ws2p/lib/WS2PCluster.ts    |  9 ++--
 app/modules/ws2p/lib/WS2PConnection.ts |  6 +--
 app/modules/ws2p/lib/WS2PServer.ts     | 15 ++++--
 app/modules/ws2p/lib/constants.ts      | 10 ++--
 index.ts                               | 29 +++++-----
 server.ts                              |  3 +-
 test/fast/proxies.ts                   | 75 +++++++++++++++++++++-----
 14 files changed, 155 insertions(+), 80 deletions(-)

diff --git a/app/cli.ts b/app/cli.ts
index 187022269..26accf24f 100644
--- a/app/cli.ts
+++ b/app/cli.ts
@@ -49,10 +49,10 @@ export const ExecuteCommand = () => {
         .option('--nostdout', 'Disable stdout printing for `export-bc` command')
         .option('--noshuffle', 'Disable peers shuffling for `sync` command')
 
-        .option('--proxy-socks <host:port>', 'Use Socks Proxy')
-        .option('--proxy-tor <host:port>', 'Use Tor Socks Proxy')
-        .option('--tor-always', 'Pass all outgoing requests through the tor network')
-        .option('--tor-mixed', 'Pass only ".onion" outgoing requests through the tor network. It\'s the default behavior')
+        .option('--socks-proxy <host:port>', 'Use Socks Proxy')
+        .option('--tor-proxy <host:port>', 'Use Tor Socks Proxy')
+        .option('--reaching-clear-ep <clear|tor|none>', 'method for reaching an clear endpoint')
+        .option('--force-tor', 'force duniter to contact endpoint tor (if you redirect the traffic to tor yourself)')
         .option('--rm-proxies', 'Remove all proxies')
 
         .option('--timeout <milliseconds>', 'Timeout to use when contacting peers', parseInt)
diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts
index 267d0f184..600226123 100644
--- a/app/lib/dal/fileDAL.ts
+++ b/app/lib/dal/fileDAL.ts
@@ -845,7 +845,7 @@ export class FileDAL {
       const savedConf = await this.confDAL.loadConf();
       const savedProxyConf = _(savedConf.proxyConf).extend({});
       conf = _(savedConf).extend(overrideConf || {});
-      if (overrideConf.proxyConf !== undefined) {} else {
+      if (overrideConf.proxiesConf !== undefined) {} else {
         conf.proxyConf = _(savedProxyConf).extend({});
       }
     }
diff --git a/app/lib/dto/ConfDTO.ts b/app/lib/dto/ConfDTO.ts
index fdd2038b7..1227913e3 100644
--- a/app/lib/dto/ConfDTO.ts
+++ b/app/lib/dto/ConfDTO.ts
@@ -47,7 +47,7 @@ export interface KeypairConfDTO {
 }
 
 export interface NetworkConfDTO {
-  proxyConf: ProxiesConf|undefined
+  proxiesConf: ProxiesConf|undefined
   nobma: boolean
   remoteport: number
   remotehost: string|null
@@ -136,7 +136,7 @@ export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO,
     public homename: string,
     public memory: boolean,
     public nobma: boolean,
-    public proxyConf: ProxiesConf|undefined,
+    public proxiesConf: ProxiesConf|undefined,
     public ws2p?: {
       privateAccess: boolean
       publicAccess: boolean
diff --git a/app/lib/dto/PeerDTO.ts b/app/lib/dto/PeerDTO.ts
index 2bb2e91cd..dc5754b68 100644
--- a/app/lib/dto/PeerDTO.ts
+++ b/app/lib/dto/PeerDTO.ts
@@ -94,11 +94,11 @@ export class PeerDTO implements Cloneable {
     return bma || {};
   }
 
-  getWS2P(tor:boolean = false) {
+  getWS2P(canReachTorEp:boolean, canReachClearEp:boolean) {
     let api:{ uuid:string, host:string, port:number, path:string }|null = null
-    const endpointRegexp = (tor) ? CommonConstants.WS2PTOR_REGEXP:CommonConstants.WS2P_REGEXP
+    const endpointRegexp = (canReachTorEp) ? CommonConstants.WS2PTOR_REGEXP:CommonConstants.WS2P_REGEXP
     for (const ep of this.endpoints) {
-      if (tor) {
+      if (canReachTorEp) {
         const matches:any = ep.match(CommonConstants.WS2PTOR_REGEXP)
         if (matches) {
           return {
@@ -109,13 +109,15 @@ export class PeerDTO implements Cloneable {
           }
         }
       }
-      const matches:any = !api && ep.match(CommonConstants.WS2P_REGEXP)
-      if (matches) {
-        api = {
-          uuid: matches[1],
-          host: matches[2] || '',
-          port: parseInt(matches[3]) || 0,
-          path: matches[4]
+      if (canReachClearEp) {
+        const matches:any = !api && ep.match(CommonConstants.WS2P_REGEXP)
+        if (matches) {
+          api = {
+            uuid: matches[1],
+            host: matches[2] || '',
+            port: parseInt(matches[3]) || 0,
+            path: matches[4]
+          }
         }
       }
     }
diff --git a/app/lib/proxy.ts b/app/lib/proxy.ts
index 051a57d3a..43b000a22 100644
--- a/app/lib/proxy.ts
+++ b/app/lib/proxy.ts
@@ -1,39 +1,51 @@
 const SocksProxyAgent = require('socks-proxy-agent');
 
-const HOST_ONION_REGEX = new RegExp('(\S*?\.onion)$');
-const WS_ENDPOINT_ONION_REGEX =  new RegExp('(?:wss?:\/\/)?(?:www)?(\S*?\.onion)(\/[-\w]*)*');
+const HOST_ONION_REGEX = new RegExp('(?:www\.)?([0-9a-z]{16}?\.onion)$');
+const WS_FULL_ADDRESS_ONION_REGEX =  new RegExp('^(?:wss?:\/\/)(?:www\.)?([0-9a-z]{16}\.onion)(:[0-9]+)?(\/[-\w]*)*');
 
 export class ProxiesConf {
   public proxySocksAddress: string|undefined
   public proxyTorAddress: string|undefined
-  public alwaysUseTor: boolean|undefined
+  public reachingClearEp: string
+  public forceTor: boolean
 
   constructor () {
     this.proxySocksAddress = undefined
     this.proxyTorAddress = undefined
-    this.alwaysUseTor = undefined
+    this.reachingClearEp = 'clear'
+    this.forceTor = false
   }
 
-  static canReachTorEndpoint(proxyConf: ProxiesConf|undefined):boolean {
-    return (proxyConf !== undefined && (proxyConf.alwaysUseTor === true || (proxyConf.proxyTorAddress !== undefined) ) )
+  static canReachClearEndpoint(proxiesConf: ProxiesConf|undefined):boolean {
+    return (proxiesConf === undefined || proxiesConf.reachingClearEp !== 'none')
   }
 
-  static httpProxy(url:string, proxyConf: ProxiesConf|undefined):string|undefined {
-    return ProxiesConf.chooseProxyAgent(url, proxyConf, HOST_ONION_REGEX)
+  static canReachTorEndpoint(proxiesConf: ProxiesConf|undefined):boolean {
+    return (proxiesConf !== undefined && (proxiesConf.forceTor || proxiesConf.proxyTorAddress !== undefined) )
   }
 
-  static wsProxy(address:string, proxyConf: ProxiesConf|undefined):string|undefined {
-    return ProxiesConf.chooseProxyAgent(address, proxyConf, WS_ENDPOINT_ONION_REGEX)
+  static httpProxy(url:string, proxiesConf: ProxiesConf|undefined):string|undefined {
+    return ProxiesConf.chooseProxyAgent(url, proxiesConf, HOST_ONION_REGEX)
   }
 
-  private static chooseProxyAgent(address:string, proxyConf: ProxiesConf|undefined,  onionRegex:RegExp):string|undefined {
-    if (proxyConf !== undefined) {
-      if ( proxyConf.proxyTorAddress !== undefined && (proxyConf.alwaysUseTor || address.match(onionRegex)))
-      {
-          return proxyConf.proxyTorAddress
-      }
-      else if (proxyConf.proxySocksAddress !== undefined) {
-          return proxyConf.proxySocksAddress
+  static wsProxy(address:string, proxiesConf: ProxiesConf|undefined):string|undefined {
+    return ProxiesConf.chooseProxyAgent(address, proxiesConf, WS_FULL_ADDRESS_ONION_REGEX)
+  }
+
+  private static chooseProxyAgent(address:string, proxiesConf: ProxiesConf|undefined,  onionRegex:RegExp):string|undefined {
+    if (proxiesConf !== undefined) {
+      if (address.match(onionRegex)) {
+        if (ProxiesConf.canReachTorEndpoint(proxiesConf)) {
+          return proxiesConf.proxyTorAddress
+        }
+      } else {
+        if (ProxiesConf.canReachClearEndpoint(proxiesConf)) {
+          if (proxiesConf.reachingClearEp == 'tor') {
+            return proxiesConf.proxyTorAddress
+          } else {
+            return proxiesConf.proxySocksAddress
+          }
+        }
       }
     }
     return undefined
diff --git a/app/modules/bma/lib/network.ts b/app/modules/bma/lib/network.ts
index ae3459eb9..250a622bf 100644
--- a/app/modules/bma/lib/network.ts
+++ b/app/modules/bma/lib/network.ts
@@ -337,7 +337,7 @@ async function upnpConf (noupnp:boolean, logger:any) {
   const publicPort = await getAvailablePort(client)
   const privatePort = publicPort
   const conf:NetworkConfDTO = {
-    proxyConf: undefined,
+    proxiesConf: undefined,
     nobma: true,
     port: privatePort,
     ipv4: '127.0.0.1',
diff --git a/app/modules/ws2p/lib/WS2PClient.ts b/app/modules/ws2p/lib/WS2PClient.ts
index 80a4fd1bd..569609f6e 100644
--- a/app/modules/ws2p/lib/WS2PClient.ts
+++ b/app/modules/ws2p/lib/WS2PClient.ts
@@ -20,7 +20,7 @@ export class WS2PClient {
       messageHandler,
       new WS2PPubkeyLocalAuth(server.conf.currency , k2, allowKey),
       new WS2PPubkeyRemoteAuth(server.conf.currency, k2, allowKey),
-      ProxiesConf.wsProxy(fullEndpointAddress, server.conf.proxyConf),
+      ProxiesConf.wsProxy(fullEndpointAddress, server.conf.proxiesConf),
       {
         connectionTimeout: WS2PConstants.REQUEST_TIMEOUT,
         requestTimeout: WS2PConstants.REQUEST_TIMEOUT
diff --git a/app/modules/ws2p/lib/WS2PCluster.ts b/app/modules/ws2p/lib/WS2PCluster.ts
index 5cc022579..875c552dd 100644
--- a/app/modules/ws2p/lib/WS2PCluster.ts
+++ b/app/modules/ws2p/lib/WS2PCluster.ts
@@ -332,12 +332,12 @@ export class WS2PCluster {
     const prefered = ((this.server.conf.ws2p && this.server.conf.ws2p.preferedNodes) || []).slice() // Copy
     // Our key is also a prefered one, so we connect to our siblings
     prefered.push(this.server.conf.pair.pub)
-    const imCanReachTorEndpoint = ProxiesConf.canReachTorEndpoint(this.server.conf.proxyConf)
+    const canReachTorEndpoint = ProxiesConf.canReachTorEndpoint(this.server.conf.proxiesConf)
     peers.sort((a, b) => {
       const aIsPrefered = prefered.indexOf(a.pubkey) !== -1
       const bIsPrefered = prefered.indexOf(b.pubkey) !== -1
 
-      if (imCanReachTorEndpoint) {
+      if (canReachTorEndpoint) {
         const aAtWs2pTorEnpoint = a.endpoints.filter(function (element) { return element.match(CommonConstants.WS2PTOR_REGEXP); }).length > 0
         const bAtWs2pTorEnpoint = b.endpoints.filter(function (element) { return element.match(CommonConstants.WS2PTOR_REGEXP); }).length > 0
 
@@ -367,9 +367,10 @@ export class WS2PCluster {
       }
     })
     let i = 0
+    const canReachClearEndpoint = ProxiesConf.canReachClearEndpoint(this.server.conf.proxiesConf)
     while (i < peers.length && this.clientsCount() < this.maxLevel1Size) {
       const p = peers[i]
-      const api = p.getWS2P(imCanReachTorEndpoint)
+      const api = p.getWS2P(canReachTorEndpoint, canReachClearEndpoint)
       if (api) {
         try {
           // We do not connect to local host
@@ -396,7 +397,7 @@ export class WS2PCluster {
         // New peer
         if (data.endpoints) {
           const peer = PeerDTO.fromJSONObject(data)
-          const ws2pEnpoint = peer.getWS2P()
+          const ws2pEnpoint = peer.getWS2P(ProxiesConf.canReachTorEndpoint(this.server.conf.proxiesConf), ProxiesConf.canReachClearEndpoint(this.server.conf.proxiesConf))
           if (ws2pEnpoint) {
             // Check if already connected to the pubkey (in any way: server or client)
             const connectedPubkeys = this.getConnectedPubkeys()
diff --git a/app/modules/ws2p/lib/WS2PConnection.ts b/app/modules/ws2p/lib/WS2PConnection.ts
index 9a08ac742..0595e5626 100644
--- a/app/modules/ws2p/lib/WS2PConnection.ts
+++ b/app/modules/ws2p/lib/WS2PConnection.ts
@@ -278,10 +278,10 @@ export class WS2PConnection {
       requestTimeout: REQUEST_TIMEOUT_VALUE
     },
     expectedPub:string = "") {
-      if (proxySocksAddress !== undefined) {
+      if (address.match(WS2PConstants.FULL_ADDRESS_ONION_REGEX)) {
         options = {
-          connectionTimeout: WS2PConstants.PROXY_TIMEOUT,
-          requestTimeout: WS2PConstants.PROXY_TIMEOUT
+          connectionTimeout: WS2PConstants.CONNEXION_TOR_TIMEOUT,
+          requestTimeout: WS2PConstants.REQUEST_TOR_TIMEOUT
         }
       }
       const websocket = (proxySocksAddress !== undefined) ? new ws(address, { agent: SocksProxyAgent("socks://"+proxySocksAddress) }):new ws(address)
diff --git a/app/modules/ws2p/lib/WS2PServer.ts b/app/modules/ws2p/lib/WS2PServer.ts
index c9d3dc9fb..ae79815fe 100644
--- a/app/modules/ws2p/lib/WS2PServer.ts
+++ b/app/modules/ws2p/lib/WS2PServer.ts
@@ -63,16 +63,23 @@ export class WS2PServer extends events.EventEmitter {
         }
         return await this.shouldAcceptConnection(pub, this.getConnexions().map(c => c.pubkey))
       }
+      let timeout = {
+        connectionTimeout: WS2PConstants.CONNEXION_TIMEOUT,
+        requestTimeout: WS2PConstants.REQUEST_TIMEOUT
+      }
+      if (this.server.conf.ws2p && this.server.conf.ws2p.remotehost && this.server.conf.ws2p.remotehost.match(WS2PConstants.HOST_ONION_REGEX)) {
+        timeout = {
+          connectionTimeout: WS2PConstants.CONNEXION_TOR_TIMEOUT,
+          requestTimeout: WS2PConstants.REQUEST_TOR_TIMEOUT
+        }
+      }
 
       const c = WS2PConnection.newConnectionFromWebSocketServer(
         ws,
         messageHandler,
         new WS2PPubkeyLocalAuth(this.server.conf.currency, key, acceptPubkey),
         new WS2PPubkeyRemoteAuth(this.server.conf.currency, key, acceptPubkey),
-        {
-          connectionTimeout: WS2PConstants.CONNEXION_TIMEOUT,
-          requestTimeout: WS2PConstants.REQUEST_TIMEOUT
-        }
+        timeout
       )
 
       try {
diff --git a/app/modules/ws2p/lib/constants.ts b/app/modules/ws2p/lib/constants.ts
index 6a5e93f24..28df5f5fb 100644
--- a/app/modules/ws2p/lib/constants.ts
+++ b/app/modules/ws2p/lib/constants.ts
@@ -6,9 +6,10 @@ export const WS2PConstants = {
   WS2P_PORTS_END: 20999,
   WS2P_UPNP_INTERVAL: 300,
 
-  CONNEXION_TIMEOUT: 10000,
-  REQUEST_TIMEOUT: 10000,
-  PROXY_TIMEOUT: 30000,
+  CONNEXION_TIMEOUT: 15000,
+  REQUEST_TIMEOUT: 15000,
+  CONNEXION_TOR_TIMEOUT: 30000,
+  REQUEST_TOR_TIMEOUT: 30000,
   RECONNEXION_INTERVAL_IN_SEC: 60 * 10, // 10 minutes
 
   BLOCK_PULLING_INTERVAL: 300 * 2,    // 10 minutes
@@ -38,7 +39,8 @@ export const WS2PConstants = {
   + '(' + CommonConstants.FORMATS.POW_PREFIX + ')'
   + '$'),
 
-  HOST_ONION_REGEX: new RegExp('(\S*?\.onion)$'),
+  HOST_ONION_REGEX: new RegExp('^(?:www\.)?([0-9a-z]{16}\.onion)$'),
+  FULL_ADDRESS_ONION_REGEX: new RegExp('^(?:wss?:\/\/)(?:www\.)?([0-9a-z]{16}\.onion)(:[0-9]+)?(\/[-\w]*)*'),
 
   HEADS_SPREAD_TIMEOUT: 100 // Wait 100ms before sending a bunch of signed heads
 }
\ No newline at end of file
diff --git a/index.ts b/index.ts
index 70b7bd28a..f9e3cf445 100644
--- a/index.ts
+++ b/index.ts
@@ -449,10 +449,10 @@ function commandLineConf(program:any, conf:any = {}) {
       port: program.port,
     },
     proxies: {
-      proxySocks: program.proxySocks,
-      proxyTor: program.proxyTor,
-      torAlways: program.torAlways,
-      torMixed: program.torMixed,
+      proxySocks: program.socksProxy,
+      proxyTor: program.torProxy,
+      reachingClearEp: program.reachingClearEp,
+      forceTor: program.forceTor,
       rmProxies: program.rmProxies
     },
     logs: {
@@ -467,20 +467,25 @@ function commandLineConf(program:any, conf:any = {}) {
     timeout: program.timeout
   };
 
-  // Declare proxyConf
-  if (cli.proxies.proxySocks || cli.proxies.proxyTor || cli.proxies.torAlways || cli.proxies.torMixed || cli.proxies.rmProxies) {
-    conf.proxyConf = new ProxiesConf()
+  // Declare and update proxiesConf
+  if (cli.proxies.proxySocks || cli.proxies.proxyTor || cli.proxies.reachingClearEp || cli.proxies.forceTor || cli.proxies.rmProxies) {
+    conf.proxiesConf = new ProxiesConf()
+    if (cli.proxies.proxySocks) conf.proxiesConf.proxySocksAddress = cli.proxies.proxySocks;
+    if (cli.proxies.proxyTor)   conf.proxiesConf.proxyTorAddress = cli.proxies.proxyTor;
+    if (cli.proxies.reachingClearEp)  {
+      switch (cli.proxies.reachingClearEp) {
+        case 'tor': conf.proxiesConf.reachingClearEp = 'tor'; break;
+        case 'none': conf.proxiesConf.reachingClearEp = 'none'; break;
+      }
+    }
+    if (cli.proxies.forceTor) conf.proxiesConf.forceTor = true
   }
 
-  // Update conf
+  // Update the rest of the conf
   if (cli.currency)                             conf.currency = cli.currency;
   if (cli.server.port)                          conf.port = cli.server.port;
   if (cli.cpu)                                  conf.cpu = Math.max(0.01, Math.min(1.0, cli.cpu));
   if (cli.prefix)                               conf.prefix = Math.max(Constants.MIN_PEER_ID, Math.min(Constants.MAX_PEER_ID, cli.prefix));
-  if (cli.proxies.proxySocks && conf.proxyConf) conf.proxyConf.proxySocksAddress = cli.proxies.proxySocks;
-  if (cli.proxies.proxyTor && conf.proxyConf)   conf.proxyConf.proxyTorAddress = cli.proxies.proxyTor;
-  if (cli.proxies.torAlways && conf.proxyConf)  conf.proxyConf.alwaysUseTor = true;
-  if (cli.proxies.torMixed && conf.proxyConf)   conf.proxyConf.alwaysUseTor = false;
   if (cli.logs.http)                            conf.httplogs = true;
   if (cli.logs.nohttp)                          conf.httplogs = false;
   if (cli.isolate)                              conf.isolate = cli.isolate;
diff --git a/server.ts b/server.ts
index 9eca4b855..fff2e8acc 100644
--- a/server.ts
+++ b/server.ts
@@ -149,8 +149,7 @@ export class Server extends stream.Duplex implements HookableServer {
     logger.debug('Loading conf...');
     this.conf = await this.dal.loadConf(this.overrideConf, useDefaultConf)
     // Default values
-    this.conf.proxyConf        = this.conf.proxyConf === undefined ?         new ProxiesConf()                           : this.conf.proxyConf
-    this.conf.proxyConf.alwaysUseTor = this.conf.proxyConf.alwaysUseTor === undefined ? false                 : this.conf.proxyConf.alwaysUseTor
+    this.conf.proxiesConf      = this.conf.proxiesConf === undefined ?       new ProxiesConf()                            : this.conf.proxiesConf
     this.conf.remoteipv6       = this.conf.remoteipv6 === undefined ?        this.conf.ipv6                               : this.conf.remoteipv6
     this.conf.remoteport       = this.conf.remoteport === undefined ?        this.conf.port                               : this.conf.remoteport
     this.conf.c                = this.conf.c === undefined ?                 constants.CONTRACT.DEFAULT.C                 : this.conf.c
diff --git a/test/fast/proxies.ts b/test/fast/proxies.ts
index c47468092..73377bf21 100644
--- a/test/fast/proxies.ts
+++ b/test/fast/proxies.ts
@@ -13,39 +13,86 @@ describe("Proxies Conf", function() {
     // Third conf : always use tor 
     let proxiesConf3 = new ProxiesConf()
     proxiesConf3.proxyTorAddress = "127.0.0.1:9050"
-    proxiesConf3.alwaysUseTor = true
+    proxiesConf3.reachingClearEp = 'tor'
 
-    // Fourth cont : use classical socks proxy
+    // Fourth conf : use classical socks proxy
     let proxiesConf4 = new ProxiesConf()
     proxiesConf4.proxySocksAddress = "127.0.0.1:8888"
 
-    // Fifth : use classical socks proxy + use tor proxy only to reach ".onion" endpoints
+    // Fifth conf : use classical socks proxy + use tor proxy only to reach ".onion" endpoints
     let proxiesConf5 = new ProxiesConf()
     proxiesConf5.proxySocksAddress = "127.0.0.1:8888"
     proxiesConf5.proxyTorAddress = "127.0.0.1:9050"
 
+    // Sixth conf : always use tor and contact only tor endpoints
+    let proxiesConf6 = new ProxiesConf()
+    proxiesConf6.proxyTorAddress = "127.0.0.1:9050"
+    proxiesConf6.reachingClearEp = 'none'
+
+    // Seventh conf : force duniter to contact endpoint tor (if user redirect the traffic to tor himself)
+    let proxiesConf7 = new ProxiesConf()
+    proxiesConf7.forceTor = true;
+
     it('should do not use any sock proxy', () => {
-        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf1) === undefined, true)
-        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf1) === undefined, true)
+        assert.equal(ProxiesConf.canReachClearEndpoint(proxiesConf1), true)
+        assert.equal(ProxiesConf.canReachTorEndpoint(proxiesConf1), false)
+        assert.equal(ProxiesConf.httpProxy("3asufnydqmup533h.onion", proxiesConf1), undefined)
+        assert.equal(ProxiesConf.httpProxy("domain.tld", proxiesConf1), undefined)
+        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf1), undefined)
+        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf1), undefined)
     })
 
     it('should use tor proxy only to reach ".onion" endpoints', () => {
-        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf2) === proxiesConf2.proxyTorAddress, true)
-        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf2) === undefined, true)
+        assert.equal(ProxiesConf.canReachClearEndpoint(proxiesConf2), true)
+        assert.equal(ProxiesConf.canReachTorEndpoint(proxiesConf2), true)
+        assert.equal(ProxiesConf.httpProxy("3asufnydqmup533h.onion", proxiesConf2), proxiesConf2.proxyTorAddress)
+        assert.equal(ProxiesConf.httpProxy("domain.tld", proxiesConf2), undefined)
+        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf2), proxiesConf2.proxyTorAddress)
+        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf2),  undefined)
     })
 
     it('should always use tor proxy', () => {
-        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf3) === proxiesConf3.proxyTorAddress, true)
-        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf3) === proxiesConf3.proxyTorAddress, true)
+        assert.equal(ProxiesConf.canReachClearEndpoint(proxiesConf3), true)
+        assert.equal(ProxiesConf.canReachTorEndpoint(proxiesConf3), true)
+        assert.equal(ProxiesConf.httpProxy("3asufnydqmup533h.onion", proxiesConf3), proxiesConf3.proxyTorAddress)
+        assert.equal(ProxiesConf.httpProxy("domain.tld", proxiesConf3), proxiesConf3.proxyTorAddress)
+        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf3), proxiesConf3.proxyTorAddress)
+        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf3), proxiesConf3.proxyTorAddress)
     })
 
     it('should always use classical socks proxy', () => {
-        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf4) === proxiesConf4.proxySocksAddress, true)
-        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf4) === proxiesConf4.proxySocksAddress, true)
+        assert.equal(ProxiesConf.canReachClearEndpoint(proxiesConf4), true)
+        assert.equal(ProxiesConf.canReachTorEndpoint(proxiesConf4), false)
+        assert.equal(ProxiesConf.httpProxy("3asufnydqmup533h.onion", proxiesConf4), undefined)
+        assert.equal(ProxiesConf.httpProxy("domain.tld", proxiesConf4), proxiesConf4.proxySocksAddress)
+        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf4), undefined)
+        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf4), proxiesConf4.proxySocksAddress)
+    })
+
+    it('should use tor proxy for ".onion" endpoints and classical socks proxy for everyone else', () => {
+        assert.equal(ProxiesConf.canReachClearEndpoint(proxiesConf5), true)
+        assert.equal(ProxiesConf.canReachTorEndpoint(proxiesConf5), true)
+        assert.equal(ProxiesConf.httpProxy("3asufnydqmup533h.onion", proxiesConf5), proxiesConf5.proxyTorAddress)
+        assert.equal(ProxiesConf.httpProxy("domain.tld", proxiesConf5), proxiesConf5.proxySocksAddress)
+        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf5), proxiesConf5.proxyTorAddress)
+        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf5), proxiesConf5.proxySocksAddress)
+    })
+
+    it('should always use tor proxy and contact only tor endpoints', () => {
+        assert.equal(ProxiesConf.canReachClearEndpoint(proxiesConf6), false)
+        assert.equal(ProxiesConf.canReachTorEndpoint(proxiesConf6), true)
+        assert.equal(ProxiesConf.httpProxy("3asufnydqmup533h.onion", proxiesConf6), proxiesConf6.proxyTorAddress)
+        assert.equal(ProxiesConf.httpProxy("domain.tld", proxiesConf6), undefined)
+        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf6), proxiesConf6.proxyTorAddress)
+        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf6), undefined)
     })
 
-    it('should use or tor proxy for ".onion" endpoints and classical socks proxy for everyone else', () => {
-        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf5) === proxiesConf5.proxyTorAddress, true)
-        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf5) === proxiesConf5.proxySocksAddress, true)
+    it('should never use proxy and contact tor endpoints (user redirect the traffic to tor himself)', () => {
+        assert.equal(ProxiesConf.canReachClearEndpoint(proxiesConf7), true)
+        assert.equal(ProxiesConf.canReachTorEndpoint(proxiesConf7), true)
+        assert.equal(ProxiesConf.httpProxy("3asufnydqmup533h.onion", proxiesConf7), undefined)
+        assert.equal(ProxiesConf.httpProxy("domain.tld", proxiesConf7), undefined)
+        assert.equal(ProxiesConf.wsProxy("ws://3asufnydqmup533h.onion:80", proxiesConf7), undefined)
+        assert.equal(ProxiesConf.wsProxy("ws://domain.tld:20900", proxiesConf7), undefined)
     })
 });
-- 
GitLab