|
|
|
@ -1,74 +1,148 @@
|
|
|
|
|
|
|
|
|
|
// 导入 FileSystemConfig 类型,用于定义文件系统配置
|
|
|
|
|
import type { FileSystemConfig } from 'common/fileSystemConfig';
|
|
|
|
|
// 导入 dns 模块,用于域名解析
|
|
|
|
|
import * as dns from 'dns';
|
|
|
|
|
// 导入 http 模块中的 request 函数,用于发送 HTTP 请求
|
|
|
|
|
import { request } from 'http';
|
|
|
|
|
// 导入 socks 模块中的 SocksClient 类,用于创建 SOCKS 代理客户端
|
|
|
|
|
import { SocksClient } from 'socks';
|
|
|
|
|
// 导入 Logging 模块,用于日志记录
|
|
|
|
|
import { Logging } from './logging';
|
|
|
|
|
// 导入 toPromise 和 validatePort 函数,用于将回调函数转换为 Promise 和验证端口号
|
|
|
|
|
import { toPromise, validatePort } from './utils';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 解析主机名并返回其对应的 IP 地址。
|
|
|
|
|
*
|
|
|
|
|
* @param hostname - 要解析的主机名。
|
|
|
|
|
* @returns 解析后的 IP 地址。
|
|
|
|
|
* @throws {Error} 如果无法解析主机名,则抛出错误。
|
|
|
|
|
*/
|
|
|
|
|
async function resolveHostname(hostname: string): Promise<string> {
|
|
|
|
|
// 使用 toPromise 函数将 dns.lookup 方法转换为 Promise
|
|
|
|
|
return toPromise<string>(cb => dns.lookup(hostname, cb)).then((ip) => {
|
|
|
|
|
// 记录调试信息,显示已解析的主机名和 IP 地址
|
|
|
|
|
Logging.debug`Resolved hostname "${hostname}" to: ${ip}`;
|
|
|
|
|
// 返回解析后的 IP 地址
|
|
|
|
|
return ip;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证文件系统配置对象的有效性。
|
|
|
|
|
*
|
|
|
|
|
* @param config - 要验证的文件系统配置对象。
|
|
|
|
|
* @throws {Error} 如果配置对象缺少必要的字段,则抛出错误。
|
|
|
|
|
*/
|
|
|
|
|
function validateConfig(config: FileSystemConfig) {
|
|
|
|
|
// 检查 config.proxy 是否存在,如果不存在则抛出错误
|
|
|
|
|
if (!config.proxy) throw new Error(`Missing field 'config.proxy'`);
|
|
|
|
|
// 检查 config.proxy.type 是否存在,如果不存在则抛出错误
|
|
|
|
|
if (!config.proxy.type) throw new Error(`Missing field 'config.proxy.type'`);
|
|
|
|
|
// 检查 config.proxy.host 是否存在,如果不存在则抛出错误
|
|
|
|
|
if (!config.proxy.host) throw new Error(`Missing field 'config.proxy.host'`);
|
|
|
|
|
// 检查 config.proxy.port 是否存在,如果不存在则抛出错误
|
|
|
|
|
if (!config.proxy.port) throw new Error(`Missing field 'config.proxy.port'`);
|
|
|
|
|
// 验证 config.proxy.port 是否为有效的端口号,并更新 config.proxy.port 的值
|
|
|
|
|
config.proxy.port = validatePort(config.proxy.port);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建一个通过 SOCKS 代理的网络连接。
|
|
|
|
|
*
|
|
|
|
|
* @param config - 包含连接配置的对象。
|
|
|
|
|
* @returns 一个可读流,表示与目标主机的连接。
|
|
|
|
|
* @throws {Error} 如果配置无效或连接失败,则抛出错误。
|
|
|
|
|
*/
|
|
|
|
|
export async function socks(config: FileSystemConfig): Promise<NodeJS.ReadableStream> {
|
|
|
|
|
// 记录创建 SOCKS 代理连接的信息
|
|
|
|
|
Logging.info`Creating socks proxy connection for ${config.name}`;
|
|
|
|
|
// 验证配置对象的有效性
|
|
|
|
|
validateConfig(config);
|
|
|
|
|
// 检查代理类型是否为 socks4 或 socks5
|
|
|
|
|
if (config.proxy!.type !== 'socks4' && config.proxy!.type !== 'socks5') {
|
|
|
|
|
// 如果不是,抛出错误
|
|
|
|
|
throw new Error(`Expected 'config.proxy.type' to be 'socks4' or 'socks5'`);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
// 解析代理主机名为 IP 地址
|
|
|
|
|
const ipaddress = (await resolveHostname(config.proxy!.host));
|
|
|
|
|
// 如果解析失败,抛出错误
|
|
|
|
|
if (!ipaddress) throw new Error(`Couldn't resolve '${config.proxy!.host}'`);
|
|
|
|
|
// 记录连接信息
|
|
|
|
|
Logging.debug`\tConnecting to ${config.host}:${config.port} over ${config.proxy!.type} proxy at ${ipaddress}:${config.proxy!.port}`;
|
|
|
|
|
// 创建到目标主机的 SOCKS 连接
|
|
|
|
|
const con = await SocksClient.createConnection({
|
|
|
|
|
// 定义要执行的命令为 'connect',即建立连接
|
|
|
|
|
command: 'connect',
|
|
|
|
|
// 目标地址,即要连接的主机和端口
|
|
|
|
|
destination: {
|
|
|
|
|
// 目标主机,使用配置中的 host 值,该值是必需的
|
|
|
|
|
host: config.host!,
|
|
|
|
|
// 目标端口,使用配置中的 port 值,该值是必需的
|
|
|
|
|
port: config.port!,
|
|
|
|
|
},
|
|
|
|
|
// 代理服务器的配置
|
|
|
|
|
proxy: {
|
|
|
|
|
// 代理服务器的 IP 地址,通过解析配置中的 proxy.host 得到
|
|
|
|
|
ipaddress,
|
|
|
|
|
// 代理服务器的端口号,使用配置中的 proxy.port 值,该值是必需的
|
|
|
|
|
port: config.proxy!.port,
|
|
|
|
|
// 代理服务器的类型,根据配置中的 proxy.type 确定,支持 socks4 和 socks5
|
|
|
|
|
type: config.proxy!.type === 'socks4' ? 4 : 5,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
// 返回连接的可读流
|
|
|
|
|
return con.socket as NodeJS.ReadableStream;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// 如果连接失败,抛出错误
|
|
|
|
|
throw new Error(`Error while connecting to the the proxy: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建一个通过 HTTP 代理的网络连接。
|
|
|
|
|
*
|
|
|
|
|
* @param config - 包含连接配置的对象。
|
|
|
|
|
* @returns 一个可读流,表示与目标主机的连接。
|
|
|
|
|
* @throws {Error} 如果配置无效或连接失败,则抛出错误。
|
|
|
|
|
*/
|
|
|
|
|
export function http(config: FileSystemConfig): Promise<NodeJS.ReadableStream> {
|
|
|
|
|
// 记录创建 HTTP 代理连接的信息
|
|
|
|
|
Logging.info`Creating http proxy connection for ${config.name}`;
|
|
|
|
|
// 验证配置对象的有效性
|
|
|
|
|
validateConfig(config);
|
|
|
|
|
// 返回一个 Promise,用于处理连接结果
|
|
|
|
|
return new Promise<NodeJS.ReadableStream>((resolve, reject) => {
|
|
|
|
|
// 检查代理类型是否为 http
|
|
|
|
|
if (config.proxy!.type !== 'http') {
|
|
|
|
|
// 如果不是,拒绝 Promise 并抛出错误
|
|
|
|
|
reject(new Error(`Expected config.proxy.type' to be 'http'`));
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
// 记录连接信息
|
|
|
|
|
Logging.debug`\tConnecting to ${config.host}:${config.port} over http proxy at ${config.proxy!.host}:${config.proxy!.port}`;
|
|
|
|
|
// 创建到目标主机的 HTTP 连接
|
|
|
|
|
const req = request({
|
|
|
|
|
// 代理服务器的端口号,使用配置中的 proxy.port 值,该值是必需的
|
|
|
|
|
port: config.proxy!.port,
|
|
|
|
|
// 代理服务器的主机名,使用配置中的 proxy.host 值,该值是必需的
|
|
|
|
|
hostname: config.proxy!.host,
|
|
|
|
|
// 使用 CONNECT 方法建立隧道
|
|
|
|
|
method: 'CONNECT',
|
|
|
|
|
// 目标主机和端口,使用配置中的 host 和 port 值,该值是必需的
|
|
|
|
|
path: `${config.host}:${config.port}`,
|
|
|
|
|
});
|
|
|
|
|
// 结束请求
|
|
|
|
|
req.end();
|
|
|
|
|
// 监听连接事件
|
|
|
|
|
req.on('connect', (res, socket) => {
|
|
|
|
|
// 连接成功,解析 socket 并返回可读流
|
|
|
|
|
resolve(socket as NodeJS.ReadableStream);
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// 如果连接失败,拒绝 Promise 并抛出错误
|
|
|
|
|
reject(new Error(`Error while connecting to the the proxy: ${e.message}`));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|