Comparing version 2.0.3 to 2.0.4
@@ -28,9 +28,12 @@ "use strict"; | ||
/** | ||
* Creates a | ||
* @param options | ||
* @param callback | ||
* Creates a new SOCKS connection. | ||
* | ||
* Note: Supports callbacks and promises. Only supports the connect command. | ||
* @param options { SocksClientOptions } Options. | ||
* @param callback { Function } An optional callback function. | ||
* @returns { Promise } | ||
*/ | ||
static createConnection(options, callback) { | ||
// Validate SocksClientOptions | ||
helpers_1.validateSocksClientOptions(options); | ||
helpers_1.validateSocksClientOptions(options, ['connect']); | ||
return new Promise((resolve, reject) => { | ||
@@ -40,4 +43,6 @@ const client = new SocksClient(options); | ||
client.once('established', (info) => { | ||
client.removeAllListeners(); | ||
if (typeof callback === 'function') { | ||
callback(null, info); | ||
resolve(); // Resolves pending promise (prevents memory leaks). | ||
} | ||
@@ -48,5 +53,8 @@ else { | ||
}); | ||
// Error occurred, failed to establish connection. | ||
client.once('error', (err) => { | ||
client.removeAllListeners(); | ||
if (typeof callback === 'function') { | ||
callback(err); | ||
resolve(); // Resolves pending promise (prevents memory leaks). | ||
} | ||
@@ -57,21 +65,62 @@ else { | ||
}); | ||
client.once('close', () => { | ||
if (typeof callback === 'function') { | ||
callback(); | ||
} | ||
else { | ||
reject(); | ||
} | ||
}); | ||
}); | ||
} | ||
// todo test this, test error handling reject() ? | ||
/** | ||
* Creates a new SOCKS connection chain to a destination host through 2 or more SOCKS proxies. | ||
* | ||
* Note: Supports callbacks and promises. Only supports the connect method. | ||
* Note: Implemented via createConnection() factory function. | ||
* @param options { SocksClientChainOptions } Options | ||
* @param callback { Function } An optional callback function. | ||
* @returns { Promise } | ||
*/ | ||
static createConnectionChain(options, callback) { | ||
// Validate SocksClientChainOptions | ||
helpers_1.validateSocksClientChainOptions(options); | ||
// Shuffle proxies | ||
if (options.randomizeChain) { | ||
util_1.shuffleArray(options.proxies); | ||
} | ||
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
if (options.randomizeChain) { | ||
util_1.shuffleArray(options.proxies); | ||
let sock; | ||
try { | ||
for (let i = 0; i < options.proxies.length; i++) { | ||
const nextProxy = options.proxies[i]; | ||
// If we've reached the last proxy in the chain, the destination is the actual destination, otherwise it's the next proxy. | ||
const nextDestination = i === options.proxies.length - 1 | ||
? options.destination | ||
: { | ||
host: options.proxies[i + 1].ipaddress, | ||
port: options.proxies[i + 1].port | ||
}; | ||
console.log(nextProxy, '->', nextDestination); | ||
// Creates the next connection in the chain. | ||
const result = yield SocksClient.createConnection({ | ||
command: 'connect', | ||
proxy: nextProxy, | ||
destination: nextDestination | ||
// Initial connection ignores this as sock is undefined. Subsequent connections re-use the first proxy socket to form a chain. | ||
}); | ||
// If sock is undefined, assign it here. | ||
if (!sock) { | ||
sock = result.socket; | ||
} | ||
} | ||
if (typeof callback === 'function') { | ||
callback(null, { socket: sock }); | ||
resolve(); // Resolves pending promise (prevents memory leaks). | ||
} | ||
else { | ||
resolve({ socket: sock }); | ||
} | ||
} | ||
// todo | ||
catch (err) { | ||
if (typeof callback === 'function') { | ||
callback(err); | ||
resolve(); // Resolves pending promise (prevents memory leaks). | ||
} | ||
else { | ||
reject(err); | ||
} | ||
} | ||
})); | ||
@@ -143,7 +192,6 @@ } | ||
/** | ||
* Internal state setter. If the SocksClient is in a closed or error state, it cannot be changed to a non error state. | ||
* Internal state setter. If the SocksClient is in an error state, it cannot be changed to a non error state. | ||
*/ | ||
set state(newState) { | ||
if (this._state !== constants_1.SocksClientState.Closed && | ||
this._state !== constants_1.SocksClientState.Error) { | ||
if (this._state !== constants_1.SocksClientState.Error) { | ||
this._state = newState; | ||
@@ -154,3 +202,3 @@ } | ||
* Starts the connection establishment to the proxy and destination. | ||
* @param existing_socket Connected socket to use instead of creating a new one (iternal use). | ||
* @param existing_socket Connected socket to use instead of creating a new one (internal use). | ||
*/ | ||
@@ -193,3 +241,3 @@ connect(existing_socket) { | ||
this.state !== constants_1.SocksClientState.BoundWaitingForConnection) { | ||
this._closeSocket('Proxy connection timed out'); | ||
this._closeSocket(constants_1.ERRORS.ProxyConnectionTimedOut); | ||
} | ||
@@ -228,3 +276,3 @@ } | ||
} | ||
else if (this.state === constants_1.SocksClientState.SentAuthenication) { | ||
else if (this.state === constants_1.SocksClientState.SentAuthentication) { | ||
this.handleInitialSocks5AuthenticationHandshakeResponse(data); | ||
@@ -246,3 +294,3 @@ // Sent final Socks v5 handshake, waiting for final response. | ||
else { | ||
this._closeSocket('SocksClient is in a invalid internal state.'); | ||
this._closeSocket(constants_1.ERRORS.InternalError); | ||
} | ||
@@ -255,3 +303,3 @@ } | ||
onClose(had_error) { | ||
this._closeSocket(); | ||
this._closeSocket(constants_1.ERRORS.SocketClosed); | ||
} | ||
@@ -263,3 +311,3 @@ /** | ||
onError(err) { | ||
this._closeSocket(err); | ||
this._closeSocket(err.message); | ||
} | ||
@@ -276,30 +324,18 @@ /** | ||
/** | ||
* Closes and destroys the underlying Socket. | ||
* | ||
* Note: Either only the 'close' or 'error' event will be emitted in a SocksClient lifetime. Never both. | ||
* @param err Optional error message to include in error event. | ||
* Closes and destroys the underlying Socket. Emits an error event. | ||
* @param err { String } An error string to include in error event. | ||
*/ | ||
_closeSocket(err) { | ||
// Make sure only one of 'close' and 'error' are fired for the lifetime of this SocksClient instance. | ||
if (this.state !== constants_1.SocksClientState.Error && | ||
this.state !== constants_1.SocksClientState.Closed) { | ||
// Make sure only one 'error' event is fired for the lifetime of this SocksClient instance. | ||
if (this.state !== constants_1.SocksClientState.Error) { | ||
// Set internal state to Error. | ||
this.state = constants_1.SocksClientState.Error; | ||
// Destroy Socket | ||
if (!this._socket.destroyed) { | ||
this._socket.destroy(err); | ||
this._socket.destroy(); | ||
} | ||
// Remove internal listeners | ||
this.removeInternalSocketHandlers(); | ||
if (err) { | ||
this.state = constants_1.SocksClientState.Error; | ||
if (err instanceof Error) { | ||
this.emit('error', new util_1.SocksClientError(err.message, this._options)); | ||
} | ||
else { | ||
this.emit('error', new util_1.SocksClientError(err, this._options)); | ||
} | ||
} | ||
else { | ||
this.state = constants_1.SocksClientState.Closed; | ||
this.emit('close'); | ||
} | ||
// Fire 'error' event. | ||
this.emit('error', new util_1.SocksClientError(err, this._options)); | ||
} | ||
@@ -338,6 +374,7 @@ } | ||
if (data.length < 8) { | ||
this._closeSocket('Received invalid Socks4 handshake response'); | ||
// 8 is required | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks4HandshakeResponse); | ||
} | ||
else if (data[1] !== constants_1.Socks4Response.Granted) { | ||
this._closeSocket(`Server rejected connection (${constants_1.Socks4Response[data[1]]})`); | ||
this._closeSocket(`${constants_1.ERRORS.Socks4ProxyRejectedConnection} - (${constants_1.Socks4Response[data[1]]})`); | ||
} | ||
@@ -349,3 +386,3 @@ else { | ||
buff.readOffset = 2; | ||
const remoteHostInfo = { | ||
const remoteHost = { | ||
port: buff.readUInt16BE(), | ||
@@ -355,7 +392,7 @@ host: ip.fromLong(buff.readUInt32BE()) | ||
// If host is 0.0.0.0, set to proxy host. | ||
if (remoteHostInfo.host === '0.0.0.0') { | ||
remoteHostInfo.host = this._options.proxy.ipaddress; | ||
if (remoteHost.host === '0.0.0.0') { | ||
remoteHost.host = this._options.proxy.ipaddress; | ||
} | ||
this.state = constants_1.SocksClientState.BoundWaitingForConnection; | ||
this.emit('bound', { socket: this._socket, remoteHostInfo }); | ||
this.emit('bound', { socket: this._socket, remoteHost }); | ||
// Connect response | ||
@@ -365,3 +402,2 @@ } | ||
this.state = constants_1.SocksClientState.Established; | ||
this._socket.pause(); | ||
this.emit('established', { socket: this._socket }); | ||
@@ -377,6 +413,7 @@ } | ||
if (data.length < 8) { | ||
this._closeSocket('Received invalid incoming connection response'); | ||
// 8 is required. | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks4IncomingConnectionResponse); | ||
} | ||
else if (data[1] !== constants_1.Socks4Response.Granted) { | ||
this._closeSocket(`Server rejected incoming bound connection (${constants_1.Socks4Response[data[1]]})`); | ||
this._closeSocket(`${constants_1.ERRORS.Socks4ProxyRejectedIncomingBoundConnection} - (${constants_1.Socks4Response[data[1]]})`); | ||
} | ||
@@ -386,3 +423,3 @@ else { | ||
buff.readOffset = 2; | ||
const remoteHostInfo = { | ||
const remoteHost = { | ||
port: buff.readUInt16BE(), | ||
@@ -392,4 +429,3 @@ host: ip.fromLong(buff.readUInt32BE()) | ||
this.state = constants_1.SocksClientState.Established; | ||
this._socket.pause(); | ||
this.emit('established', { socket: this._socket, remoteHostInfo }); | ||
this.emit('established', { socket: this._socket, remoteHost }); | ||
} | ||
@@ -415,9 +451,10 @@ } | ||
if (data.length !== 2) { | ||
this._closeSocket('Negotiation Error'); | ||
// 2 is required | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks5InitialHandshakeResponse); | ||
} | ||
else if (data[0] !== 0x05) { | ||
this._closeSocket('Negotiation Error (invalid socks version)'); | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks5IntiailHandshakeSocksVersion); | ||
} | ||
else if (data[1] === 0xff) { | ||
this._closeSocket('Negotiation Error (no accepted authentication type)'); | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks5InitialHandshakeNoAcceptedAuthType); | ||
} | ||
@@ -434,3 +471,3 @@ else { | ||
else { | ||
this._closeSocket('Negotiation Error (unknown authentication type)'); | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks5InitialHandshakeUnknownAuthType); | ||
} | ||
@@ -454,3 +491,3 @@ } | ||
this._socket.write(buff.toBuffer()); | ||
this.state = constants_1.SocksClientState.SentAuthenication; | ||
this.state = constants_1.SocksClientState.SentAuthentication; | ||
} | ||
@@ -463,7 +500,7 @@ /** | ||
this.state = constants_1.SocksClientState.ReceivedAuthenticationResponse; | ||
if (data.length === 2 && data[1] === 0x00) { | ||
this.sendSocks5CommandRequest(); | ||
if (data.length !== 2 || data[1] !== 0x00) { | ||
this._closeSocket(constants_1.ERRORS.Socks5AuthenticationFailed); | ||
} | ||
else { | ||
this._closeSocket('Negotiation Error (authentication failed)'); | ||
this.sendSocks5CommandRequest(); | ||
} | ||
@@ -502,7 +539,8 @@ } | ||
handleSocks5FinalHandshakeResponse(data) { | ||
if (data.length < 4) { | ||
this._closeSocket('Negotiation Error'); | ||
if (data.length < 5) { | ||
// 4 is required to get address type, 5 is hostname length and should be there anyways. | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks5FinalHandshake); | ||
} | ||
else if (data[0] !== 0x05 || data[1] !== constants_1.Socks5Response.Granted) { | ||
this._closeSocket('Negotiation Error'); | ||
this._closeSocket(`${constants_1.ERRORS.InvalidSocks5FinalHandshakeRejected} - ${constants_1.Socks5Response[data[1]]}`); | ||
} | ||
@@ -520,6 +558,10 @@ else { | ||
const addressType = buff.readUInt8(); | ||
let remoteHostInfo; | ||
let remoteHost; | ||
// IPv4 | ||
if (addressType === constants_1.Socks5HostType.IPv4) { | ||
remoteHostInfo = { | ||
// Check if data is available. | ||
if (data.length < 10) { | ||
return this._closeSocket(constants_1.ERRORS.InvalidSocks5FinalHandshake); | ||
} | ||
remoteHost = { | ||
host: ip.fromLong(buff.readUInt32BE()), | ||
@@ -529,4 +571,4 @@ port: buff.readUInt16BE() | ||
// If given host is 0.0.0.0, assume remote proxy ip instead. | ||
if (remoteHostInfo.host === '0.0.0.0') { | ||
remoteHostInfo.host = this._options.proxy.ipaddress; | ||
if (remoteHost.host === '0.0.0.0') { | ||
remoteHost.host = this._options.proxy.ipaddress; | ||
} | ||
@@ -537,3 +579,7 @@ // Hostname | ||
const hostLength = buff.readUInt8(); | ||
remoteHostInfo = { | ||
// Check if data is available. | ||
if (buff.length - 5 < hostLength) { | ||
return this._closeSocket(constants_1.ERRORS.InvalidSocks5FinalHandshake); | ||
} | ||
remoteHost = { | ||
host: buff.readString(hostLength), | ||
@@ -545,3 +591,7 @@ port: buff.readUInt16BE() | ||
else if (addressType === constants_1.Socks5HostType.IPv6) { | ||
remoteHostInfo = { | ||
// Check if data is available. | ||
if (buff.length < 24) { | ||
return this._closeSocket(constants_1.ERRORS.InvalidSocks5FinalHandshake); | ||
} | ||
remoteHost = { | ||
host: ip.toString(buff.readBuffer(16)), | ||
@@ -555,3 +605,3 @@ port: buff.readUInt16BE() | ||
this.state = constants_1.SocksClientState.BoundWaitingForConnection; | ||
this.emit('bound', { socket: this._socket, remoteHostInfo }); | ||
this.emit('bound', { socket: this._socket, remoteHost }); | ||
/* | ||
@@ -564,3 +614,3 @@ If using Associate, the Socks client is now Established. And the proxy server is now accepting UDP packets at the | ||
this.state = constants_1.SocksClientState.Established; | ||
this.emit('established', { socket: this._socket, remoteHostInfo }); | ||
this.emit('established', { socket: this._socket, remoteHost }); | ||
} | ||
@@ -576,9 +626,8 @@ } | ||
if (data.length < 4) { | ||
this._closeSocket('Negotiation Error'); | ||
this._closeSocket(constants_1.ERRORS.InvalidSocks5IncomingConnectionResponse); | ||
} | ||
else if (data[0] !== 0x05 || data[1] !== constants_1.Socks5Response.Granted) { | ||
this._closeSocket('Negotiation Error'); | ||
this._closeSocket(`${constants_1.ERRORS.Socks5ProxyRejectedIncomingBoundConnection} - ${constants_1.Socks5Response[data[1]]}`); | ||
} | ||
else { | ||
// <Buffer 05 00 00 01 68 ec d8 de 8b ac> | ||
// Read address type | ||
@@ -588,11 +637,16 @@ const buff = smart_buffer_1.SmartBuffer.fromBuffer(data); | ||
const addressType = buff.readUInt8(); | ||
let remoteHostInfo; | ||
let remoteHost; | ||
// IPv4 | ||
if (addressType === constants_1.Socks5HostType.IPv4) { | ||
remoteHostInfo = { | ||
// Check if data is available. | ||
if (data.length < 10) { | ||
return this._closeSocket(constants_1.ERRORS.InvalidSocks5IncomingConnectionResponse); | ||
} | ||
remoteHost = { | ||
host: ip.fromLong(buff.readUInt32BE()), | ||
port: buff.readUInt16BE() | ||
}; | ||
if (remoteHostInfo.host === '0.0.0.0') { | ||
remoteHostInfo.host = this._options.proxy.ipaddress; | ||
// If given host is 0.0.0.0, assume remote proxy ip instead. | ||
if (remoteHost.host === '0.0.0.0') { | ||
remoteHost.host = this._options.proxy.ipaddress; | ||
} | ||
@@ -603,3 +657,7 @@ // Hostname | ||
const hostLength = buff.readUInt8(); | ||
remoteHostInfo = { | ||
// Check if data is available. | ||
if (buff.length - 5 < hostLength) { | ||
return this._closeSocket(constants_1.ERRORS.InvalidSocks5IncomingConnectionResponse); | ||
} | ||
remoteHost = { | ||
host: buff.readString(hostLength), | ||
@@ -611,3 +669,7 @@ port: buff.readUInt16BE() | ||
else if (addressType === constants_1.Socks5HostType.IPv6) { | ||
remoteHostInfo = { | ||
// Check if data is available. | ||
if (buff.length < 24) { | ||
return this._closeSocket(constants_1.ERRORS.InvalidSocks5IncomingConnectionResponse); | ||
} | ||
remoteHost = { | ||
host: ip.toString(buff.readBuffer(16)), | ||
@@ -618,3 +680,3 @@ port: buff.readUInt16BE() | ||
this.state = constants_1.SocksClientState.Established; | ||
this.emit('established', { socket: this._socket, remoteHostInfo }); | ||
this.emit('established', { socket: this._socket, remoteHost }); | ||
} | ||
@@ -621,0 +683,0 @@ } |
@@ -8,2 +8,3 @@ "use strict"; | ||
InvalidSocksCommand: 'An invalid SOCKS command was provided. Valid options are connect, bind, and associate.', | ||
InvalidSocksCommandForOperation: 'An invalid SOCKS command was provided. Only a subset of commands are supported for this operation.', | ||
InvalidSocksCommandChain: 'An invalid SOCKS command was provided. Chaining currently only supports the connect command.', | ||
@@ -14,3 +15,20 @@ InvalidSocksClientOptionsDestination: 'An invalid destination host was provided.', | ||
InvalidSocksClientOptionsTimeout: 'An invalid timeout value was provided. Please enter a value above 0 (in ms).', | ||
InvalidSocksClientOptionsProxiesLength: 'At least two socks proxies must be provided for chaining.' | ||
InvalidSocksClientOptionsProxiesLength: 'At least two socks proxies must be provided for chaining.', | ||
NegotiationError: 'Negotiation error', | ||
SocketClosed: 'Socket closed', | ||
ProxyConnectionTimedOut: 'Proxy connection timed out', | ||
InternalError: 'SocksClient internal error (this should not happen)', | ||
InvalidSocks4HandshakeResponse: 'Received invalid Socks4 handshake response', | ||
Socks4ProxyRejectedConnection: 'Socks4 Proxy rejected connection', | ||
InvalidSocks4IncomingConnectionResponse: 'Socks4 invalid incoming connection response', | ||
Socks4ProxyRejectedIncomingBoundConnection: 'Socks4 Proxy rejected incoming bound connection', | ||
InvalidSocks5InitialHandshakeResponse: 'Received invalid Socks5 initial handshake response', | ||
InvalidSocks5IntiailHandshakeSocksVersion: 'Received invalid Socks5 initial handshake (invalid socks version)', | ||
InvalidSocks5InitialHandshakeNoAcceptedAuthType: 'Received invalid Socks5 initial handshake (no accepted authentication type)', | ||
InvalidSocks5InitialHandshakeUnknownAuthType: 'Received invalid Socks5 initial handshake (unknown authentication type)', | ||
Socks5AuthenticationFailed: 'Socks5 Authentication failed', | ||
InvalidSocks5FinalHandshake: 'Received invalid Socks5 final handshake response', | ||
InvalidSocks5FinalHandshakeRejected: 'Socks5 proxy rejected connection', | ||
InvalidSocks5IncomingConnectionResponse: 'Received invalid Socks5 incoming connection response', | ||
Socks5ProxyRejectedIncomingBoundConnection: 'Socks5 Proxy rejected incoming bound connection', | ||
}; | ||
@@ -67,3 +85,3 @@ exports.ERRORS = ERRORS; | ||
SocksClientState[SocksClientState["ReceivedInitialHandshakeResponse"] = 4] = "ReceivedInitialHandshakeResponse"; | ||
SocksClientState[SocksClientState["SentAuthenication"] = 5] = "SentAuthenication"; | ||
SocksClientState[SocksClientState["SentAuthentication"] = 5] = "SentAuthentication"; | ||
SocksClientState[SocksClientState["ReceivedAuthenticationResponse"] = 6] = "ReceivedAuthenticationResponse"; | ||
@@ -76,5 +94,4 @@ SocksClientState[SocksClientState["SentFinalHandshake"] = 7] = "SentFinalHandshake"; | ||
SocksClientState[SocksClientState["Error"] = 99] = "Error"; | ||
SocksClientState[SocksClientState["Closed"] = 100] = "Closed"; | ||
})(SocksClientState || (SocksClientState = {})); | ||
exports.SocksClientState = SocksClientState; | ||
//# sourceMappingURL=constants.js.map |
@@ -6,17 +6,8 @@ "use strict"; | ||
const net = require("net"); | ||
function normalizeSocksClientOptions(options) { | ||
options.proxy.userId = options.proxy.userId || ''; | ||
options.proxy.password = options.proxy.password || ''; | ||
} | ||
function normalizeSocksClientChainOptions(options) { | ||
options.proxies.forEach(proxy => { | ||
proxy.userId = proxy.userId || ''; | ||
proxy.password = proxy.password || ''; | ||
}); | ||
} | ||
/** | ||
* Validates the provided SocksClientOptions | ||
* @param options { SocksClientOptions } | ||
* @param acceptedCommands { string[] } A list of accepted SocksProxy commands. | ||
*/ | ||
function validateSocksClientOptions(options) { | ||
function validateSocksClientOptions(options, acceptedCommands = ['connect', 'bind', 'associate']) { | ||
// Check SOCKs command option. | ||
@@ -26,2 +17,6 @@ if (!constants_1.SocksCommand[options.command]) { | ||
} | ||
// Check SocksCommand for acceptable command. | ||
if (acceptedCommands.indexOf(options.command) === -1) { | ||
throw new util_1.SocksClientError(constants_1.ERRORS.InvalidSocksCommandForOperation, options); | ||
} | ||
// Check destination | ||
@@ -60,3 +55,5 @@ if (!isValidSocksRemoteHost(options.destination)) { | ||
// Validate proxies (length) | ||
if (!(options.proxies && Array.isArray(options.proxies) && options.proxies.length >= 2)) { | ||
if (!(options.proxies && | ||
Array.isArray(options.proxies) && | ||
options.proxies.length >= 2)) { | ||
throw new util_1.SocksClientError(constants_1.ERRORS.InvalidSocksClientOptionsProxiesLength, options); | ||
@@ -63,0 +60,0 @@ } |
{ | ||
"name": "socks", | ||
"private": false, | ||
"version": "2.0.3", | ||
"version": "2.0.4", | ||
"description": "Fully featured SOCKS proxy client supporting SOCKSv4, SOCKSv4a, and SOCKSv5. Includes Bind and Associate functionality.", | ||
@@ -24,3 +24,3 @@ "main": "build/index.js", | ||
"engines": { | ||
"node": ">= 4.0.0", | ||
"node": ">= 6.0.0", | ||
"npm": ">= 3.0.0" | ||
@@ -37,4 +37,6 @@ }, | ||
"chai": "^4.1.2", | ||
"coveralls": "^3.0.0", | ||
"mocha": "^4.0.1", | ||
"nyc": "11.4.0", | ||
"prettier": "^1.9.2", | ||
"socks5-server": "^0.1.1", | ||
@@ -55,3 +57,3 @@ "ts-node": "^3.3.0", | ||
"lint": "tslint --project tsconfig.json 'src/**/*.ts'", | ||
"build": "tsc -p ./" | ||
"build": "tslint --project tsconfig.json && prettier --write ./src/**/*.ts --config .prettierrc.yaml && tsc -p ." | ||
}, | ||
@@ -58,0 +60,0 @@ "nyc": { |
770
README.md
@@ -1,16 +0,21 @@ | ||
socks | ||
============= | ||
# socks [![Build Status](https://travis-ci.org/JoshGlazebrook/socks.svg?branch=master)](https://travis-ci.org/JoshGlazebrook/socks) [![Coverage Status](https://coveralls.io/repos/github/JoshGlazebrook/socks/badge.svg?branch=v2)](https://coveralls.io/github/JoshGlazebrook/socks?branch=v2) | ||
Fully featured SOCKS proxy client supporting SOCKSv4, SOCKSv4a, and SOCKSv5. Includes Bind and Associate functionality. | ||
### Highlights | ||
### Features | ||
* Supports SOCKS 4, 4a, and 5 protocols. | ||
* Supports SOCKS v4, v4a, and v5 protocols. | ||
* Supports the CONNECT, BIND, and ASSOCIATE commands. | ||
* Supports callbacks, promises, and events for proxy connection creation flows. | ||
* Supports Proxy chaining (CONNECT only). | ||
* Built in UDP frame creationg & parsing methods. | ||
* Built in TypeScript, typings are provided. | ||
* Supports callbacks, promises, and events for proxy connection creation async flow control. | ||
* Supports proxy chaining (CONNECT only). | ||
* Supports user/pass authentication. | ||
* Built in UDP frame creation & parse functions. | ||
* Created with TypeScript, type definitions are provided. | ||
## Installing: | ||
### Requirements | ||
* Node.js v6.0+ (Please use [v1](https://github.com/JoshGlazebrook/socks/tree/82d83923ad960693d8b774cafe17443ded7ed584) for older versions of Node.js) | ||
## Installation | ||
`yarn add socks` | ||
@@ -20,315 +25,633 @@ | ||
`npm install socks` | ||
`npm install --save socks` | ||
### Getting Started Example | ||
## Usage | ||
For this example, say you wanted to grab the html of google's home page. | ||
```typescript | ||
// TypeScript | ||
import { SocksClient, SocksClientOptions, SocksClientChainOptions } from 'socks'; | ||
// ES6 JavaScript | ||
import { SocksClient } from 'socks'; | ||
// Legacy JavaScript | ||
const SocksClient = require('socks').SocksClient; | ||
``` | ||
## Quick Start Example | ||
Connect to github.com (192.30.253.113) on port 80, using a SOCKS proxy. | ||
```javascript | ||
var Socks = require('socks'); | ||
const options = { | ||
proxy: { | ||
ipaddress: '159.203.75.200', | ||
port: 1080, | ||
type: 5 // Proxy version (4 or 5) | ||
}, | ||
var options = { | ||
proxy: { | ||
ipaddress: "202.101.228.108", // Random public proxy | ||
port: 1080, | ||
type: 5 // type is REQUIRED. Valid types: [4, 5] (note 4 also works for 4a) | ||
}, | ||
target: { | ||
host: "google.com", // can be an ip address or domain (4a and 5 only) | ||
port: 80 | ||
}, | ||
command: 'connect' // This defaults to connect, so it's optional if you're not using BIND or Associate. | ||
command: 'connect', // SOCKS command (createConnection factory function only supports the connect command) | ||
destination: { | ||
host: '192.30.253.113', // github.com (hostname lookups are supported with SOCKS v4a and 5) | ||
port: 80 | ||
} | ||
}; | ||
Socks.createConnection(options, function(err, socket, info) { | ||
if (err) | ||
console.log(err); | ||
else { | ||
// Connection has been established, we can start sending data now: | ||
socket.write("GET / HTTP/1.1\nHost: google.com\n\n"); | ||
socket.on('data', function(data) { | ||
console.log(data.length); | ||
console.log(data); | ||
}); | ||
// Async/Await | ||
try { | ||
const info = await SocksClient.createConnection(options); | ||
// PLEASE NOTE: sockets need to be resumed before any data will come in or out as they are paused right before this callback is fired. | ||
socket.resume(); | ||
console.log(info.socket); | ||
// <Socket ...> (this is a raw net.Socket that is established to the destination host through the given proxy server) | ||
} catch (err) { | ||
// Handle errors | ||
} | ||
// 569 | ||
// <Buffer 48 54 54 50 2f 31 2e 31 20 33 30 31 20 4d 6f 76 65 64 20 50 65... | ||
} | ||
// Promises | ||
SocksClient.createConnection(options) | ||
.then(info => { | ||
console.log(info.socket); | ||
// <Socket ...> (this is a raw net.Socket that is established to the destination host through the given proxy server) | ||
}) | ||
.catch(err => { | ||
// Handle errors | ||
}); | ||
// Callbacks | ||
SocksClient.createConnection(options, (err, info) => { | ||
if (!err) { | ||
console.log(info.socket); | ||
// <Socket ...> (this is a raw net.Socket that is established to the destination host through the given proxy server) | ||
} else { | ||
// Handle errors | ||
} | ||
}); | ||
``` | ||
### BIND Example: | ||
## Chaining Proxies | ||
When sending the BIND command to a SOCKS proxy server, this will cause the proxy server to open up a new tcp port. Once this port is open, you, another client, application, etc, can then connect to the SOCKS proxy on that tcp port and communications will be forwarded to each connection through the proxy itself. | ||
**Note:** Chaining is only supported when using the SOCKS connect command, and chaining can only be done through the special factory chaining function. | ||
This example makes a proxy chain through two SOCKS proxies to ip-api.com. Once the connection to the destination is established it sends an HTTP request to get a JSON response that returns ip info for the requesting ip. | ||
```javascript | ||
var options = { | ||
proxy: { | ||
ipaddress: "202.101.228.108", | ||
port: 1080, | ||
type: 4, | ||
command: "bind" // Since we are using bind, we must specify it here. | ||
const options = { | ||
destination: { | ||
host: 'ip-api.com', // host names are supported with SOCKS v4a and SOCKS v5. | ||
port: 80 | ||
}, | ||
command: 'connect', // Only the connect command is supported when chaining proxies. | ||
proxies: [ // The chain order is the order in the proxies array, meaning the last proxy will establish a connection to the destination. | ||
{ | ||
ipaddress: '159.203.75.235', | ||
port: 1081, | ||
type: 5 | ||
}, | ||
target: { | ||
host: "1.2.3.4", // When using bind, it's best to give an estimation of the ip that will be connecting to the newly opened tcp port on the proxy server. | ||
port: 1080 | ||
{ | ||
ipaddress: '104.131.124.203', | ||
port: 1081, | ||
type: 5 | ||
} | ||
}; | ||
] | ||
} | ||
Socks.createConnection(options, function(err, socket, info) { | ||
if (err) | ||
console.log(err); | ||
else { | ||
// BIND request has completed. | ||
// info object contains the remote ip and newly opened tcp port to connect to. | ||
console.log(info); | ||
// Async/Await | ||
try { | ||
const info = await SocksClient.createConnectionChain(options); | ||
// { port: 1494, host: '202.101.228.108' } | ||
console.log(info.socket); | ||
// <Socket ...> (this is a raw net.Socket that is established to the destination host through the given proxy servers) | ||
socket.on('data', function(data) { | ||
console.log(data.length); | ||
console.log(data); | ||
}); | ||
console.log(info.socket.remoteAddress) // The remote address of the returned socket is the first proxy in the chain. | ||
// 159.203.75.235 | ||
// Remember to resume the socket stream. | ||
socket.resume(); | ||
} | ||
info.socket.write('GET /json HTTP/1.1\nHost: ip-api.com\n\n'); | ||
info.socket.on('data', (data) => { | ||
console.log(data.toString()); // ip-api.com sees that the last proxy in the chain (104.131.124.203) is connected to it. | ||
/* | ||
HTTP/1.1 200 OK | ||
Access-Control-Allow-Origin: * | ||
Content-Type: application/json; charset=utf-8 | ||
Date: Sun, 24 Dec 2017 03:47:51 GMT | ||
Content-Length: 300 | ||
{ | ||
"as":"AS14061 Digital Ocean, Inc.", | ||
"city":"Clifton", | ||
"country":"United States", | ||
"countryCode":"US", | ||
"isp":"Digital Ocean", | ||
"lat":40.8326, | ||
"lon":-74.1307, | ||
"org":"Digital Ocean", | ||
"query":"104.131.124.203", | ||
"region":"NJ", | ||
"regionName":"New Jersey", | ||
"status":"success", | ||
"timezone":"America/New_York", | ||
"zip":"07014" | ||
} | ||
*/ | ||
}); | ||
} catch (err) { | ||
// Handle errors | ||
} | ||
// Promises | ||
SocksClient.createConnectionChain(options) | ||
.then(info => { | ||
console.log(info.socket); | ||
// <Socket ...> (this is a raw net.Socket that is established to the destination host through the given proxy server) | ||
console.log(info.socket.remoteAddress) // The remote address of the returned socket is the first proxy in the chain. | ||
// 159.203.75.235 | ||
info.socket.write('GET /json HTTP/1.1\nHost: ip-api.com\n\n'); | ||
info.socket.on('data', (data) => { | ||
console.log(data.toString()); // ip-api.com sees that the last proxy in the chain (104.131.124.203) is connected to it. | ||
/* | ||
HTTP/1.1 200 OK | ||
Access-Control-Allow-Origin: * | ||
Content-Type: application/json; charset=utf-8 | ||
Date: Sun, 24 Dec 2017 03:47:51 GMT | ||
Content-Length: 300 | ||
{ | ||
"as":"AS14061 Digital Ocean, Inc.", | ||
"city":"Clifton", | ||
"country":"United States", | ||
"countryCode":"US", | ||
"isp":"Digital Ocean", | ||
"lat":40.8326, | ||
"lon":-74.1307, | ||
"org":"Digital Ocean", | ||
"query":"104.131.124.203", | ||
"region":"NJ", | ||
"regionName":"New Jersey", | ||
"status":"success", | ||
"timezone":"America/New_York", | ||
"zip":"07014" | ||
} | ||
*/ | ||
}); | ||
}) | ||
.catch(err => { | ||
// Handle errors | ||
}); | ||
``` | ||
At this point, your original connection to the proxy server remains open, and no data will be received until a tcp connection is made to the given endpoint in the info object. | ||
// Callbacks | ||
SocksClient.createConnectionChain(options, (err, info) => { | ||
if (!err) { | ||
console.log(info.socket); | ||
// <Socket ...> (this is a raw net.Socket that is established to the destination host through the given proxy server) | ||
For an example, I am going to connect to the endpoint with telnet: | ||
console.log(info.socket.remoteAddress) // The remote address of the returned socket is the first proxy in the chain. | ||
// 159.203.75.235 | ||
info.socket.write('GET /json HTTP/1.1\nHost: ip-api.com\n\n'); | ||
info.socket.on('data', (data) => { | ||
console.log(data.toString()); // ip-api.com sees that the last proxy in the chain (104.131.124.203) is connected to it. | ||
/* | ||
HTTP/1.1 200 OK | ||
Access-Control-Allow-Origin: * | ||
Content-Type: application/json; charset=utf-8 | ||
Date: Sun, 24 Dec 2017 03:47:51 GMT | ||
Content-Length: 300 | ||
{ | ||
"as":"AS14061 Digital Ocean, Inc.", | ||
"city":"Clifton", | ||
"country":"United States", | ||
"countryCode":"US", | ||
"isp":"Digital Ocean", | ||
"lat":40.8326, | ||
"lon":-74.1307, | ||
"org":"Digital Ocean", | ||
"query":"104.131.124.203", | ||
"region":"NJ", | ||
"regionName":"New Jersey", | ||
"status":"success", | ||
"timezone":"America/New_York", | ||
"zip":"07014" | ||
} | ||
*/ | ||
}); | ||
} else { | ||
// Handle errors | ||
} | ||
}); | ||
``` | ||
Joshs-MacBook-Pro:~ Josh$ telnet 202.101.228.108 1494 | ||
Trying 202.101.228.108... | ||
Connected to 202.101.228.108. | ||
Escape character is '^]'. | ||
hello | ||
aaaaaaaaa | ||
``` | ||
Note that this connection to the newly bound port does not need to go through the SOCKS handshake. | ||
## Bind Example (TCP Relay) | ||
Back at our original connection we see that we have received some new data: | ||
When the bind command is sent to a SOCKS v4/v5 proxy server, the proxy server starts listening on a new TCP port and the proxy relays then remote host information back to the client. When another remote client connects to the proxy server on this port the SOCKS proxy sends a notification that an incoming connection has been accepted to the initial client and a full duplex stream is now established to the initial client and the client that connected to that special port. | ||
``` | ||
8 | ||
<Buffer 00 5a ca 61 43 a8 09 01> // This first piece of information can be ignored. | ||
```javascript | ||
const options = { | ||
proxy: { | ||
ipaddress: '159.203.75.235', | ||
port: 1081, | ||
type: 5 | ||
}, | ||
7 | ||
<Buffer 68 65 6c 6c 6f 0d 0a> // Hello <\r\n (enter key)> | ||
command: 'bind', | ||
11 | ||
<Buffer 61 61 61 61 61 61 61 61 61 0d 0a> // aaaaaaaaa <\r\n (enter key)> | ||
``` | ||
// When using BIND, the destination should be the remote client that is expected to connect to the SOCKS proxy. Using 0.0.0.0 makes the Proxy accept any incoming connection on that port. | ||
destination: { | ||
host: '0.0.0.0', | ||
port: 0 | ||
} | ||
}; | ||
As you can see the data entered in the telnet terminal is routed through the SOCKS proxy and back to the original connection that was made to the proxy. | ||
// Creates a new SocksClient instance. | ||
const client = new SocksClient(options); | ||
**Note** Please pay close attention to the first piece of data that was received. | ||
// When the SOCKS proxy has bound a new port and started listening, this event is fired. | ||
client.on('bound', info => { | ||
console.log(info.remoteHost); | ||
/* | ||
{ | ||
host: "159.203.75.235", | ||
port: 57362 | ||
} | ||
*/ | ||
}); | ||
``` | ||
<Buffer 00 5a ca 61 43 a8 09 01> | ||
// When a client connects to the newly bound port on the SOCKS proxy, this event is fired. | ||
client.on('established', info => { | ||
// info.remoteHost is the remote address of the client that connected to the SOCKS proxy. | ||
console.log(info.remoteHost); | ||
/* | ||
host: 67.171.34.23, | ||
port: 49823 | ||
*/ | ||
[005a] [PORT:2} [IP:4] | ||
console.log(info.socket); | ||
// <Socket ...> (This is a raw net.Socket that is a connection between the initial client and the remote client that connected to the proxy) | ||
// Handle received data... | ||
info.socket.on('data', data => { | ||
console.log('recv', data); | ||
}); | ||
}); | ||
// An error occurred trying to establish this SOCKS connection. | ||
client.on('error', err => { | ||
console.error(err); | ||
}); | ||
// Start connection to proxy | ||
client.connect(); | ||
``` | ||
This piece of data is technically part of the SOCKS BIND specifications, but because of my design decisions that were made in an effort to keep this library simple to use, you will need to make sure to ignore and/or deal with this initial packet that is received when a connection is made to the newly opened port. | ||
## Associate Example (UDP Relay) | ||
### Associate Example: | ||
The associate command sets up a UDP relay for the remote SOCKS proxy server to relay UDP packets to the remote host of your choice. | ||
When the associate command is sent to a SOCKS v5 proxy server, it sets up a UDP relay that allows the client to send UDP packets to a remote host through the proxy server, and also receive UDP packet responses back through the proxy server. | ||
```javascript | ||
var options = { | ||
proxy: { | ||
ipaddress: "202.101.228.108", | ||
port: 1080, | ||
type: 5, | ||
command: "associate" // Since we are using associate, we must specify it here. | ||
}, | ||
target: { | ||
// When using associate, either set the ip and port to 0.0.0.0:0 or the expected source of incoming udp packets. | ||
// Note: Some SOCKS servers MAY block associate requests with 0.0.0.0:0 endpoints. | ||
// Note: ipv4, ipv6, and hostnames are supported here. | ||
host: "0.0.0.0", | ||
port: 0 | ||
} | ||
}; | ||
const options = { | ||
proxy: { | ||
ipaddress: '159.203.75.235', | ||
port: 1081, | ||
type: 5 | ||
}, | ||
command: 'associate', | ||
Socks.createConnection(options, function(err, socket, info) { | ||
if (err) | ||
console.log(err); | ||
else { | ||
// Associate request has completed. | ||
// info object contains the remote ip and udp port to send UDP packets to. | ||
console.log(info); | ||
// { port: 42803, host: '202.101.228.108' } | ||
// When using associate, the destination should be the remote client that is expected to send UDP packets to the proxy server to be forwarded. This should be your local ip, or optionally the wildcard address (0.0.0.0) UDP Client <-> Proxy <-> UDP Client | ||
destination: { | ||
host: '0.0.0.0', | ||
port: 0 | ||
} | ||
}; | ||
var udp = new dgram.Socket('udp4'); | ||
// Create a local UDP socket for sending packets to the proxy. | ||
const udpSocket = dgram.createSocket('udp4'); | ||
udpSocket.bind(); | ||
// In this example we are going to send "Hello" to 1.2.3.4:2323 through the SOCKS proxy. | ||
// Listen for incoming UDP packets from the proxy server. | ||
udpSocket.on('message', (message, rinfo) => { | ||
console.log(SocksClient.parseUDPFrame(message)); | ||
/* | ||
{ frameNumber: 0, | ||
remoteHost: { host: '165.227.108.231', port: 4444 }, // The remote host that replied with a UDP packet | ||
data: <Buffer 74 65 73 74 0a> // The data | ||
} | ||
*/ | ||
}); | ||
var pack = Socks.createUDPFrame({ host: "1.2.3.4", port: 2323}, new Buffer("hello")); | ||
let client = new SocksClient(associateOptions); | ||
// Send Packet to Proxy UDP endpoint given in the info object. | ||
udp.send(pack, 0, pack.length, info.port, info.host); | ||
// When the UDP relay is established, this event is fired and includes the UDP relay port to send data to on the proxy server. | ||
client.on('established', info => { | ||
console.log(info.remoteHost); | ||
/* | ||
{ | ||
host: '159.203.75.235', | ||
port: 44711 | ||
} | ||
*/ | ||
// Send 'hello' to 165.227.108.231:4444 | ||
const packet = SocksClient.createUDPFrame({ | ||
remoteHost: { host: '165.227.108.231', port: 4444 }, | ||
data: Buffer.from(line) | ||
}); | ||
udpSocket.send(packet, info.remoteHost.port, info.remoteHost.host); | ||
}); | ||
``` | ||
Now assuming that the associate request went through correctly. Anything that is typed in the stdin will first be sent to the SOCKS proxy on the endpoint that was provided in the info object. Once the SOCKS proxy receives it, it will then forward on the actual UDP packet to the host you you wanted. | ||
**Note:** The associate TCP connection to the proxy must remain open for the UDP relay to work. | ||
1.2.3.4:2323 should now receive our relayed UDP packet from 202.101.228.108 (SOCKS proxy) | ||
``` | ||
// <Buffer 68 65 6c 6c 6f> | ||
``` | ||
## Additional Examples | ||
## Using socks as an HTTP Agent | ||
[Documentation](docs) | ||
You can use socks as a http agent which will relay all your http | ||
connections through the socks server. | ||
The object that `Socks.Agent` accepts is the same as `Socks.createConnection`, you don't need to set a target since you have to define it in `http.request` or `http.get` methods. | ||
## Migrating from v1 | ||
The second argument is a boolean which indicates whether the remote endpoint requires TLS. | ||
Looking for a guide to migrate from v1? Look [here](docs/migratingFromV1.md) | ||
```javascript | ||
var socksAgent = new Socks.Agent({ | ||
proxy: { | ||
ipaddress: "202.101.228.108", | ||
port: 1080, | ||
type: 5, | ||
}}, | ||
true, // we are connecting to a HTTPS server, false for HTTP server | ||
false // rejectUnauthorized option passed to tls.connect(). Only when secure is set to true | ||
); | ||
## Api Reference: | ||
http.get({ hostname: 'google.com', port: '443', agent: socksAgent}, function (res) { | ||
// Connection header by default is keep-alive, we have to manually end the socket | ||
socksAgent.encryptedSocket.end(); | ||
}); | ||
``` | ||
**Note:** socks includes full TypeScript definitions. These can even be used without using TypeScript as most IDEs (such as VS Code) will use these type definition files for auto completion intellisense even in JavaScript files. | ||
# Api Reference: | ||
* Class: SocksClient | ||
* [new SocksClient(options[, callback])](#new-socksclientoptions) | ||
* [Class Method: SocksClient.createConnection(options[, callback])](#class-method-socksclientcreateconnectionoptions-callback) | ||
* [Class Method: SocksClient.createConnectionChain(options[, callback])](#class-method-socksclientcreateconnectionchainoptions-callback) | ||
* [Class Method: SocksClient.createUDPFrame(options)](#class-method-socksclientcreateudpframedetails) | ||
* [Class Method: SocksClient.parseUDPFrame(data)](#class-method-socksclientparseudpframedata) | ||
* [Event: 'error'](#event-error) | ||
* [Event: 'bound'](#event-bound) | ||
* [Event: 'established'](#event-established) | ||
* [client.connect()](#clientconnect) | ||
* [client.socksClientOptions](#clientconnect) | ||
There are only three exported functions that you will ever need to use. | ||
### SocksClient | ||
### Socks.createConnection( options, callback(err, socket, info) ) | ||
> `Object` **Object containing options to use when creating this connection** | ||
SocksClient establishes SOCKS proxy connections to remote destination hosts. These proxy connections are fully transparent to the server and once established act as full duplex streams. SOCKS v4, v4a, and v5 are supported, as well as the connect, bind, and associate commands. | ||
> `function` **Callback that is called when connection completes or errors** | ||
SocksClient supports creating connections using callbacks, promises, and async/await flow control using two static factory functions createConnection and createConnectionChain. It also internally extends EventEmitter which results in allowing event handling based async flow control. | ||
Options: | ||
**SOCKS Compatibility Table** | ||
| Socks Version | TCP | UDP | IPv4 | IPv6 | Hostname | | ||
| --- | :---: | :---: | :---: | :---: | :---: | | ||
| SOCKS v4 | ✅ | ❌ | ✅ | ❌ | ❌ | | ||
| SOCKS v4a | ✅ | ❌ | ✅ | ❌ | ✅ | | ||
| SOCKS v5 | ✅ | ✅ | ✅ | ✅ | ✅ | | ||
```javascript | ||
var options = { | ||
### new SocksClient(options) | ||
// Information about proxy server | ||
proxy: { | ||
// IP Address of Proxy (Required) | ||
ipaddress: "1.2.3.4", | ||
* ```options``` {SocksClientOptions} - An object describing the SOCKS proxy to use, the command to send and establish, and the destination host to connect to. | ||
// TCP Port of Proxy (Required) | ||
port: 1080, | ||
### SocksClientOptions | ||
// Proxy Type [4, 5] (Required) | ||
// Note: 4 works for both 4 and 4a. | ||
type: 4, | ||
```typescript | ||
{ | ||
proxy: { | ||
ipaddress: '159.203.75.200', // ipv4 or ipv6 | ||
port: 1080, | ||
type: 5 // Proxy version (4 or 5). For v4a, just use 4. | ||
// SOCKS Connection Type (Optional) | ||
// - defaults to 'connect' | ||
// Optional fields | ||
userId: 'some username', // Used for SOCKS4 userId auth, and SOCKS5 user/pass auth in conjunction with password. | ||
password: 'some password' // Used in conjunction with userId for user/pass auth for SOCKS5 proxies. | ||
}, | ||
// 'connect' - establishes a regular SOCKS connection to the target host. | ||
// 'bind' - establishes an open tcp port on the SOCKS for another client to connect to. | ||
// 'associate' - establishes a udp association relay on the SOCKS server. | ||
command: "connect", | ||
command: 'connect', // connect, bind, associate | ||
destination: { | ||
host: '192.30.253.113', // ipv4, ipv6, hostname. Hostnames work with v4a and v5. | ||
port: 80 | ||
}, | ||
// SOCKS 4 Specific: | ||
// Optional fields | ||
timeout: 30000; // How long to wait to establish a proxy connection. (defaults to 30 seconds) | ||
} | ||
``` | ||
// UserId used when making a SOCKS 4/4a request. (Optional) | ||
userid: "someuserid", | ||
### Class Method: SocksClient.createConnection(options[, callback]) | ||
* ```options``` { SocksClientOptions } - An object describing the SOCKS proxy to use, the command to send and establish, and the destination host to connect to. | ||
* ```callback``` { Function } - Optional callback function that is called when the proxy connection is established, or an error occurs. | ||
* ```returns``` { Promise } - A Promise is returned that is resolved when the proxy connection is established, or rejected when an error occurs. | ||
// SOCKS 5 Specific: | ||
Creates a new proxy connection through the given proxy to the given destination host. This factory function supports callbacks and promises for async flow control. | ||
// Authentication used for SOCKS 5 (when it's required) (Optional) | ||
authentication: { | ||
username: "Josh", | ||
password: "somepassword" | ||
} | ||
}, | ||
**Note:** If a callback function is provided, the promise will always resolve regardless of an error occurring. Please be sure to exclusively use either promises or callbacks when using this factory function. | ||
// Information about target host and/or expected client of a bind association. (Required) | ||
target: { | ||
// When using 'connect': IP Address or hostname (4a and 5 only) of a target to connect to. | ||
// When using 'bind': IP Address of the expected client that will connect to the newly open tcp port. | ||
// When using 'associate': IP Address and Port of the expected client that will send UDP packets to this UDP association relay. | ||
```typescript | ||
const options = { | ||
proxy: { | ||
ipaddress: '159.203.75.200', // ipv4 or ipv6 | ||
port: 1080, | ||
type: 5 // Proxy version (4 or 5) | ||
}, | ||
// Note: | ||
// When using SOCKS 4, only an ipv4 address can be used. | ||
// When using SOCKS 4a, an ipv4 address OR a hostname can be used. | ||
// When using SOCKS 5, ipv4, ipv6, or a hostname can be used. | ||
host: "1.2.3.4", | ||
command: 'connect', // connect, bind, associate | ||
// TCP port of target to connect to. | ||
port: 1080 | ||
destination: { | ||
host: '192.30.253.113', // ipv4, ipv6, hostname | ||
port: 80 | ||
} | ||
} | ||
// Await/Async (uses a Promise) | ||
try { | ||
const info = await SocksClient.createConnection(options); | ||
console.log(info); | ||
/* | ||
{ | ||
socket: <Socket ...>, // Raw net.Socket | ||
} | ||
*/ | ||
/ <Socket ...> (this is a raw net.Socket that is established to the destination host through the given proxy server) | ||
} catch (err) { | ||
// Handle error... | ||
} | ||
// Promise | ||
SocksClient.createConnection(options) | ||
.then(info => { | ||
console.log(info); | ||
/* | ||
{ | ||
socket: <Socket ...>, // Raw net.Socket | ||
} | ||
*/ | ||
}) | ||
.catch(err => { | ||
// Handle error... | ||
}); | ||
// Callback | ||
SocksClient.createConnection(options, (err, info) => { | ||
if (!err) { | ||
console.log(info); | ||
/* | ||
{ | ||
socket: <Socket ...>, // Raw net.Socket | ||
} | ||
*/ | ||
} else { | ||
// Handle error... | ||
} | ||
}); | ||
``` | ||
### Class Method: SocksClient.createConnectionChain(options[, callback]) | ||
* ```options``` { SocksClientChainOptions } - An object describing a list of SOCKS proxies to use, the command to send and establish, and the destination host to connect to. | ||
* ```callback``` { Function } - Optional callback function that is called when the proxy connection chain is established, or an error occurs. | ||
* ```returns``` { Promise } - A Promise is returned that is resolved when the proxy connection chain is established, or rejected when an error occurs. | ||
Creates a new proxy connection chain through a list of at least two SOCKS proxies to the given destination host. This factory method supports callbacks and promises for async flow control. | ||
**Note:** If a callback function is provided, the promise will always resolve regardless of an error occurring. Please be sure to exclusively use either promises or callbacks when using this factory function. | ||
**Note:** At least two proxies must be provided for the chain to be established. | ||
```typescript | ||
const options = { | ||
proxies: [ // The chain order is the order in the proxies array, meaning the last proxy will establish a connection to the destination. | ||
{ | ||
ipaddress: '159.203.75.235', // ipv4 or ipv6 | ||
port: 1081, | ||
type: 5 | ||
}, | ||
{ | ||
ipaddress: '104.131.124.203', // ipv4 or ipv6 | ||
port: 1081, | ||
type: 5 | ||
} | ||
] | ||
// Amount of time to wait for a connection to be established. (Optional) | ||
// - defaults to 10000ms (10 seconds) | ||
timeout: 10000 | ||
}; | ||
command: 'connect', // Only connect is supported in chaining mode. | ||
destination: { | ||
host: '192.30.253.113', // ipv4, ipv6, hostname | ||
port: 80 | ||
} | ||
} | ||
``` | ||
Callback: | ||
```javascript | ||
### Class Method: SocksClient.createUDPFrame(details) | ||
* ```details``` { SocksUDPFrameDetails } - An object containing the remote host, frame number, and frame data to use when creating a SOCKS UDP frame packet. | ||
* ```returns``` { Buffer } - A Buffer containing all of the UDP frame data. | ||
// err: If an error occurs, err will be an Error object, otherwise null. | ||
// socket: Socket with established connection to your target host. | ||
// info: If using BIND or associate, this will be the remote endpoint to use. | ||
Creates a SOCKS UDP frame relay packet that is sent and received via a SOCKS proxy when using the associate command for UDP packet forwarding. | ||
function(err, socket, info) { | ||
// Hopefully no errors :-) | ||
**SocksUDPFrameDetails** | ||
```typescript | ||
{ | ||
frameNumber: 0, // The frame number (used for breaking up larger packets) | ||
remoteHost: { // The remote host to have the proxy send data to, or the remote host that send this data. | ||
host: '1.2.3.4', | ||
port: 1234 | ||
}, | ||
data: <Buffer 01 02 03 04...> // A Buffer instance of data to include in the packet (actual data sent to the remote host) | ||
} | ||
interface SocksUDPFrameDetails { | ||
// The frame number of the packet. | ||
frameNumber?: number; | ||
// The remote host. | ||
remoteHost: SocksRemoteHost; | ||
// The packet data. | ||
data: Buffer; | ||
} | ||
``` | ||
### Socks.createUDPFrame( target, data, [frame] ) | ||
> `Object` **Target host object containing destination for UDP packet** | ||
### Class Method: SocksClient.parseUDPFrame(data) | ||
* ```data``` { Buffer } - A Buffer instance containing SOCKS UDP frame data to parse. | ||
* ```returns``` { SocksUDPFrameDetails } - An object containing the remote host, frame number, and frame data of the SOCKS UDP frame. | ||
> `Buffer` **Data Buffer to send in the UDP packet** | ||
```typescript | ||
const frame = SocksClient.parseUDPFrame(data); | ||
console.log(frame); | ||
/* | ||
{ | ||
frameNumber: 0, | ||
remoteHost: { | ||
host: '1.2.3.4', | ||
port: 1234 | ||
}, | ||
data: <Buffer 01 02 03 04 ...> | ||
} | ||
*/ | ||
``` | ||
> `Number` **Frame number in UDP packet. (defaults to 0)** | ||
Parses a Buffer instance and returns the parsed SocksUDPFrameDetails object. | ||
Creates a UDP packet frame for using with UDP association relays. | ||
## Event: 'error' | ||
* ```err``` { SocksClientError } - An Error object containing an error message and the original SocksClientOptions. | ||
returns `Buffer` The completed UDP packet container to be sent to the proxy for forwarding. | ||
This event is emitted if an error occurs when trying to establish the proxy connection. | ||
target: | ||
```javascript | ||
## Event: 'bound' | ||
* ```info``` { SocksClientBoundEvent } An object containing a Socket and SocksRemoteHost info. | ||
// Target host information for where the UDP packet should be sent. | ||
var target = | ||
{ | ||
// ipv4, ipv6, or hostname for where to have the proxy send the UDP packet. | ||
host: "1.2.3.4", | ||
This event is emitted when using the BIND command on a remote SOCKS proxy server. This event indicates the proxy server is now listening for incoming connections on a specified port. | ||
// udpport for where to send the UDP packet. | ||
port: 2323 | ||
} | ||
**SocksClientBoundEvent** | ||
```typescript | ||
{ | ||
socket: net.Socket, // The underlying raw Socket | ||
remoteHost: { | ||
host: '1.2.3.4', // The remote host that is listening (usually the proxy itself) | ||
port: 4444 // The remote port the proxy is listening on for incoming connections (when using BIND). | ||
} | ||
} | ||
``` | ||
## Event: 'established' | ||
* ```info``` { SocksClientEstablishedEvent } An object containing a Socket and SocksRemoteHost info. | ||
This event is emitted when the following conditions are met: | ||
1. When using the CONNECT command, and a proxy connection has been established to the remote host. | ||
2. When using the BIND command, and an incoming connection has been accepted by the proxy and a TCP relay has been established. | ||
3. When using the ASSOCIATE command, and a UDP relay has been established. | ||
When using BIND, 'bound' is first emitted to indicate the SOCKS server is waiting for an incoming connection, and provides the remote port the SOCKS server is listening on. | ||
When using ASSOCIATE, 'established' is emitted with the remote UDP port the SOCKS server is accepting UDP frame packets on. | ||
**SocksClientEstablishedEvent** | ||
```typescript | ||
{ | ||
socket: net.Socket, // The underlying raw Socket | ||
remoteHost: { | ||
host: '1.2.3.4', // The remote host that is listening (usually the proxy itself) | ||
port: 52738 // The remote port the proxy is listening on for incoming connections (when using BIND). | ||
} | ||
} | ||
``` | ||
### Socks.Agent( options, tls) ) | ||
> `Object` **Object containing options to use when creating this connection (see above in createConnection)** | ||
## client.connect() | ||
> `boolean` **Boolean indicating if we upgrade the connection to TLS on the socks server** | ||
Starts connecting to the remote SOCKS proxy server to establish a proxy connection to the destination host. | ||
## client.socksClientOptions | ||
* ```returns``` { SocksClientOptions } The options that were passed to the SocksClient. | ||
Gets the options that were passed to the SocksClient when it was created. | ||
**SocksClientError** | ||
```typescript | ||
{ // Subclassed from Error. | ||
message: 'An error has occurred', | ||
options: { | ||
// SocksClientOptions | ||
} | ||
} | ||
``` | ||
# Further Reading: | ||
Please read the SOCKS 5 specifications for more information on how to use BIND and Associate. | ||
@@ -338,2 +661,3 @@ http://www.ietf.org/rfc/rfc1928.txt | ||
# License | ||
This work is licensed under the [MIT license](http://en.wikipedia.org/wiki/MIT_License). |
/// <reference types="node" /> | ||
import { EventEmitter } from 'events'; | ||
import * as net from 'net'; | ||
import { SocksClientOptions, SocksClientChainOptions, SocksRemoteHost, SocksProxy, SocksClientEstablishedEvent, SocksUDPFrameDetails } from '../common/constants'; | ||
import { SocksClientOptions, SocksClientChainOptions, SocksRemoteHost, SocksProxy, SocksClientBoundEvent, SocksClientEstablishedEvent, SocksUDPFrameDetails } from '../common/constants'; | ||
import { SocksClientError } from '../common/util'; | ||
interface SocksClient { | ||
on(event: 'close', listener: (had_error: boolean) => void): this; | ||
on(event: 'error', listener: (err: SocksClientError) => void): this; | ||
on(event: 'bound', listener: (info: SocksClientEstablishedEvent) => void): this; | ||
on(event: 'bound', listener: (info: SocksClientBoundEvent) => void): this; | ||
on(event: 'established', listener: (info: SocksClientEstablishedEvent) => void): this; | ||
once(event: string, listener: (...args: any[]) => void): this; | ||
once(event: 'close', listener: (had_error: boolean) => void): this; | ||
once(event: 'error', listener: (err: SocksClientError) => void): this; | ||
once(event: 'bound', listener: (info: SocksClientEstablishedEvent) => void): this; | ||
once(event: 'bound', listener: (info: SocksClientBoundEvent) => void): this; | ||
once(event: 'established', listener: (info: SocksClientEstablishedEvent) => void): this; | ||
emit(event: string | symbol, ...args: any[]): boolean; | ||
emit(event: 'close'): boolean; | ||
emit(event: 'error', err: SocksClientError): boolean; | ||
emit(event: 'bound', info: SocksClientEstablishedEvent): boolean; | ||
emit(event: 'bound', info: SocksClientBoundEvent): boolean; | ||
emit(event: 'established', info: SocksClientEstablishedEvent): boolean; | ||
@@ -32,7 +29,19 @@ } | ||
/** | ||
* Creates a | ||
* @param options | ||
* @param callback | ||
* Creates a new SOCKS connection. | ||
* | ||
* Note: Supports callbacks and promises. Only supports the connect command. | ||
* @param options { SocksClientOptions } Options. | ||
* @param callback { Function } An optional callback function. | ||
* @returns { Promise } | ||
*/ | ||
static createConnection(options: SocksClientOptions, callback?: Function): Promise<SocksClientEstablishedEvent>; | ||
/** | ||
* Creates a new SOCKS connection chain to a destination host through 2 or more SOCKS proxies. | ||
* | ||
* Note: Supports callbacks and promises. Only supports the connect method. | ||
* Note: Implemented via createConnection() factory function. | ||
* @param options { SocksClientChainOptions } Options | ||
* @param callback { Function } An optional callback function. | ||
* @returns { Promise } | ||
*/ | ||
static createConnectionChain(options: SocksClientChainOptions, callback?: Function): Promise<SocksClientEstablishedEvent>; | ||
@@ -53,3 +62,3 @@ /** | ||
/** | ||
* Internal state setter. If the SocksClient is in a closed or error state, it cannot be changed to a non error state. | ||
* Internal state setter. If the SocksClient is in an error state, it cannot be changed to a non error state. | ||
*/ | ||
@@ -59,3 +68,3 @@ private state; | ||
* Starts the connection establishment to the proxy and destination. | ||
* @param existing_socket Connected socket to use instead of creating a new one (iternal use). | ||
* @param existing_socket Connected socket to use instead of creating a new one (internal use). | ||
*/ | ||
@@ -92,8 +101,6 @@ connect(existing_socket?: net.Socket): void; | ||
/** | ||
* Closes and destroys the underlying Socket. | ||
* | ||
* Note: Either only the 'close' or 'error' event will be emitted in a SocksClient lifetime. Never both. | ||
* @param err Optional error message to include in error event. | ||
* Closes and destroys the underlying Socket. Emits an error event. | ||
* @param err { String } An error string to include in error event. | ||
*/ | ||
private _closeSocket(err?); | ||
private _closeSocket(err); | ||
/** | ||
@@ -100,0 +107,0 @@ * Sends initial Socks v4 handshake request. |
@@ -7,2 +7,3 @@ /// <reference types="node" /> | ||
InvalidSocksCommand: string; | ||
InvalidSocksCommandForOperation: string; | ||
InvalidSocksCommandChain: string; | ||
@@ -14,2 +15,19 @@ InvalidSocksClientOptionsDestination: string; | ||
InvalidSocksClientOptionsProxiesLength: string; | ||
NegotiationError: string; | ||
SocketClosed: string; | ||
ProxyConnectionTimedOut: string; | ||
InternalError: string; | ||
InvalidSocks4HandshakeResponse: string; | ||
Socks4ProxyRejectedConnection: string; | ||
InvalidSocks4IncomingConnectionResponse: string; | ||
Socks4ProxyRejectedIncomingBoundConnection: string; | ||
InvalidSocks5InitialHandshakeResponse: string; | ||
InvalidSocks5IntiailHandshakeSocksVersion: string; | ||
InvalidSocks5InitialHandshakeNoAcceptedAuthType: string; | ||
InvalidSocks5InitialHandshakeUnknownAuthType: string; | ||
Socks5AuthenticationFailed: string; | ||
InvalidSocks5FinalHandshake: string; | ||
InvalidSocks5FinalHandshakeRejected: string; | ||
InvalidSocks5IncomingConnectionResponse: string; | ||
Socks5ProxyRejectedIncomingBoundConnection: string; | ||
}; | ||
@@ -55,3 +73,3 @@ declare type SocksCommandOption = 'connect' | 'bind' | 'associate'; | ||
ReceivedInitialHandshakeResponse = 4, | ||
SentAuthenication = 5, | ||
SentAuthentication = 5, | ||
ReceivedAuthenticationResponse = 6, | ||
@@ -64,4 +82,6 @@ SentFinalHandshake = 7, | ||
Error = 99, | ||
Closed = 100, | ||
} | ||
/** | ||
* Represents a SocksProxy | ||
*/ | ||
interface SocksProxy { | ||
@@ -74,2 +94,5 @@ ipaddress: string; | ||
} | ||
/** | ||
* Represents a remote host | ||
*/ | ||
interface SocksRemoteHost { | ||
@@ -79,4 +102,7 @@ host: string; | ||
} | ||
/** | ||
* SocksClient connection options. | ||
*/ | ||
interface SocksClientOptions { | ||
command?: SocksCommandOption; | ||
command: SocksCommandOption; | ||
destination: SocksRemoteHost; | ||
@@ -87,2 +113,5 @@ proxy: SocksProxy; | ||
} | ||
/** | ||
* SocksClient chain connection options. | ||
*/ | ||
interface SocksClientChainOptions { | ||
@@ -97,4 +126,5 @@ command: 'connect'; | ||
socket: Socket; | ||
remoteHostInfo?: SocksRemoteHost; | ||
remoteHost?: SocksRemoteHost; | ||
} | ||
declare type SocksClientBoundEvent = SocksClientEstablishedEvent; | ||
interface SocksUDPFrameDetails { | ||
@@ -105,2 +135,2 @@ frameNumber?: number; | ||
} | ||
export { DEFAULT_TIMEOUT, ERRORS, SocksProxyType, SocksCommand, Socks4Response, Socks5Auth, Socks5HostType, Socks5Response, SocksClientState, SocksProxy, SocksRemoteHost, SocksCommandOption, SocksClientOptions, SocksClientChainOptions, SocksClientEstablishedEvent, SocksUDPFrameDetails }; | ||
export { DEFAULT_TIMEOUT, ERRORS, SocksProxyType, SocksCommand, Socks4Response, Socks5Auth, Socks5HostType, Socks5Response, SocksClientState, SocksProxy, SocksRemoteHost, SocksCommandOption, SocksClientOptions, SocksClientChainOptions, SocksClientEstablishedEvent, SocksClientBoundEvent, SocksUDPFrameDetails }; |
@@ -5,4 +5,5 @@ import { SocksClientOptions, SocksClientChainOptions } from '../client/socksclient'; | ||
* @param options { SocksClientOptions } | ||
* @param acceptedCommands { string[] } A list of accepted SocksProxy commands. | ||
*/ | ||
declare function validateSocksClientOptions(options: SocksClientOptions): void; | ||
declare function validateSocksClientOptions(options: SocksClientOptions, acceptedCommands?: string[]): void; | ||
/** | ||
@@ -9,0 +10,0 @@ * Validates the SocksClientChainOptions |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
191479
30
662
2
13
1181