Skip to content

Commit

Permalink
✨ feat: Client add userId identification
Browse files Browse the repository at this point in the history
  • Loading branch information
MoIzadloo committed Mar 7, 2023
1 parent 99a5def commit a55882b
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 81 deletions.
34 changes: 12 additions & 22 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ export class Client {
*/
private readonly port: number

/**
* Triggers whenever the socket initialized
*/
private readonly connectionListener: (() => void) | undefined

/**
* The object which contains all default handlers
*/
Expand All @@ -36,11 +31,7 @@ export class Client {
*/
private readonly event: Event<EventTypes>

constructor(
port: number,
host: string,
connectionListener: (() => void) | undefined
) {
constructor(port: number, host: string) {
this.host = host
this.port = port
this.handlers = new Handlers({
Expand All @@ -49,24 +40,28 @@ export class Client {
bind: handlers.bind,
})
this.event = new Event<EventTypes>()
this.connectionListener = connectionListener
}

/**
* Connect to the target host trough socks server
* @param port - Server port
* @param host - Server address
* @param port - Target port
* @param host - Target host address
* @param version - Server protocol version
* @param userId - userId for identification in v4
* @returns void
*/
connect(port: number, host: string, version: 4 | 5) {
connect(port: number, host: string, version: 4 | 5, userId?: string) {
return new Promise<net.Socket>((resolve, reject) => {
const socket = net.connect(this.port, this.host, this.connectionListener)
const socket = net.connect(this.port, this.host)
const connection = new Connection(this.event, socket, this.handlers)
connection.version = version
connection.address = new Address(port, host)
connection.resolve = resolve
connection.reject = reject
connection.userId = userId
connection.event.subscribeOnce('error', (err) => {
reject(err.message)
})
if (version === 5) {
const authenticator = new Authenticator(connection)
authenticator.authenticate()
Expand All @@ -81,13 +76,8 @@ export class Client {
* Open new connection and connect to server
* @param port - Server port
* @param host - Server address
* @param connectionListener - Emitted when a new connection opens
* @returns void
*/
export const connect = (
port: number,
host: string,
connectionListener?: (() => void) | undefined
) => {
return new Client(port, host, connectionListener)
export const connect = (port: number, host: string) => {
return new Client(port, host)
}
12 changes: 7 additions & 5 deletions src/client/handlers/connect.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { handler } from '../../helper/handler'
import { handler, Info } from '../../helper/handler'
import Writable from '../../helper/writable'
import { Readable } from '../../helper/readable'
import { COMMANDS, SOCKS4REPLY, SOCKS5REPLY } from '../../helper/constants'
import net from 'net'

/**
* Handle connect request
Expand Down Expand Up @@ -32,10 +33,11 @@ export const connect = handler((info, socket, resolve, reject) => {
}
} else if (info.version === 4) {
const addressBuff = info.address.toBuffer()
writeable.push(
COMMANDS.connect,
Buffer.concat([addressBuff.port, addressBuff.host, Buffer.from([0x00])])
)
writeable.push(COMMANDS.connect, addressBuff.port, addressBuff.host)
if (info.userId) {
writeable.push(Buffer.from(info.userId))
}
writeable.push(Buffer.from([0x00]))
}
socket.write(writeable.toBuffer())
socket.on('data', (data) => {
Expand Down
5 changes: 5 additions & 0 deletions src/helper/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class Connection {
socks4: true,
}

/**
* UserId for socks4 client
*/
userId: string | undefined

constructor(
event: Event<EventTypes>,
socket: net.Socket,
Expand Down
2 changes: 2 additions & 0 deletions src/helper/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as net from 'net'
export interface Info {
version: number
address: Address
userId?: string
}

export type Handler = (
Expand All @@ -27,6 +28,7 @@ export const handler =
{
version: connection.version,
address: connection.address,
userId: connection.userId,
},
connection.socket,
connection.resolve,
Expand Down
2 changes: 1 addition & 1 deletion src/helper/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class Handlers {
constructor(
req: Req,
auth: AuthMethod[] = [],
userId: (userId: string) => boolean = () => true,
userId: (userId: string) => boolean = () => true
) {
this.req = req
this.auth = auth
Expand Down
26 changes: 22 additions & 4 deletions src/server/state/socks4.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { ADDRESSTYPES, COMMANDS } from '../../helper/constants'
import {
ADDRESSTYPES,
COMMANDS,
SOCKS4REPLY,
SOCKSVERSIONS,
} from '../../helper/constants'
import Address from '../../helper/address'
import { State } from '../../helper/state'
import Writable from '../../helper/writable'
import writable from '../../helper/writable'

/**
* The RequestState class is responsible to handle
Expand Down Expand Up @@ -43,9 +50,6 @@ export class RequestState extends State {
ADDRESSTYPES.ipv4
)
this.userId = this.context.readUntil(Buffer.from([0x00]))
if (!this.context.handlers.userId(this.userId.toString())) {
this.context.close()
}
}

/**
Expand All @@ -54,6 +58,20 @@ export class RequestState extends State {
* @returns void
*/
reply() {
if (
this.userId &&
this.context.address &&
!this.context.handlers.userId(this.userId.toString())
) {
const writable = new Writable()
writable.push(
0x00,
SOCKS4REPLY.identFail.code,
this.context.address.toBuffer().port,
this.context.address.toBuffer().host
)
this.context.write(writable)
}
this.context.socket.removeAllListeners('data')
switch (this.cmd) {
case COMMANDS.connect:
Expand Down
131 changes: 86 additions & 45 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,39 @@ describe('client socks5 (connect | associate | bind)', () => {
done()
})

test('connect to google.com',(done) => {
connect(serverPort, serverHost).connect(
httpPort,
'google.com',
5
).then(socket =>{
socket.write(
Buffer.from(
'GET / HTTP/1.1\r\n' +
'Host: www.google.com:80\r\n' +
'Connection: close\r\n' +
'\r\n'
test('connect to google.com', (done) => {
connect(serverPort, serverHost)
.connect(httpPort, 'google.com', 5)
.then((socket) => {
socket.write(
Buffer.from(
'GET / HTTP/1.1\r\n' +
'Host: www.google.com:80\r\n' +
'Connection: close\r\n' +
'\r\n'
)
)
)
socket.on('data', async (data) => {
socket.removeAllListeners('data')
expect(data.toString()).toMatch(/20[01] OK/)
done()
socket.on('data', async (data) => {
socket.removeAllListeners('data')
expect(data.toString()).toMatch(/20[01] OK/)
done()
})
})
}).catch(reason => {
expect(true).toBe(false)
})
.catch((reason) => {
expect(true).toBe(false)
})
})

test('connect to wrong domain', () => {
return expect(
connect(serverPort, serverHost).connect(httpPort, 'dummy-url.c', 5)
).rejects.toBe(SOCKS5REPLY.generalFailure.msg)
})

test('connect to wrong domain',() => {
return expect(connect(serverPort, serverHost).connect(
httpPort,
'dummy-url.c',
5
)).rejects.toBe(SOCKS5REPLY.generalFailure.msg)
test('wrong socks server address', async () => {
return expect(
connect(22, serverHost).connect(httpPort, 'google.com', 5)
).rejects.toMatch(/ECONNREFUSED/)
})

afterAll((done) => {
Expand All @@ -58,28 +61,66 @@ describe('client socks4 (connect | associate | bind)', () => {
done()
})

test('connect to google.com',(done) => {
connect(serverPort, serverHost).connect(
httpPort,
'142.251.1.101',
4
).then(socket =>{
socket.write(
Buffer.from(
'GET / HTTP/1.1\r\n' +
'Host: www.google.com:80\r\n' +
'Connection: close\r\n' +
'\r\n'
test('connect to google.com', (done) => {
connect(serverPort, serverHost)
.connect(httpPort, '142.251.1.101', 4)
.then((socket) => {
socket.write(
Buffer.from(
'GET / HTTP/1.1\r\n' +
'Host: www.google.com:80\r\n' +
'Connection: close\r\n' +
'\r\n'
)
)
)
socket.on('data', async (data) => {
expect(data.toString()).toMatch(/20[01] OK/)
done()
socket.end()
socket.on('data', async (data) => {
expect(data.toString()).toMatch(/20[01] OK/)
done()
socket.end()
})
})
.catch((reason) => {
expect(true).toBe(false)
})
}).catch(reason => {
expect(true).toBe(false)
})

afterAll((done) => {
server.close()
done()
})
})

describe('client check authentication and identification', () => {
const server = createServer()
beforeAll((done) => {
server.listen(serverPort, serverHost)
server.useIdent((userId) => {
return userId === 'tsocks'
})
done()
})

test('socks4 identification', (done) => {
connect(serverPort, serverHost)
.connect(httpPort, '142.251.1.101', 4, 'tsocks')
.then((socket) => {
socket.write(
Buffer.from(
'GET / HTTP/1.1\r\n' +
'Host: www.google.com:80\r\n' +
'Connection: close\r\n' +
'\r\n'
)
)
socket.on('data', async (data) => {
expect(data.toString()).toMatch(/20[01] OK/)
done()
socket.end()
})
})
.catch((reason) => {
expect(true).toBe(false)
})
})

afterAll((done) => {
Expand Down
5 changes: 1 addition & 4 deletions test/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ describe('server socks5 (connect | associate | bind)', () => {
++state
break
default:
const google = new Address(
httpPort,
'google.com',
).toBuffer()
const google = new Address(httpPort, 'google.com').toBuffer()
client.write(
Buffer.concat([
Buffer.from([
Expand Down

0 comments on commit a55882b

Please sign in to comment.