made TCP binding and forwarding modules respond to config changes
This commit is contained in:
		
							parent
							
								
									c637671c78
								
							
						
					
					
						commit
						c132861cab
					
				@ -5,7 +5,7 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  //var PromiseA = global.Promise;
 | 
					  //var PromiseA = global.Promise;
 | 
				
			||||||
  var PromiseA = require('bluebird');
 | 
					  var PromiseA = require('bluebird');
 | 
				
			||||||
  var listeners = require('./servers').listeners;
 | 
					  var listeners = require('./servers').listeners.tcp;
 | 
				
			||||||
  var domainUtils = require('./domain-utils');
 | 
					  var domainUtils = require('./domain-utils');
 | 
				
			||||||
  var modules;
 | 
					  var modules;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,15 +31,7 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
    modules.http = require('./modules/http').create(deps, config, modules.tls.middleware);
 | 
					    modules.http = require('./modules/http').create(deps, config, modules.tls.middleware);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function checkTcpProxy(conn, opts) {
 | 
					  function proxy(mod, conn, opts) {
 | 
				
			||||||
    var proxied = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TCP Proxying (ie forwarding based on domain name not incoming port) only works for
 | 
					 | 
				
			||||||
    // TLS wrapped connections, so if the opts don't give us a servername or don't tell us
 | 
					 | 
				
			||||||
    // this is the decrypted side of a TLS connection we can't handle it here.
 | 
					 | 
				
			||||||
    if (!opts.servername || !opts.encrypted) { return proxied; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function proxy(mod) {
 | 
					 | 
				
			||||||
    // First thing we need to add to the connection options is where to proxy the connection to
 | 
					    // First thing we need to add to the connection options is where to proxy the connection to
 | 
				
			||||||
    var newConnOpts = domainUtils.separatePort(mod.address || '');
 | 
					    var newConnOpts = domainUtils.separatePort(mod.address || '');
 | 
				
			||||||
    newConnOpts.port = newConnOpts.port || mod.port;
 | 
					    newConnOpts.port = newConnOpts.port || mod.port;
 | 
				
			||||||
@ -56,6 +48,14 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function checkTcpProxy(conn, opts) {
 | 
				
			||||||
 | 
					    var proxied = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TCP Proxying (ie routing based on domain name [vs local port]) only works for
 | 
				
			||||||
 | 
					    // TLS wrapped connections, so if the opts don't give us a servername or don't tell us
 | 
				
			||||||
 | 
					    // this is the decrypted side of a TLS connection we can't handle it here.
 | 
				
			||||||
 | 
					    if (!opts.servername || !opts.encrypted) { return proxied; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    proxied = config.domains.some(function (dom) {
 | 
					    proxied = config.domains.some(function (dom) {
 | 
				
			||||||
      if (!dom.modules || !Array.isArray(dom.modules.tcp)) { return false; }
 | 
					      if (!dom.modules || !Array.isArray(dom.modules.tcp)) { return false; }
 | 
				
			||||||
      if (!nameMatchesDomains(opts.servername, dom.names)) { return false; }
 | 
					      if (!nameMatchesDomains(opts.servername, dom.names)) { return false; }
 | 
				
			||||||
@ -63,7 +63,7 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
      return dom.modules.tcp.some(function (mod) {
 | 
					      return dom.modules.tcp.some(function (mod) {
 | 
				
			||||||
        if (mod.type !== 'proxy') { return false; }
 | 
					        if (mod.type !== 'proxy') { return false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return proxy(mod);
 | 
					        return proxy(mod, conn, opts);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -71,12 +71,24 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
      if (mod.type !== 'proxy') { return false; }
 | 
					      if (mod.type !== 'proxy') { return false; }
 | 
				
			||||||
      if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; }
 | 
					      if (!nameMatchesDomains(opts.servername, mod.domains)) { return false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return proxy(mod);
 | 
					      return proxy(mod, conn, opts);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return proxied;
 | 
					    return proxied;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function checkTcpForward(conn, opts) {
 | 
				
			||||||
 | 
					    // TCP forwarding (ie routing connections based on local port) requires the local port
 | 
				
			||||||
 | 
					    if (!conn.localPort) { return false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return config.tcp.modules.some(function (mod) {
 | 
				
			||||||
 | 
					      if (mod.type !== 'forward')                { return false; }
 | 
				
			||||||
 | 
					      if (mod.ports.indexOf(conn.localPort) < 0) { return false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return proxy(mod, conn, opts);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
 | 
					  // opts = { servername, encrypted, peek, data, remoteAddress, remotePort }
 | 
				
			||||||
  function peek(conn, firstChunk, opts) {
 | 
					  function peek(conn, firstChunk, opts) {
 | 
				
			||||||
    if (!modules) {
 | 
					    if (!modules) {
 | 
				
			||||||
@ -134,6 +146,7 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
      console.log('[tcpHandler]', logName, 'connection closed', (Date.now()-start)/1000);
 | 
					      console.log('[tcpHandler]', logName, 'connection closed', (Date.now()-start)/1000);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (checkTcpForward(conn, opts)) { return; }
 | 
				
			||||||
    if (checkTcpProxy(conn, opts))   { return; }
 | 
					    if (checkTcpProxy(conn, opts))   { return; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // XXX PEEK COMMENT XXX
 | 
					    // XXX PEEK COMMENT XXX
 | 
				
			||||||
@ -159,21 +172,6 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function createTcpForwarder(mod) {
 | 
					 | 
				
			||||||
    var dest = require('./domain-utils').separatePort(mod.address || '');
 | 
					 | 
				
			||||||
    dest.port = dest.port || mod.port;
 | 
					 | 
				
			||||||
    dest.host = dest.host || mod.host || 'localhost';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return function (conn) {
 | 
					 | 
				
			||||||
      var newConnOpts = {};
 | 
					 | 
				
			||||||
      addrProperties.forEach(function (name) {
 | 
					 | 
				
			||||||
        newConnOpts['_'+name] = conn[name];
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      deps.proxy(conn, Object.assign(newConnOpts, dest));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  deps.tunnel = deps.tunnel || {};
 | 
					  deps.tunnel = deps.tunnel || {};
 | 
				
			||||||
  deps.tunnel.net = {
 | 
					  deps.tunnel.net = {
 | 
				
			||||||
    createConnection: function (opts, cb) {
 | 
					    createConnection: function (opts, cb) {
 | 
				
			||||||
@ -240,37 +238,76 @@ module.exports.create = function (deps, config) {
 | 
				
			|||||||
  deps.tunnelClients = require('./tunnel-client-manager').create(deps, config);
 | 
					  deps.tunnelClients = require('./tunnel-client-manager').create(deps, config);
 | 
				
			||||||
  deps.tunnelServer = require('./tunnel-server-manager').create(deps, config);
 | 
					  deps.tunnelServer = require('./tunnel-server-manager').create(deps, config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var listenPromises = [];
 | 
					  function updateListeners() {
 | 
				
			||||||
  var tcpPortMap = {};
 | 
					    var current = listeners.list();
 | 
				
			||||||
  config.tcp.bind.filter(Number).forEach(function (port) {
 | 
					    var wanted = config.tcp.bind;
 | 
				
			||||||
    tcpPortMap[port] = true;
 | 
					
 | 
				
			||||||
 | 
					    if (!Array.isArray(wanted)) { wanted = []; }
 | 
				
			||||||
 | 
					    wanted = wanted.map(Number).filter((port) => port > 0 && port < 65356);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var closeProms = current.filter(function (port) {
 | 
				
			||||||
 | 
					      return wanted.indexOf(port) < 0;
 | 
				
			||||||
 | 
					    }).map(function (port) {
 | 
				
			||||||
 | 
					      return listeners.close(port, 1000);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  (config.tcp.modules || []).forEach(function (mod) {
 | 
					    // We don't really need to filter here since listening on the same port with the
 | 
				
			||||||
    if (mod.type === 'forward') {
 | 
					    // same handler function twice is basically a no-op.
 | 
				
			||||||
      var forwarder = createTcpForwarder(mod);
 | 
					    var openProms = wanted.map(function (port) {
 | 
				
			||||||
 | 
					      return listeners.add(port, tcpHandler);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Promise.all(closeProms.concat(openProms));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var mainPort;
 | 
				
			||||||
 | 
					  function updateConf() {
 | 
				
			||||||
 | 
					    updateListeners().catch(function (err) {
 | 
				
			||||||
 | 
					      console.error('Error updating TCP listeners to match bind configuration');
 | 
				
			||||||
 | 
					      console.error(err);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var unforwarded = {};
 | 
				
			||||||
 | 
					    config.tcp.bind.forEach(function (port) {
 | 
				
			||||||
 | 
					      unforwarded[port] = true;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    config.tcp.modules.forEach(function (mod) {
 | 
				
			||||||
 | 
					      if (['forward', 'proxy'].indexOf(mod.type) < 0) {
 | 
				
			||||||
 | 
					        console.warn('unknown TCP module type specified', JSON.stringify(mod));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (mod.type !== 'forward') { return; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      mod.ports.forEach(function (port) {
 | 
					      mod.ports.forEach(function (port) {
 | 
				
			||||||
        if (!tcpPortMap[port]) {
 | 
					        if (!unforwarded[port]) {
 | 
				
			||||||
          console.log("forwarding port", port, "that wasn't specified in bind");
 | 
					          console.warn('trying to forward TCP port ' + port + ' multiple times or it is unbound');
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          delete tcpPortMap[port];
 | 
					          delete unforwarded[port];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        listenPromises.push(listeners.tcp.add(port, forwarder));
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if (mod.type !== 'proxy') {
 | 
					 | 
				
			||||||
      console.warn('unknown TCP module specified', mod);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var portList = Object.keys(tcpPortMap).map(Number).sort();
 | 
					    // Not really sure what we can reasonably do to prevent this. At least not without making
 | 
				
			||||||
  portList.forEach(function (port) {
 | 
					    // our configuration validation more complicated.
 | 
				
			||||||
    listenPromises.push(listeners.tcp.add(port, tcpHandler));
 | 
					    if (!Object.keys(unforwarded).length) {
 | 
				
			||||||
  });
 | 
					      console.warn('no bound TCP ports are not being forwarded, admin interface will be inaccessible');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If we are listening on port 443 make that the main port we respond to mDNS queries with
 | 
				
			||||||
 | 
					    // otherwise choose the lowest number port we are bound to but not forwarding.
 | 
				
			||||||
 | 
					    if (unforwarded['443']) {
 | 
				
			||||||
 | 
					      mainPort = 443;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      mainPort = Object.keys(unforwarded).map(Number).sort((a, b) => a - b)[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateConf();
 | 
				
			||||||
  if (!config.mdns.disabled) {
 | 
					  if (!config.mdns.disabled) {
 | 
				
			||||||
    require('./mdns').start(deps, config, portList[0]);
 | 
					    require('./mdns').start(deps, config, mainPort);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return PromiseA.all(listenPromises);
 | 
					  return {
 | 
				
			||||||
 | 
					    updateConf
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -10,20 +10,16 @@ module.exports.addTcpListener = function (port, handler) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (stat) {
 | 
					    if (stat) {
 | 
				
			||||||
      if (stat._closing) {
 | 
					      if (stat._closing) {
 | 
				
			||||||
        module.exports.destroyTcpListener(port);
 | 
					        stat.server.destroy();
 | 
				
			||||||
      }
 | 
					      } else {
 | 
				
			||||||
      else if (handler !== stat.handler) {
 | 
					        // We're already listening on the port, so we only have 2 options. We can either
 | 
				
			||||||
 | 
					        // replace the handler or reject with an error. (Though neither is really needed
 | 
				
			||||||
        // we'll replace the current listener
 | 
					        // if the handlers are the same). Until there is reason to do otherwise we are
 | 
				
			||||||
 | 
					        // opting for the replacement.
 | 
				
			||||||
        stat.handler = handler;
 | 
					        stat.handler = handler;
 | 
				
			||||||
        resolve();
 | 
					        resolve();
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        // this exact listener is already open
 | 
					 | 
				
			||||||
        resolve();
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var enableDestroy = require('server-destroy');
 | 
					    var enableDestroy = require('server-destroy');
 | 
				
			||||||
@ -34,7 +30,7 @@ module.exports.addTcpListener = function (port, handler) {
 | 
				
			|||||||
    stat = serversMap[port] = {
 | 
					    stat = serversMap[port] = {
 | 
				
			||||||
      server: server
 | 
					      server: server
 | 
				
			||||||
    , handler: handler
 | 
					    , handler: handler
 | 
				
			||||||
    , _closing: null
 | 
					    , _closing: false
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add .destroy so we can close all open connections. Better if added before listen
 | 
					    // Add .destroy so we can close all open connections. Better if added before listen
 | 
				
			||||||
@ -66,14 +62,24 @@ module.exports.addTcpListener = function (port, handler) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
module.exports.closeTcpListener = function (port) {
 | 
					module.exports.closeTcpListener = function (port, timeout) {
 | 
				
			||||||
  return new PromiseA(function (resolve) {
 | 
					  return new PromiseA(function (resolve) {
 | 
				
			||||||
    var stat = serversMap[port];
 | 
					    var stat = serversMap[port];
 | 
				
			||||||
    if (!stat) {
 | 
					    if (!stat) {
 | 
				
			||||||
      resolve();
 | 
					      resolve();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    stat.server.once('close', resolve);
 | 
					    stat._closing = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var timeoutId;
 | 
				
			||||||
 | 
					    if (timeout) {
 | 
				
			||||||
 | 
					      timeoutId = setTimeout(() => stat.server.destroy(), timeout);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stat.server.once('close', function () {
 | 
				
			||||||
 | 
					      clearTimeout(timeoutId);
 | 
				
			||||||
 | 
					      resolve();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    stat.server.close();
 | 
					    stat.server.close();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -84,7 +90,9 @@ module.exports.destroyTcpListener = function (port) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
module.exports.listTcpListeners = function () {
 | 
					module.exports.listTcpListeners = function () {
 | 
				
			||||||
  return Object.keys(serversMap).map(Number).filter(Boolean);
 | 
					  return Object.keys(serversMap).map(Number).filter(function (port) {
 | 
				
			||||||
 | 
					    return port && !serversMap[port]._closing;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -52,10 +52,10 @@ function create(conf) {
 | 
				
			|||||||
  , socks5:   require('./socks5-server').create(deps, conf)
 | 
					  , socks5:   require('./socks5-server').create(deps, conf)
 | 
				
			||||||
  , ddns:     require('./ddns').create(deps, conf)
 | 
					  , ddns:     require('./ddns').create(deps, conf)
 | 
				
			||||||
  , udp:      require('./udp').create(deps, conf)
 | 
					  , udp:      require('./udp').create(deps, conf)
 | 
				
			||||||
 | 
					  , tcp:      require('./goldilocks').create(deps, conf)
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  Object.assign(deps, modules);
 | 
					  Object.assign(deps, modules);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  require('./goldilocks.js').create(deps, conf);
 | 
					 | 
				
			||||||
  process.removeListener('message', create);
 | 
					  process.removeListener('message', create);
 | 
				
			||||||
  process.on('message', update);
 | 
					  process.on('message', update);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user