Skip to content

Commit

Permalink
✨ feat: Client associate completed
Browse files Browse the repository at this point in the history
  • Loading branch information
MoIzadloo committed Mar 24, 2023
1 parent f8aa719 commit 250aa30
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 91 deletions.
12 changes: 10 additions & 2 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ export class Client {
private connector(
port: number,
host: string,
cmd: number,
resolve: (value: HandlerResolve | PromiseLike<HandlerResolve>) => void,
reject: (arg0: string) => void,
version?: 4 | 5,
userId?: string
) {
const socket = net.connect(this.port, this.host)
const connection = new Connection(this.event, socket, this.handlers)
const cmd = COMMANDS.connect
let ver
if (version) {
ver = version
Expand All @@ -95,7 +95,14 @@ export class Client {
associate(port: number, host: string, version?: 4 | 5) {
return new Promise<HandlerResolve>((resolve, reject) => {
if (version === 5 || (!version && this.version === 5)) {
const connection = this.connector(port, host, resolve, reject, 5)
const connection = this.connector(
port,
host,
COMMANDS.associate,
resolve,
reject,
5
)
const authenticator = new Authenticator(connection)
authenticator.authenticate()
} else {
Expand All @@ -117,6 +124,7 @@ export class Client {
const connection = this.connector(
port,
host,
COMMANDS.connect,
resolve,
reject,
version,
Expand Down
10 changes: 2 additions & 8 deletions src/client/handlers/associate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@ import Reply from '../../helper/reply'
* Handle udp associate request
* @returns void
*/
export const associate = handler((info, socket, resolve, reject) => {
const request = new Request(
info.version,
COMMANDS.associate,
info.address,
0,
info.userId
)
export const associate = handler((info, socket, event, resolve, reject) => {
const request = new Request(info.version, COMMANDS.associate, info.address)
socket.write(request.toBuffer())
socket.on('data', (data) => {
const reply = Reply.from(data)
Expand Down
39 changes: 37 additions & 2 deletions src/client/handlers/bind.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
import { handler } from '../../helper/handler'
import Request from '../../helper/request'
import { COMMANDS, SOCKS4REPLY, SOCKS5REPLY } from '../../helper/constants'
import Reply from '../../helper/reply'

/**
* Handle bind request
* @returns void
*/
export const bind = handler((info) => {
console.log(info)
export const bind = handler((info, socket, event, resolve, reject) => {
const request = new Request(
info.version,
COMMANDS.bind,
info.address,
0,
info.userId
)
socket.write(request.toBuffer())
socket.on('data', (data) => {
const reply = Reply.from(data)
if (reject) {
if (
reply.rep !== SOCKS5REPLY.succeeded.code &&
reply.rep !== SOCKS4REPLY.granted.code
) {
let msg = ''
msg += Object.values(reply.ver === 5 ? SOCKS5REPLY : SOCKS4REPLY).find(
(rep) => {
return rep.code === reply.rep
}
)?.msg
reject(msg)
}
}
if (resolve) {
resolve({
address: reply.addr,
socket: socket,
rsv: reply.rsv,
})
}
socket.removeAllListeners('data')
})
})
2 changes: 1 addition & 1 deletion src/client/handlers/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Reply from '../../helper/reply'
* Handle connect request
* @returns void
*/
export const connect = handler((info, socket, resolve, reject) => {
export const connect = handler((info, socket, event, resolve, reject) => {
const request = new Request(
info.version,
COMMANDS.connect,
Expand Down
1 change: 1 addition & 0 deletions src/helper/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type EventTypes = {
data: (data: Buffer) => void
error: (err: Error) => void
close: (connection: Connection) => void
terminate: () => void
}

export type Options = {
Expand Down
5 changes: 4 additions & 1 deletion src/helper/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Address from './address'
import Connection from './connection'
import Connection, { EventTypes } from './connection'
import * as net from 'net'
import Event from './event'

export interface Info {
version: number
Expand All @@ -17,6 +18,7 @@ export interface HandlerResolve {
export type Handler = (
info: Info,
socket: net.Socket,
event?: Event<EventTypes>,
resolve?: (value: PromiseLike<HandlerResolve> | HandlerResolve) => void,
reject?: ((reason?: any) => void) | undefined
) => void
Expand All @@ -37,6 +39,7 @@ export const handler =
userId: connection.userId,
},
connection.socket,
connection.event,
connection.resolve,
connection.reject
)
Expand Down
17 changes: 12 additions & 5 deletions src/helper/udpRelay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class UdpRelay {
*/
private udpRelay: dgram.Socket

private constructor(port: number, callback?: () => void) {
private constructor(port: number) {
this.udpRelay = dgram.createSocket('udp4')
this.udpRelay.on('message', (msg, crinfo) => {
const parsedMsg = UdpRelay.parseUdpFrame(msg)
Expand All @@ -51,18 +51,17 @@ class UdpRelay {
)
}
})
this.udpRelay.bind(port, callback)
this.udpRelay.bind(port)
}

/**
* The static method that controls the access to the UdpRelay instance
* @param port - Port to bind socket to
* @param callback - Callback for bind
* @returns UdpRelay
*/
public static getInstance(port: number, callback?: () => void): UdpRelay {
public static getInstance(port: number): UdpRelay {
if (!UdpRelay.instance) {
UdpRelay.instance = new UdpRelay(port, callback)
UdpRelay.instance = new UdpRelay(port)
}
return UdpRelay.instance
}
Expand Down Expand Up @@ -126,6 +125,14 @@ class UdpRelay {
data,
}
}

/**
* Close Relay's socket
* @param callback - Called when the socket has been closed
*/
close(callback?: () => void) {
this.udpRelay.close(callback)
}
}

export default UdpRelay
5 changes: 3 additions & 2 deletions src/server/auth/authenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Connection from '../../helper/connection'
import { Handlers } from '../../helper/handlers'
import Writable from '../../helper/writable'
import { none } from './methods'
import {SOCKSVERSIONS} from "../../helper/constants";

/**
* The Authenticator Class is responsible for resolving incoming
Expand Down Expand Up @@ -53,11 +54,11 @@ class Authenticator {
}
}
if (acceptable && selectedMethod) {
writable.push(this.connection?.request?.ver, selectedMethod.method)
writable.push(SOCKSVERSIONS.socks5, selectedMethod.method)
this.connection.write(writable)
selectedMethod.authenticate(this.connection)
} else {
writable.push(this.connection?.request?.ver, 0xff)
writable.push(SOCKSVERSIONS.socks5, 0xff)
this.connection.write(writable)
this.connection.close()
}
Expand Down
56 changes: 19 additions & 37 deletions src/server/handlers/associate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,40 @@ import { SOCKS5REPLY } from '../../helper/constants'
import Address from '../../helper/address'
import * as ip from 'ip'
import UdpRelay from '../../helper/udpRelay'
import Reply from '../../helper/reply'

/**
* Default implementation of associate
* @returns void
*/
export const associate = handler((info, socket) => {
export const associate = handler((info, socket, event) => {
const relayPort = socket.address()
let reply: Reply
if ('port' in relayPort) {
const relayHost =
info.address.host === '127.0.0.1' ? '127.0.0.1' : ip.address('private')
try {
UdpRelay.getInstance(relayPort.port, () => {
const relayAddress = new Address(relayPort.port, relayHost)
socket.write(
Buffer.concat([
Buffer.from([
info.version,
SOCKS5REPLY.succeeded.code,
0x00,
relayAddress.toBuffer().type,
]),
relayAddress.toBuffer().host,
relayAddress.toBuffer().port,
])
)
const relay = UdpRelay.getInstance(relayPort.port)
event?.subscribeOnce('terminate', () => {
relay.close()
})
const relayAddress = new Address(relayPort.port, relayHost)
reply = new Reply(info.version, SOCKS5REPLY.succeeded.code, relayAddress)
socket.write(reply.toBuffer())
} catch {
socket.write(
Buffer.concat([
Buffer.from([
info.version,
SOCKS5REPLY.generalFailure.code,
0x00,
info.address.toBuffer().type,
]),
info.address.toBuffer().host,
info.address.toBuffer().port,
])
reply = new Reply(
info.version,
SOCKS5REPLY.generalFailure.code,
info.address
)
socket.write(reply.toBuffer())
}
} else {
socket.write(
Buffer.concat([
Buffer.from([
info.version,
SOCKS5REPLY.generalFailure.code,
0x00,
info.address.toBuffer().type,
]),
info.address.toBuffer().host,
info.address.toBuffer().port,
])
reply = new Reply(
info.version,
SOCKS5REPLY.generalFailure.code,
info.address
)
socket.write(reply.toBuffer())
}
})
1 change: 1 addition & 0 deletions src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class Server {
this.connections.push(connection)
connectionListener?.(socket)
})
this.socketServer.on('close', () => this.event.trigger('terminate'))
}

public on(event: 'data', callback: EventTypes['data']): void
Expand Down
66 changes: 33 additions & 33 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const httpPort = 80

describe('client socks5 (connect | associate | bind)', () => {
const server = createServer()
const fakeDnsPort = 2345
const fakeDnsPort = 3456
const fakeDnsServer = dgram.createSocket('udp4')
beforeAll((done) => {
fakeDnsServer.bind(fakeDnsPort)
Expand Down Expand Up @@ -48,38 +48,38 @@ describe('client socks5 (connect | associate | bind)', () => {
})
})

// test('associate', (done) =>{
// connect(serverPort, serverHost, 5)
// .associate(0, '0.0.0.0')
// .then(info => {
// dns.setServers([`127.0.0.1:${fakeDnsPort}`])
// dns.resolve('google.com', (err, addresses) => {
// console.log(addresses)
// })
// fakeDnsServer.on('message', (msg, rinfo) => {
// console.log(msg)
// // udpSocket.send(
// // UdpRelay.createUdpFrame(
// // new Address(info.address.port, '127.0.0.1'),
// // Buffer.from('Hey')
// // ),
// // info.address.port,
// // info.address.host
// // )
// })
// const udpSocket = dgram.createSocket('udp4')
// udpSocket.on('message', (msg, rinfo) => {
// const parsedMsg = UdpRelay.parseUdpFrame(msg)
// console.log(parsedMsg)
// udpSocket.close()
// done()
// })
// udpSocket.bind()
// })
// .catch(reason => {
// expect(true).toBe(false)
// })
// })
test('associate', (done) =>{
connect(serverPort, serverHost, 5)
.associate(0, '0.0.0.0')
.then(info => {
const udpSocket = dgram.createSocket('udp4')
dns.setServers([`127.0.0.1:${fakeDnsPort}`])
dns.resolve('google.com', (err, addresses) => {
fakeDnsServer.close()
udpSocket.close()
done()
})
fakeDnsServer.on('message', (msg, rinfo) => {
udpSocket.send(
UdpRelay.createUdpFrame(
new Address(53, '8.8.8.8'),
Buffer.from(msg)
),
info.address.port,
info.address.host
)
udpSocket.once('message', (msg) => {
const parsedMsg = UdpRelay.parseUdpFrame(msg)
fakeDnsServer.send(parsedMsg.data, rinfo.port, rinfo.address)
})
})
udpSocket.bind()
})
.catch(reason => {
console.log(reason)
expect(true).toBe(false)
})
})

test('connect to wrong domain', () => {
return expect(
Expand Down

0 comments on commit 250aa30

Please sign in to comment.