Skip to content

Commit

Permalink
✨ feat: Add bind
Browse files Browse the repository at this point in the history
  • Loading branch information
MoIzadloo committed Apr 3, 2023
1 parent 5196ba6 commit c0e3c1d
Show file tree
Hide file tree
Showing 13 changed files with 485 additions and 80 deletions.
8 changes: 4 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
## Todo

- [ ] Implementation of server (Connect | Bind | Associate)
- [ ] Implementation of client (Connect | Bind | Associate)
- [ ] Complete documentation
- [ ] Complete test coverage

### In Progress

- [ ] Implementation of server Bind
- [ ] Implementation of client Bind
- [ ] Complete documentation
- [ ] Complete test coverage

### Done ✓

- [x] Implementation of server Connect
- [x] Implementation of client Connect
- [x] Implementation of server Associate
- [x] Implementation of client Associate
- [x] Implementation of server Bind
- [x] Implementation of client Bind
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
},
"dependencies": {
"@types/ip": "^1.1.0",
"ip": "^1.1.8"
"@types/tcp-port-used": "^1.0.1",
"ip": "^1.1.8",
"tcp-port-used": "^1.0.2"
},
"devDependencies": {
"@commitlint/cli": "^17.4.2",
Expand Down
40 changes: 39 additions & 1 deletion src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import Event from '../helper/event'
import Connection, { EventTypes } from '../helper/connection'
import * as handlers from './handlers/index'
import { Handlers } from '../helper/handlers'
import { HandlerResolve } from '../helper/handler'
import {
handler as reqHandler,
Handler,
HandlerResolve,
} from '../helper/handler'
import Authenticator from './auth/authenticator'
import Address from '../helper/address'
import { AuthMethod } from '../helper/authMethod'
Expand Down Expand Up @@ -92,6 +96,29 @@ export class Client {
return connection
}

bind(port: number, host: string, version?: 4 | 5, userId?: string) {
return new Promise<HandlerResolve>((resolve, reject) => {
const connection = this.connector(
port,
host,
COMMANDS.bind,
resolve,
reject,
version,
userId
)
if (connection?.request?.ver === 5) {
const authenticator = new Authenticator(connection)
authenticator.authenticate()
} else if (connection?.request?.ver === 4) {
if (connection?.request?.addr?.type === 'domain') {
reject('The Address type is not supported')
}
connection.handlers.req.bind(connection)
}
})
}

associate(port: number, host: string, version?: 4 | 5) {
return new Promise<HandlerResolve>((resolve, reject) => {
if (version === 5 || (!version && this.version === 5)) {
Expand Down Expand Up @@ -151,6 +178,17 @@ export class Client {
this.handlers.auth.push(handler)
return this
}

/**
* Get the handler function, and update this.handlers.req
* @param cmd - Specify handler type (connect | associate | bind)
* @param handler - Emitted when new request appears
* @returns Server
*/
public useReq(cmd: keyof Handlers['req'], handler: Handler): Client {
this.handlers.req[cmd] = reqHandler(handler)
return this
}
}

/**
Expand Down
24 changes: 3 additions & 21 deletions src/client/handlers/associate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { handler } from '../../helper/handler'
import { COMMANDS, SOCKS4REPLY, SOCKS5REPLY } from '../../helper/constants'
import { COMMANDS } from '../../helper/constants'
import Request from '../../helper/request'
import Reply from '../../helper/reply'

Expand All @@ -12,26 +12,8 @@ export const associate = handler((info, socket, event, resolve, reject) => {
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,
})
if (resolve && reject) {
reply.promiseHandler(socket, resolve, reject)
}
socket.removeAllListeners('data')
})
Expand Down
24 changes: 3 additions & 21 deletions src/client/handlers/bind.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { handler } from '../../helper/handler'
import Request from '../../helper/request'
import { COMMANDS, SOCKS4REPLY, SOCKS5REPLY } from '../../helper/constants'
import { COMMANDS } from '../../helper/constants'
import Reply from '../../helper/reply'

/**
Expand All @@ -18,26 +18,8 @@ export const bind = handler((info, socket, event, resolve, reject) => {
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,
})
if (resolve && reject) {
reply.promiseHandler(socket, resolve, reject)
}
socket.removeAllListeners('data')
})
Expand Down
24 changes: 3 additions & 21 deletions src/client/handlers/connect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { handler } from '../../helper/handler'
import { COMMANDS, SOCKS4REPLY, SOCKS5REPLY } from '../../helper/constants'
import { COMMANDS } from '../../helper/constants'
import Request from '../../helper/request'
import Reply from '../../helper/reply'

Expand All @@ -18,26 +18,8 @@ export const connect = handler((info, socket, event, resolve, reject) => {
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,
})
if (resolve && reject) {
reply.promiseHandler(socket, resolve, reject)
}
socket.removeAllListeners('data')
})
Expand Down
37 changes: 36 additions & 1 deletion src/helper/reply.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import Address from './address'
import Readable from './readable'
import { ADDRESSTYPES, SOCKSVERSIONS } from './constants'
import {
ADDRESSTYPES,
SOCKS4REPLY,
SOCKS5REPLY,
SOCKSVERSIONS,
} from './constants'
import Writable from './writable'
import { HandlerResolve } from './handler'
import * as net from 'net'

class Replay {
public ver: number
Expand Down Expand Up @@ -63,6 +70,34 @@ class Replay {
const addr = Address.buffToAddrFactory(dstPort, dstAddr, atype)
return new Replay(ver, rep, addr, rsv)
}

promiseHandler(
socket: net.Socket,
resolve: (value: PromiseLike<HandlerResolve> | HandlerResolve) => void,
reject: (reason?: any) => void
) {
if (reject) {
if (
this.rep !== SOCKS5REPLY.succeeded.code &&
this.rep !== SOCKS4REPLY.granted.code
) {
let msg = ''
msg += Object.values(this.ver === 5 ? SOCKS5REPLY : SOCKS4REPLY).find(
(rep) => {
return rep.code === this.rep
}
)?.msg
reject(msg)
}
}
if (resolve) {
resolve({
address: this.addr,
socket: socket,
rsv: this.rsv,
})
}
}
}

export default Replay
45 changes: 45 additions & 0 deletions src/helper/tcpRelay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as net from 'net'
import Reply from './reply'
import { SOCKS5REPLY } from './constants'
import { Info } from './handler'
import Address from './address'

class TcpRelay {
/**
* TCP socket
*/
private tcpRelay: net.Server

public port: number

constructor(port: number, info: Info, socket: net.Socket) {
this.port = port
this.tcpRelay = net.createServer((remoteSocket) => {
remoteSocket.on('data', (data) => {
socket.write(data)
})
})
this.tcpRelay.on('connection', (remoteSocket) => {
const remote = remoteSocket.address()
if ('port' in remote && 'address' in remote) {
const relayAddress = new Address(remote.port, remote.address)
const reply = new Reply(
info.version,
SOCKS5REPLY.succeeded.code,
relayAddress
)
socket.write(reply.toBuffer())
}
})
this.tcpRelay.listen(this.port, '127.0.0.1')
}
/**
* Close Relay's socket
* @param callback - Called when the socket has been closed
*/
close(callback?: () => void) {
this.tcpRelay.close(callback)
}
}

export default TcpRelay
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createServer } from './server/server'
import { connect } from './client/client'
import { EventTypes } from './helper/connection'
import * as serverAuthMethods from './server/auth/methods'
import * as clientAuthMethods from './client/auth/methods'
import udpRelay from './helper/udpRelay'
Expand All @@ -16,4 +17,5 @@ export {
parseUdpFrame,
createUdpFrame,
Address,
EventTypes,
}
Loading

0 comments on commit c0e3c1d

Please sign in to comment.