Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 66 additions & 38 deletions src/lambda/tailscale-proxy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { SocksProxyAgent } from 'socks-proxy-agent';

async function proxyHttpRequest(
target: Pick<http.RequestOptions, 'hostname' | 'port' | 'agent'>,
target: Pick<http.RequestOptions, 'hostname' | 'port'>,
isHttps: boolean | undefined,
request: {
path: string;
Expand All @@ -16,53 +16,83 @@ async function proxyHttpRequest(
body: string | undefined;
},
): Promise<APIGatewayProxyResultV2> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
const httpLib = isHttps == undefined ?
(target.port == 443 ? https : http) :
(isHttps ? https : http);
const apiRequest = httpLib.request({
...target,
path: request.path,
method: request.method,
headers: request.headers,
}, (res: http.IncomingMessage) => {
res.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
res.on('end', () => {
const responseBody = Buffer.concat(chunks);
resolve({
statusCode: res.statusCode || 500,
headers: res.headers as Record<string, string>,
body: responseBody.toString('base64'),
isBase64Encoded: true,

async function requestPromise(): Promise<APIGatewayProxyResultV2> {
const socksProxyAgent = new SocksProxyAgent('socks://localhost:1055');
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
const httpLib = isHttps == undefined ?
(target.port == 443 ? https : http) :
(isHttps ? https : http);
const apiRequest = httpLib.request({
...target,
agent: socksProxyAgent,
path: request.path,
method: request.method,
headers: request.headers,
}, (res: http.IncomingMessage) => {
res.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
res.on('end', () => {
const responseBody = Buffer.concat(chunks);
resolve({
statusCode: res.statusCode || 500,
headers: res.headers as Record<string, string>,
body: responseBody.toString('base64'),
isBase64Encoded: true,
});
});
res.on('error', (error: Error): void => {
console.error('Error receiving response:', error);
reject(error);
});
});
res.on('error', (error: Error): void => {
console.error('Error receiving response:', error);

apiRequest.on('error', (error: Error): void => {
console.error('Error sending request:', error);
reject(error);
});
});

apiRequest.on('error', (error: Error): void => {
console.error('Error sending request:', error);
reject(error);
if (request.body != null) {
apiRequest.write(request.body);
}
apiRequest.end();
});
}


if (request.body != null) {
apiRequest.write(request.body);
const connectionRetryDelays = [10, 50, 100, 500, 1000, 2000, 3000];
let attempt = 0;
let success = false;
let response: APIGatewayProxyResultV2;

do {
try {
response = await requestPromise();
success = true;
} catch (error) {
if (error == 'Error: Socks5 proxy rejected connection - Failure' && attempt < connectionRetryDelays.length) {
console.error('Error: Socks5 proxy rejected connection - Failure');
console.log('Retrying in', connectionRetryDelays[attempt], 'ms');
await new Promise((resolve) => setTimeout(resolve, connectionRetryDelays[attempt]));
attempt++;
} else {
throw error;
}
}
apiRequest.end();
});
} while (!success && attempt < connectionRetryDelays.length);

if (attempt > 0) {
console.log('Error: Socks5 proxy rejected connection - Failure - RESOLVED - attempt:', attempt, 'total delay time:', connectionRetryDelays.slice(0, attempt).reduce((a, b) => a + b, 0));
}

return response!;
}

export async function handler(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> {

let metrics: Metrics | undefined;
try {
const socksProxyAgent = new SocksProxyAgent('socks://localhost:1055');

let isHttps = undefined; // Auto-detect, will be set for port 443
if (!event.headers['ts-target-ip']) {
return {
Expand Down Expand Up @@ -102,15 +132,13 @@ export async function handler(event: APIGatewayProxyEventV2): Promise<APIGateway
const response = await proxyHttpRequest({
hostname: event.headers['ts-target-ip'],
port: event.headers['ts-target-port'],
agent: socksProxyAgent,
}, isHttps,
{
path: event.requestContext.http.path,
headers: targetHeaders,
method: event.requestContext.http.method,
body: event.body,
},
);
});

metrics?.addMetric('success', MetricUnit.Count, 1);
return response;
Expand Down