socks-proxy-agent.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /**
  2. * Module dependencies.
  3. */
  4. var tls; // lazy-loaded...
  5. var url = require('url');
  6. var dns = require('dns');
  7. var extend = require('extend');
  8. var Agent = require('agent-base');
  9. var SocksClient = require('socks');
  10. var inherits = require('util').inherits;
  11. /**
  12. * Module exports.
  13. */
  14. module.exports = SocksProxyAgent;
  15. /**
  16. * The `SocksProxyAgent`.
  17. *
  18. * @api public
  19. */
  20. function SocksProxyAgent (opts) {
  21. if (!(this instanceof SocksProxyAgent)) return new SocksProxyAgent(opts);
  22. if ('string' == typeof opts) opts = url.parse(opts);
  23. if (!opts) throw new Error('a SOCKS proxy server `host` and `port` must be specified!');
  24. Agent.call(this, connect);
  25. var proxy = extend({}, opts);
  26. // prefer `hostname` over `host`, because of `url.parse()`
  27. proxy.host = proxy.hostname || proxy.host;
  28. // SOCKS doesn't *technically* have a default port, but this is
  29. // the same default that `curl(1)` uses
  30. proxy.port = +proxy.port || 1080;
  31. if (proxy.host && proxy.path) {
  32. // if both a `host` and `path` are specified then it's most likely the
  33. // result of a `url.parse()` call... we need to remove the `path` portion so
  34. // that `net.connect()` doesn't attempt to open that as a unix socket file.
  35. delete proxy.path;
  36. delete proxy.pathname;
  37. }
  38. // figure out if we want socks v4 or v5, based on the "protocol" used.
  39. // Defaults to 5.
  40. proxy.lookup = false;
  41. switch (proxy.protocol) {
  42. case 'socks4:':
  43. proxy.lookup = true;
  44. // pass through
  45. case 'socks4a:':
  46. proxy.version = 4;
  47. break;
  48. case 'socks5:':
  49. proxy.lookup = true;
  50. // pass through
  51. case 'socks:': // no version specified, default to 5h
  52. case 'socks5h:':
  53. proxy.version = 5;
  54. break;
  55. default:
  56. throw new TypeError('A "socks" protocol must be specified! Got: ' + proxy.protocol);
  57. }
  58. this.proxy = proxy;
  59. }
  60. inherits(SocksProxyAgent, Agent);
  61. /**
  62. * Initiates a SOCKS connection to the specified SOCKS proxy server,
  63. * which in turn connects to the specified remote host and port.
  64. *
  65. * @api public
  66. */
  67. function connect (req, opts, fn) {
  68. var proxy = this.proxy;
  69. // called once the SOCKS proxy has connected to the specified remote endpoint
  70. function onhostconnect (err, socket) {
  71. if (err) return fn(err);
  72. var s = socket;
  73. if (opts.secureEndpoint) {
  74. // since the proxy is connecting to an SSL server, we have
  75. // to upgrade this socket connection to an SSL connection
  76. if (!tls) tls = require('tls');
  77. opts.socket = socket;
  78. opts.servername = opts.host;
  79. opts.host = null;
  80. opts.hostname = null;
  81. opts.port = null;
  82. s = tls.connect(opts);
  83. socket.resume();
  84. }
  85. fn(null, s);
  86. }
  87. // called for the `dns.lookup()` callback
  88. function onlookup (err, ip, type) {
  89. if (err) return fn(err);
  90. options.target.host = ip;
  91. SocksClient.createConnection(options, onhostconnect);
  92. }
  93. var options = {
  94. proxy: {
  95. ipaddress: proxy.host,
  96. port: +proxy.port,
  97. type: proxy.version
  98. },
  99. target: {
  100. port: +opts.port
  101. },
  102. command: 'connect'
  103. };
  104. if (proxy.lookup) {
  105. // client-side DNS resolution for "4" and "5" socks proxy versions
  106. dns.lookup(opts.host, onlookup);
  107. } else {
  108. // proxy hostname DNS resolution for "4a" and "5h" socks proxy servers
  109. options.target.host = opts.host;
  110. SocksClient.createConnection(options, onhostconnect);
  111. }
  112. }