Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
karliatto committed Nov 29, 2024
1 parent 5ac9e53 commit 0f051a1
Show file tree
Hide file tree
Showing 15 changed files with 355 additions and 42 deletions.
35 changes: 35 additions & 0 deletions packages/node-utils/src/checkSocks5Proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import net from 'net';

export const checkSocks5Proxy = (host: string, port: number): Promise<boolean> => {
return new Promise((resolve, reject) => {
const socket = new net.Socket();

socket.setTimeout(2_000);

socket.on('connect', () => {
// Version 5, 1 method, no authentication
const handshakeRequest = Buffer.from([0x05, 0x01, 0x00]);
socket.write(handshakeRequest);
});

socket.on('data', data => {
if (data[0] === 0x05 && data[1] === 0x00) {
resolve(true);
} else {
resolve(false);
}
socket.destroy();
});

socket.on('error', err => {
reject(err);
});

socket.on('timeout', () => {
socket.destroy();
reject(new Error('Connection timed out'));
});

socket.connect(port, host);
});
};
1 change: 1 addition & 0 deletions packages/node-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export {
type Response,
} from './http';
export { checkFileExists } from './checkFileExists';
export { checkSocks5Proxy } from './checkSocks5Proxy';
61 changes: 61 additions & 0 deletions packages/node-utils/src/tests/checkSocks5Proxy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import net from 'net';

Check warning on line 1 in packages/node-utils/src/tests/checkSocks5Proxy.test.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

There should be at least one empty line between import groups
import { checkSocks5Proxy } from '../checkSocks5Proxy';

jest.mock('net');

describe('checkSocks5Proxy', () => {
const host = '127.0.0.1';
const port = 9050;

afterEach(() => {
jest.clearAllMocks();
});

it('should return true for a valid SOCKS5 proxy', async () => {
const mockSocket = {
connect: jest.fn(),
write: jest.fn(),
on: jest.fn((event, callback) => {
if (event === 'connect') {
callback();
}
if (event === 'data') {
// Valid SOCKS5 response.
callback(Buffer.from([0x05, 0x00]));
}
}),
setTimeout: jest.fn(),
destroy: jest.fn(),
};

// @ts-expect-error
net.Socket.mockImplementation(() => mockSocket);

const result = await checkSocks5Proxy(host, port);
expect(result).toBe(true);
});

it('should return false for an invalid SOCKS5 proxy', async () => {
const mockSocket = {
connect: jest.fn(),
write: jest.fn(),
on: jest.fn((event, callback) => {
if (event === 'connect') {
callback();
}
if (event === 'data') {
// Not valid SOCKS5 response
callback(Buffer.from([0x05, 0x01]));
}
}),
setTimeout: jest.fn(),
destroy: jest.fn(),
};

// @ts-expect-error
net.Socket.mockImplementation(() => mockSocket);

const result = await checkSocks5Proxy(host, port);
expect(result).toBe(false);
});
});
59 changes: 22 additions & 37 deletions packages/request-manager/src/controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { EventEmitter } from 'events';
import path from 'path';

import { createTimeoutPromise } from '@trezor/utils';
import { checkFileExists } from '@trezor/node-utils';

import { TorControlPort } from './torControlPort';
Expand All @@ -12,16 +11,18 @@ import {
TOR_CONTROLLER_STATUS,
} from './types';
import { bootstrapParser, BOOTSTRAP_EVENT_PROGRESS } from './events/bootstrap';
import { waitUntil } from './utils';

const WAITING_TIME = 1000;
const MAX_TRIES_WAITING = 200;
const BOOTSTRAP_SLOW_TRESHOLD = 1000 * 5; // 5 seconds.

export class TorController extends EventEmitter {
options: TorConnectionOptions;
controlPort: TorControlPort;
bootstrapSlownessChecker?: NodeJS.Timeout;
status: TorControllerStatus = TOR_CONTROLLER_STATUS.Stopped;
// Configurations
waitingTime = 1000;
maxTriesWaiting = 200;
bootstrapSlowThreshold = 1000 * 5; // 5 seconds.

constructor(options: TorConnectionOptions) {
super();
Expand Down Expand Up @@ -57,14 +58,14 @@ export class TorController extends EventEmitter {
if (this.bootstrapSlownessChecker) {
clearTimeout(this.bootstrapSlownessChecker);
}
// When Bootstrap starts we wait time defined in bootstrapSlowThreshold and if after that time,
// When Bootstrap starts we wait time defined in BOOTSTRAP_SLOW_TRESHOLD and if after that time,
// it has not being finalized, then we send slow event. We know that Bootstrap is going on since
// we received, at least, first Bootstrap events from ControlPort.
this.bootstrapSlownessChecker = setTimeout(() => {
this.emit('bootstrap/event', {
type: 'slow',
});
}, this.bootstrapSlowThreshold);
}, BOOTSTRAP_SLOW_TRESHOLD);
}

public async getTorConfiguration(
Expand Down Expand Up @@ -181,7 +182,7 @@ export class TorController extends EventEmitter {
// We consider that bootstrap has started when we receive any bootstrap event and
// Tor is not bootstrapping yet.
// If we do not receive any bootstrapping event, we can consider there is something going wrong and
// an error will be thrown when `maxTriesWaiting` is reached in `waitUntilAlive`.
// an error will be thrown when `MAX_TRIES_WAITING` is reached in `waitUntilAlive`.
this.startBootstrap();
}
if (event.progress === BOOTSTRAP_EVENT_PROGRESS.Done) {
Expand All @@ -191,41 +192,25 @@ export class TorController extends EventEmitter {
});
}

public waitUntilAlive(): Promise<void> {
const errorMessages: string[] = [];
public async waitUntilAlive(): Promise<void> {
this.status = TOR_CONTROLLER_STATUS.Bootstrapping;
const waitUntilResponse = async (triesCount: number): Promise<void> => {
if (this.getIsStopped()) {
// If TOR is starting and we want to cancel it.
return;
}
if (triesCount >= this.maxTriesWaiting) {
throw new Error(
`Timeout waiting for TOR control port: \n${errorMessages.join('\n')}`,
);
}
try {
await waitUntil(
MAX_TRIES_WAITING,
WAITING_TIME,
async () => {
const isConnected = await this.controlPort.connect();
const isAlive = this.controlPort.ping();
// It is running so let's not wait anymore.
if (isConnected && isAlive && this.getIsCircuitEstablished()) {
// It is running so let's not wait anymore.
return;
}
} catch (error) {
// Some error here is expected when waiting but
// we do not want to throw until maxTriesWaiting is reach.
// Instead we want to log it to know what causes the error.
if (error && error.message) {
console.warn('request-manager:', error.message);
errorMessages.push(error.message);
return true;
} else {
return false;
}
}
await createTimeoutPromise(this.waitingTime);

return waitUntilResponse(triesCount + 1);
};

return waitUntilResponse(1);
},
() => {
return this.getIsStopped();
},
);
}

public getStatus(): Promise<TorControllerStatus> {
Expand Down
39 changes: 39 additions & 0 deletions packages/request-manager/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createTimeoutPromise } from '@trezor/utils';

export const waitUntil = (
MAX_TRIES_WAITING: number,
WAITING_TIME: number,
checkToSuccess: () => Promise<boolean>,
getIsStopped: () => boolean,
): Promise<void> => {
const errorMessages: string[] = [];

const waitUntilResponse = async (triesCount: number): Promise<void> => {
if (getIsStopped()) {
// If stopped we do not wait anymore.
return;
}
if (triesCount >= MAX_TRIES_WAITING) {
throw new Error(`Timeout waiting: \n${errorMessages.join('\n')}`);
}
try {
const completed = await checkToSuccess();
if (completed) {
return;
}
} catch (error) {
// Some error here is expected when waiting but
// we do not want to throw until MAX_TRIES_WAITING is reach.
// Instead we want to log it to know what causes the error.
if (error && error.message) {
console.warn('error:', error.message);
errorMessages.push(error.message);
}
}
await createTimeoutPromise(WAITING_TIME);

return waitUntilResponse(triesCount + 1);
};

return waitUntilResponse(1);
};
1 change: 1 addition & 0 deletions packages/suite-desktop-api/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type HandshakeTorModule = {

export type TorSettings = {
snowflakeBinaryPath: string;
useExternalTor: boolean;
};

export type TraySettings = {
Expand Down
6 changes: 5 additions & 1 deletion packages/suite-desktop-core/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@ declare type UpdateSettings = {
declare type TorSettings = {
running: boolean; // Tor should be enabled
host: string; // Hostname of the tor process through which traffic is routed
port: number; // Port of the tor process through which traffic is routed
port: number; // Port of the Tor process through which traffic is routed
// controlPort: number; // Port of the Tor Control Port
// torDataDir: string; // Path of tor data directory
snowflakeBinaryPath: string; // Path in user system to the snowflake binary
// externalPort: number; // Port of the Tor process run by the user externally to suite
useExternalTor: boolean; // Tor should use external daemon instead of the one built-in suite.
};

declare type BridgeSettings = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { TorConnectionOptions } from '@trezor/request-manager/src/types';
import { checkSocks5Proxy } from '@trezor/node-utils';

import { Status } from './BaseProcess';

Check warning on line 4 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

There should be at least one empty line between import groups
import { waitUntil } from '@trezor/request-manager/src/utils';

Check warning on line 5 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

`@trezor/request-manager/src/utils` import should occur before import of `./BaseProcess`

export type TorProcessStatus = Status & { isBootstrapping?: boolean; isSocks5ProxyPort?: boolean };

const WAITING_TIME = 1_000;
const MAX_TRIES_WAITING = 200;
const DEFAULT_TOR_EXTERNAL_HOST = '127.0.0.1';
const DEFAULT_TOR_EXTERNAL_PORT = 9050;

export class TorExternalProcess {
// torController: TorController;
// port: number;
// controlPort: number;
// torHost: string;
// torDataDir: string;
// snowflakeBinaryPath: string;
// useExternalTor: boolean;

stop = true;

constructor(_options: TorConnectionOptions & { useExternalTor: boolean }) {

Check failure on line 25 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Useless constructor
// this.port = options.port;
// this.controlPort = options.controlPort;
// this.torHost = options.host;
// this.torDataDir = options.torDataDir;
// this.useExternalTor = options.useExternalTor;
// this.snowflakeBinaryPath = '';
// this.torController = new TorController({
// host: this.torHost,
// port: this.port,
// controlPort: this.controlPort,
// torDataDir: this.torDataDir,
// snowflakeBinaryPath: this.snowflakeBinaryPath,
// });
}

setTorConfig(_torConfig: { useExternalTor: boolean; snowflakeBinaryPath: string }) {
// Do nothing
}

async status(): Promise<TorProcessStatus> {
console.log('check status in TorExternalProcess');

Check failure on line 46 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Unexpected console statement

let isSocks5ProxyPort = false;
try {
isSocks5ProxyPort = await checkSocks5Proxy(
DEFAULT_TOR_EXTERNAL_HOST,
DEFAULT_TOR_EXTERNAL_PORT,
);
} catch {
// Ignore errors.
}

return {
service: false,
process: false,
isBootstrapping: false,
isSocks5ProxyPort: isSocks5ProxyPort,

Check failure on line 62 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Expected property shorthand
};
}

public getIsStopped() {
return this.stop;
}

public async waitUntilAliveExternal(host: string, port: number): Promise<void> {
await waitUntil(
MAX_TRIES_WAITING,
WAITING_TIME,
async () => {
let isSocks5ProxyPort = false;
try {
console.log('calling checkSocks5Proxy in waitUntilAliveExternal');

Check failure on line 77 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Unexpected console statement
isSocks5ProxyPort = await checkSocks5Proxy(host, port);
} catch {
// Ignore errors.
}
return isSocks5ProxyPort;

Check failure on line 82 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Expected blank line before this statement
},
() => {
return this.getIsStopped();
},
);
}

async start(): Promise<void> {
console.log('startExternal in TorProcess start');

Check failure on line 91 in packages/suite-desktop-core/src/libs/processes/TorExternalProcess.ts

View workflow job for this annotation

GitHub Actions / Linting and formatting

Unexpected console statement
await this.waitUntilAliveExternal(DEFAULT_TOR_EXTERNAL_HOST, DEFAULT_TOR_EXTERNAL_PORT);
this.stop = false;
}
}
Loading

0 comments on commit 0f051a1

Please sign in to comment.