Skip to content

Commit

Permalink
✨ feat: clinet (connect) in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
MoIzadloo committed Mar 5, 2023
1 parent 718b2c2 commit 034fb86
Show file tree
Hide file tree
Showing 36 changed files with 465 additions and 135 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ server.listen(port, host)
There are two different authentication methods independent
of which version of the socks protocol you are working with.
in case you are using V5, you can use the useAuth hook and use
one of the available methods from the [methods](./src/auth/methods)
one of the available methods from the [methods](src/server/auth/methods)
directory, or if you want to create your own implementations or
custom methods, you have to define a function that implements the
AuthMethod interface, like [userPass.ts](./src/auth/methods/userPass.ts) and pass it as an argument to useAuth hook, in case you are using V4 you have to use useIdent hook which receives a callback function which gives you userId, a string in which
AuthMethod interface, like [userPass.ts](src/server/auth/methods/userPass.ts) and pass it as an argument to useAuth hook, in case you are using V4 you have to use useIdent hook which receives a callback function which gives you userId, a string in which
should be processed and the result, which is going to be
a boolean should be return from the callback function, in case you want to support
both versions and also authenticate users on both you can use
Expand Down
6 changes: 0 additions & 6 deletions src/auth/methods/method.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/auth/methods/none.ts

This file was deleted.

46 changes: 46 additions & 0 deletions src/client/auth/authenticator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Connection from '../../helper/connection'
import { Handlers } from '../../helper/handlers'
import Writable from '../../helper/writable'
import { none } from './methods/none'
import { SOCKSVERSIONS } from '../../helper/constants'
import { MethodSelectionState } from '../state/socks5'

/**
* The Authenticator Class is responsible for resolving incoming
* authentication requests specific for socks5
*/
class Authenticator {
/**
* Server acceptable methods
*/
private readonly availableMethods: Handlers['auth']

/**
* Corresponding connection
*/
private readonly connection: Connection

constructor(connection: Connection) {
this.connection = connection
if (connection.handlers.auth.length <= 0) {
connection.handlers.auth.push(none())
}
this.availableMethods = connection.handlers.auth
}

/**
* Negotiates for authentication method with the user and authenticates users
* @returns void
*/
public authenticate(): void {
const writable = new Writable()
writable.push(SOCKSVERSIONS.socks5, this.availableMethods.length)
for (const method of this.availableMethods) {
writable.push(method.method)
}
this.connection.transitionTo(new MethodSelectionState(this.connection))
this.connection.write(writable)
}
}

export default Authenticator
File renamed without changes.
15 changes: 15 additions & 0 deletions src/client/auth/methods/none.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AUTHMODES } from '../../../helper/constants'
import { AuthMethod } from '../../../helper/authMethod'

/**
* No authentication method
* @returns AuthMethod
*/
export const none = (): AuthMethod => {
return {
method: AUTHMODES.none,
authenticate: (connection) => {
connection.handlers.req.connect(connection)
},
}
}
20 changes: 20 additions & 0 deletions src/client/auth/methods/userPass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AUTHMODES } from '../../../helper/constants'
import { AuthMethod } from '../../../helper/authMethod'
import Connection from '../../../helper/connection'

/**
* Extract user/pass from user authentication request and,
* execute the handler function
* @param handler - Check the authorization of user/pass
* @returns AuthMethod
*/
export const userPass = (
handler: (user: string, pass: string) => boolean
): AuthMethod => {
return {
method: AUTHMODES.userPass,
authenticate: (connection: Connection) => {
console.log('hey userPass')
},
}
}
73 changes: 73 additions & 0 deletions src/client/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* The Client class is responsible for creating a TCP socket connection,
* and communicate with a standard socks server
*/
import net from 'net'
import Event from '../helper/event'
import Connection, { EventTypes } from '../helper/connection'
import * as handlers from './handlers/index'
import { Handlers } from '../helper/handlers'
import Authenticator from './auth/authenticator'
import Address from '../helper/address'

export class Client {
private readonly host: string
private readonly port: number
private readonly connectionListener: (() => void) | undefined
/**
* The main event object
*/
private readonly event: Event<EventTypes>
constructor(
port: number,
host: string,
connectionListener: (() => void) | undefined
) {
this.host = host
this.port = port
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 version - Server protocol version
* @returns void
*/
connect(port: number, host: string, version: 4 | 5) {
const socket = net.connect(this.port, this.host, this.connectionListener)
const connection = new Connection(
this.event,
socket,
new Handlers({
connect: handlers.connect,
associate: handlers.associate,
bind: handlers.bind,
})
)
connection.version = version
connection.address = new Address(port, host)
if (version === 5) {
const authenticator = new Authenticator(connection)
authenticator.authenticate()
}
return socket
}
}

/**
* 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)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { handler } from '../handler'
import { handler } from '../../helper/handler'

/**
* Default implementation of connect
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { handler } from '../handler'
import { handler } from '../../helper/handler'

/**
* Default implementation of connect
Expand Down
38 changes: 38 additions & 0 deletions src/client/handlers/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { handler } from '../../helper/handler'
import Writable from '../../helper/writable'
import { COMMANDS } from '../../helper/constants'

/**
* Default implementation of connect
* @returns void
*/
export const connect = handler((info, socket) => {
const writeable = new Writable()
writeable.push(info.version)
if (info.version === 5) {
const addressBuff = info.address.toBuffer()
if (info.address.type === 'domain') {
writeable.push(
COMMANDS.connect,
0x00,
addressBuff.type,
addressBuff.host.length,
addressBuff.host,
addressBuff.port
)
} else {
writeable.push(
COMMANDS.connect,
0x00,
addressBuff.type,
addressBuff.host,
addressBuff.port
)
}
socket.write(writeable.toBuffer())
socket.on('data', (data) => {
console.log(data)
socket.removeAllListeners('data')
})
}
})
File renamed without changes.
Empty file added src/client/state/socks4.ts
Empty file.
38 changes: 38 additions & 0 deletions src/client/state/socks5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { State } from '../../helper/state'
/**
* The MethodSelectionState negotiates
* with the client about the proper authentication method
* specific for socks5
* @remarks
* References: {@link https://www.rfc-editor.org/rfc/rfc1928#section-3}
*/
export class MethodSelectionState extends State {
/**
* users' suggested authentication methods
*/
private method?: Buffer

/**
* Parse request and extracts user suggested authentication methods
* @returns void
*/
parse() {
this.context.read(1)
this.method = this.context.read(1)
}

/**
* Continues to authenticate procedure with authenticator class
* @returns void
*/
reply() {
this.context.socket.removeAllListeners('data')
this.context.handlers.auth
.find((method) => {
if (this.method && method) {
return method.method === this.method.readInt8()
}
})
?.authenticate(this.context)
}
}
24 changes: 22 additions & 2 deletions src/helper/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ class Address {
public type: string
public port: number

constructor(port: number, host: string, type: string) {
constructor(port: number, host: string, type?: string) {
/**
* Regular expression for ipv4
*/
const ipv4Regex =
'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
/**
* Regular expression for ipv6
*/
const ipv6Regex =
'(?!^(?:(?:.*(?:::.*::|:::).*)|::|[0:]+[01]|.*[^:]:|[0-9a-fA-F](?:.*:.*){8}[0-9a-fA-F]|(?:[0-9a-fA-F]:){1,6}[0-9a-fA-F])$)^(?:(::|[0-9a-fA-F]{1,4}:{1,2})([0-9a-fA-F]{1,4}:{1,2}){0,6}([0-9a-fA-F]{1,4}|::)?)$'
/**
* Port
*/
Expand All @@ -30,7 +40,17 @@ class Address {
/**
* Type (ipv4 | ipv6 | domain)
*/
this.type = type.toLowerCase()
if (type) {
this.type = type.toLowerCase()
} else {
if (this.host.match(ipv4Regex)) {
this.type = 'ipv4'
} else if (this.host.match(ipv6Regex)) {
this.type = 'ipv6'
} else {
this.type = 'domain'
}
}
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/helper/authMethod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Connection from './connection'

export interface AuthMethod {
method: number
authenticate: (connection: Connection) => void
}
15 changes: 7 additions & 8 deletions src/server/connection.ts → src/helper/connection.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as net from 'net'
import { State } from './state/socks4'
import { Readable } from '../helper/readable'
import { IdentifierState } from './state/socks5'
import Writable from '../helper/writable'
import { Handlers } from './handlers/handlers'
import Address from '../helper/address'
import Event from '../helper/event'
import { State } from '../helper/state'
import { Readable } from './readable'
import { IdentifierState } from '../server/state/socks5'
import Writable from './writable'
import { Handlers } from './handlers'
import Address from './address'
import Event from './event'

export type EventTypes = {
data: (data: Buffer) => void
Expand Down Expand Up @@ -96,7 +96,6 @@ class Connection {
socket.on('close', () => {
this.event.trigger('close', this)
})
this.transitionTo(new IdentifierState(this))
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/server/handlers/handler.ts → src/helper/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Address from '../../helper/address'
import Connection from '../connection'
import Address from './address'
import Connection from './connection'
import * as net from 'net'

export interface Info {
Expand Down
23 changes: 23 additions & 0 deletions src/helper/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AuthMethod } from './authMethod'
import Connection from './connection'

interface Req {
connect: (connection: Connection) => void
associate: (connection: Connection) => void
bind: (connection: Connection) => void
}

/**
* The Handlers class contains handler functions corresponding
* to authentication and request (connect | associate | bind)
*/
export class Handlers {
public userId: (userId: string) => boolean
public auth: AuthMethod[]
public req: Req
constructor(req: Req) {
this.userId = () => true
this.auth = []
this.req = req
}
}

0 comments on commit 034fb86

Please sign in to comment.