Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove ip package #94

Merged
merged 6 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
node-version: [18.x, 20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
Expand Down
2,559 changes: 1,534 additions & 1,025 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "socks",
"private": false,
"version": "2.7.1",
"version": "2.8.0",
"description": "Fully featured SOCKS proxy client supporting SOCKSv4, SOCKSv4a, and SOCKSv5. Includes Bind and Associate functionality.",
"main": "build/index.js",
"typings": "typings/index.d.ts",
Expand All @@ -23,7 +23,7 @@
"socks5"
],
"engines": {
"node": ">= 10.13.0",
"node": ">= 16.0.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropping support for node version is a breaking change - it looks like ip-address v7 supports Node v10; would it be possible to use that instead?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i'll just bump the overall package to 3.0.0. Anything prior to node 16.x isn't being maintained at this point.

Copy link

@G-Rath G-Rath Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that'll mean downstream packages will need to explicitly update their dependency on sock to v3 (and require that they support node v16+) which could delay rollout - any chance of getting a final v2 patch with this before immediately releasing v3?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw If you're running into problems related to Node 10, I'm happy to have a crack at getting things working :)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may just be the typescript building not working on 10.x, the actual javascript may work fine tbh.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, yeah so it works just fine on plain javascript node 10.24.1 with ip-address 9.x, it's just the building of the typescript/etc won't work for anything less than 16.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'll just leave the min version as-is with 10.x, and release as 2.8.0? Or should this just be a bug/patch as 2.7.2?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think technically it's a feature but I don't think anyone would complain if you released it as a bug fix 🤷

"npm": ">= 3.0.0"
},
"author": "Josh Glazebrook",
Expand All @@ -33,19 +33,18 @@
"license": "MIT",
"readmeFilename": "README.md",
"devDependencies": {
"@types/ip": "1.1.0",
"@types/mocha": "^9.1.1",
"@types/node": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.17",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"eslint": "^8.20.0",
"mocha": "^10.0.0",
"prettier": "^2.7.1",
"prettier": "^3.2.5",
"ts-node": "^10.9.1",
"typescript": "^4.7.4"
"typescript": "^5.3.3"
},
"dependencies": {
"ip": "^2.0.0",
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"scripts": {
Expand Down
37 changes: 23 additions & 14 deletions src/client/socksclient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {EventEmitter} from 'events';
import * as net from 'net';
import * as ip from 'ip';
import {SmartBuffer} from 'smart-buffer';
import {
DEFAULT_TIMEOUT,
Expand All @@ -24,10 +23,14 @@ import {
import {
validateSocksClientOptions,
validateSocksClientChainOptions,
ipv4ToInt32,
ipToBuffer,
int32ToIpv4,
} from '../common/helpers';
import {ReceiveBuffer} from '../common/receivebuffer';
import {SocksClientError, shuffleArray} from '../common/util';
import {Duplex} from 'stream';
import {Address6} from 'ip-address';

// Exposes SocksClient event types
declare interface SocksClient {
Expand Down Expand Up @@ -231,10 +234,10 @@ class SocksClient extends EventEmitter implements SocksClient {
// IPv4/IPv6/Hostname
if (net.isIPv4(options.remoteHost.host)) {
buff.writeUInt8(Socks5HostType.IPv4);
buff.writeUInt32BE(ip.toLong(options.remoteHost.host));
buff.writeUInt32BE(ipv4ToInt32(options.remoteHost.host));
} else if (net.isIPv6(options.remoteHost.host)) {
buff.writeUInt8(Socks5HostType.IPv6);
buff.writeBuffer(ip.toBuffer(options.remoteHost.host));
buff.writeBuffer(ipToBuffer(options.remoteHost.host));
} else {
buff.writeUInt8(Socks5HostType.Hostname);
buff.writeUInt8(Buffer.byteLength(options.remoteHost.host));
Expand Down Expand Up @@ -263,9 +266,11 @@ class SocksClient extends EventEmitter implements SocksClient {
let remoteHost;

if (hostType === Socks5HostType.IPv4) {
remoteHost = ip.fromLong(buff.readUInt32BE());
remoteHost = int32ToIpv4(buff.readUInt32BE());
} else if (hostType === Socks5HostType.IPv6) {
remoteHost = ip.toString(buff.readBuffer(16));
remoteHost = Address6.fromByteArray(
Array.from(buff.readBuffer(16)),
).canonicalForm();
} else {
remoteHost = buff.readString(buff.readUInt8());
}
Expand Down Expand Up @@ -508,7 +513,7 @@ class SocksClient extends EventEmitter implements SocksClient {

// Socks 4 (IPv4)
if (net.isIPv4(this.options.destination.host)) {
buff.writeBuffer(ip.toBuffer(this.options.destination.host));
buff.writeBuffer(ipToBuffer(this.options.destination.host));
buff.writeStringNT(userId);
// Socks 4a (hostname)
} else {
Expand Down Expand Up @@ -546,7 +551,7 @@ class SocksClient extends EventEmitter implements SocksClient {

const remoteHost: SocksRemoteHost = {
port: buff.readUInt16BE(),
host: ip.fromLong(buff.readUInt32BE()),
host: int32ToIpv4(buff.readUInt32BE()),
};

// If host is 0.0.0.0, set to proxy host.
Expand Down Expand Up @@ -584,7 +589,7 @@ class SocksClient extends EventEmitter implements SocksClient {

const remoteHost: SocksRemoteHost = {
port: buff.readUInt16BE(),
host: ip.fromLong(buff.readUInt32BE()),
host: int32ToIpv4(buff.readUInt32BE()),
};

this.setState(SocksClientState.Established);
Expand Down Expand Up @@ -747,10 +752,10 @@ class SocksClient extends EventEmitter implements SocksClient {
// ipv4, ipv6, domain?
if (net.isIPv4(this.options.destination.host)) {
buff.writeUInt8(Socks5HostType.IPv4);
buff.writeBuffer(ip.toBuffer(this.options.destination.host));
buff.writeBuffer(ipToBuffer(this.options.destination.host));
} else if (net.isIPv6(this.options.destination.host)) {
buff.writeUInt8(Socks5HostType.IPv6);
buff.writeBuffer(ip.toBuffer(this.options.destination.host));
buff.writeBuffer(ipToBuffer(this.options.destination.host));
} else {
buff.writeUInt8(Socks5HostType.Hostname);
buff.writeUInt8(this.options.destination.host.length);
Expand Down Expand Up @@ -799,7 +804,7 @@ class SocksClient extends EventEmitter implements SocksClient {
);

remoteHost = {
host: ip.fromLong(buff.readUInt32BE()),
host: int32ToIpv4(buff.readUInt32BE()),
port: buff.readUInt16BE(),
};

Expand Down Expand Up @@ -842,7 +847,9 @@ class SocksClient extends EventEmitter implements SocksClient {
);

remoteHost = {
host: ip.toString(buff.readBuffer(16)),
host: Address6.fromByteArray(
Array.from(buff.readBuffer(16)),
).canonicalForm(),
port: buff.readUInt16BE(),
};
}
Expand Down Expand Up @@ -913,7 +920,7 @@ class SocksClient extends EventEmitter implements SocksClient {
);

remoteHost = {
host: ip.fromLong(buff.readUInt32BE()),
host: int32ToIpv4(buff.readUInt32BE()),
port: buff.readUInt16BE(),
};

Expand Down Expand Up @@ -956,7 +963,9 @@ class SocksClient extends EventEmitter implements SocksClient {
);

remoteHost = {
host: ip.toString(buff.readBuffer(16)),
host: Address6.fromByteArray(
Array.from(buff.readBuffer(16)),
).canonicalForm(),
port: buff.readUInt16BE(),
};
}
Expand Down
48 changes: 22 additions & 26 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {Duplex} from 'stream';
import {Socket, SocketConnectOpts} from 'net';
import {RequireOnlyOne} from './util';

const DEFAULT_TIMEOUT = 30000;

Expand Down Expand Up @@ -112,32 +111,29 @@ enum SocksClientState {
/**
* Represents a SocksProxy
*/
type SocksProxy = RequireOnlyOne<
{
// The ip address (or hostname) of the proxy. (this is equivalent to the host option)
ipaddress?: string;
// The ip address (or hostname) of the proxy. (this is equivalent to the ipaddress option)
host?: string;
// Numeric port number of the proxy.
port: number;
// 4 or 5 (4 is also used for 4a).
type: SocksProxyType;
/* For SOCKS v4, the userId can be used for authentication.
interface SocksProxy {
// The ip address (or hostname) of the proxy. (this is equivalent to the host option)
ipaddress?: string;
// The ip address (or hostname) of the proxy. (this is equivalent to the ipaddress option)
host?: string;
// Numeric port number of the proxy.
port: number;
// 4 or 5 (4 is also used for 4a).
type: SocksProxyType;
/* For SOCKS v4, the userId can be used for authentication.
For SOCKS v5, userId is used as the username for username/password authentication. */
userId?: string;
// For SOCKS v5, this password is used in username/password authentication.
password?: string;
// If present, this auth method will be sent to the proxy server during the initial handshake.
custom_auth_method?: number;
// If present with custom_auth_method, the payload of the returned Buffer of the provided function is sent during the auth handshake.
custom_auth_request_handler?: () => Promise<Buffer>;
// If present with custom_auth_method, this is the expected total response size of the data returned from the server during custom auth handshake.
custom_auth_response_size?: number;
// If present with custom_auth_method, the response from the server is passed to this function. If true is returned from this function, socks client will continue the handshake process, if false it will disconnect.
custom_auth_response_handler?: (data: Buffer) => Promise<boolean>;
},
'host' | 'ipaddress'
>;
userId?: string;
// For SOCKS v5, this password is used in username/password authentication.
password?: string;
// If present, this auth method will be sent to the proxy server during the initial handshake.
custom_auth_method?: number;
// If present with custom_auth_method, the payload of the returned Buffer of the provided function is sent during the auth handshake.
custom_auth_request_handler?: () => Promise<Buffer>;
// If present with custom_auth_method, this is the expected total response size of the data returned from the server during custom auth handshake.
custom_auth_response_size?: number;
// If present with custom_auth_method, the response from the server is passed to this function. If true is returned from this function, socks client will continue the handshake process, if false it will disconnect.
custom_auth_response_handler?: (data: Buffer) => Promise<boolean>;
}

/**
* Represents a remote host
Expand Down
33 changes: 33 additions & 0 deletions src/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
SocksProxy,
} from './constants';
import * as stream from 'stream';
import {Address4, Address6} from 'ip-address';
import * as net from 'net';

/**
* Validates the provided SocksClientOptions
Expand Down Expand Up @@ -208,3 +210,34 @@ function isValidTimeoutValue(value: number) {
}

export {validateSocksClientOptions, validateSocksClientChainOptions};

export function ipv4ToInt32(ip: string): number {
const address = new Address4(ip);
// Convert the IPv4 address parts to an integer
return address.toArray().reduce((acc, part) => (acc << 8) + part, 0);
}

export function int32ToIpv4(int32: number): string {
// Extract each byte (octet) from the 32-bit integer
const octet1 = (int32 >>> 24) & 0xff;
const octet2 = (int32 >>> 16) & 0xff;
const octet3 = (int32 >>> 8) & 0xff;
const octet4 = int32 & 0xff;

// Combine the octets into a string in IPv4 format
return [octet1, octet2, octet3, octet4].join('.');
}

export function ipToBuffer(ip: string): Buffer {
if (net.isIPv4(ip)) {
// Handle IPv4 addresses
const address = new Address4(ip);
return Buffer.from(address.toArray());
} else if (net.isIPv6(ip)) {
// Handle IPv6 addresses
const address = new Address6(ip);
return Buffer.from(address.toByteArray());
} else {
throw new Error('Invalid IP address format');
}
}
12 changes: 1 addition & 11 deletions src/common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,4 @@ function shuffleArray(array: unknown[]) {
}
}

// Helper type to require one of N keys.
type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Pick<
T,
Exclude<keyof T, Keys>
> &
{
[K in Keys]?: Required<Pick<T, K>> &
Partial<Record<Exclude<Keys, K>, undefined>>;
}[Keys];

export {RequireOnlyOne, SocksClientError, shuffleArray};
export {SocksClientError, shuffleArray};
26 changes: 24 additions & 2 deletions test/socksclient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import {SocksClientError, shuffleArray} from '../src/common/util';
import {
validateSocksClientOptions,
validateSocksClientChainOptions,
ipv4ToInt32,
int32ToIpv4,
ipToBuffer,
} from '../src/common/helpers';
import * as net from 'net';
import { Address4, Address6 } from 'ip-address';

describe('Creating and parsing Socks UDP frames', () => {
const packetData = Buffer.from([10, 12, 14, 16, 18, 20]);
Expand All @@ -26,7 +30,7 @@ describe('Creating and parsing Socks UDP frames', () => {
};

const ipv6HostInfo = {
host: '2001:db8:85a3:1234:8a2e:370:7334:1840',
host: '2001:0db8:85a3:1234:8a2e:0370:7334:1840',
port: 80,
};

Expand Down Expand Up @@ -541,11 +545,29 @@ describe('SocksClient', () => {
describe('utils', () => {
it('should shuffle an array', () => {
const arr = [1, 2, 3, 4, 5, 6];
let arrCopy = [...arr];
const arrCopy = [...arr];

shuffleArray(arrCopy);

assert.notDeepStrictEqual(arr, arrCopy);
assert.deepStrictEqual(arr, arrCopy.sort());
});

it("should convert between int32 and ipv4 string", () => {
const ipAddr = "1.2.3.4";
const ipLong = ipv4ToInt32(ipAddr);
assert.equal(int32ToIpv4(ipLong), ipAddr);
});

it('converts a valid IPv4 address to a Buffer correctly', () => {
const ip = '192.168.1.1';
const expectedBuffer = Buffer.from(new Address4(ip).toArray());
assert.deepEqual(expectedBuffer, ipToBuffer(ip));
});

it('converts a valid IPv6 address to a Buffer correctly', () => {
const ip = '2001:0db8:85a3:0000:0000:8a2e:0370:7334';
const expectedBuffer = Buffer.from(new Address6(ip).toByteArray());
assert.deepEqual(expectedBuffer, ipToBuffer(ip));
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"sourceMap": true,
"moduleResolution": "node",
"outDir": "build",
"watch": false,
"skipLibCheck": true,
"declaration": true,
"declarationDir": "typings",
"removeComments": false,
Expand Down
Loading
Loading