@chenpu17/tcpmapping 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server.js ADDED
@@ -0,0 +1,886 @@
1
+ const net = require('net');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const express = require('express');
5
+ const session = require('express-session');
6
+ const bcrypt = require('bcryptjs');
7
+ const bodyParser = require('body-parser');
8
+
9
+ // 全局变量
10
+ let config = {};
11
+ const servers = new Map(); // 存储所有TCP转发服务器实例
12
+ const connections = new Map(); // 存储活跃连接信息
13
+ const app = express();
14
+
15
+ // 认证配置
16
+ let authConfig = {};
17
+
18
+ // 登录失败记录
19
+ const loginAttempts = new Map();
20
+
21
+ // 加载认证配置
22
+ function loadAuthConfig() {
23
+ try {
24
+ const authConfigPath = path.join(__dirname, 'auth-config.json');
25
+ const authConfigData = fs.readFileSync(authConfigPath, 'utf8');
26
+ return JSON.parse(authConfigData);
27
+ } catch (error) {
28
+ console.error('警告: 未找到认证配置文件,使用默认配置');
29
+ console.error('请运行 "node setup-password.js" 来设置安全密码');
30
+
31
+ // 返回默认配置(仅用于向后兼容)
32
+ return {
33
+ users: [{
34
+ username: 'admin',
35
+ passwordHash: bcrypt.hashSync('admin123', 10),
36
+ role: 'admin',
37
+ lastPasswordChange: new Date().toISOString(),
38
+ enabled: true
39
+ }],
40
+ passwordPolicy: {
41
+ minLength: 8,
42
+ requireUppercase: true,
43
+ requireLowercase: true,
44
+ requireNumbers: true,
45
+ requireSpecialChars: false,
46
+ passwordExpireDays: 90
47
+ },
48
+ sessionConfig: {
49
+ sessionTimeout: 24,
50
+ maxFailedAttempts: 5,
51
+ lockoutDuration: 30
52
+ }
53
+ };
54
+ }
55
+ }
56
+
57
+ // 验证用户密码
58
+ function validateUserCredentials(username, password) {
59
+ const user = authConfig.users.find(u => u.username === username && u.enabled);
60
+
61
+ if (!user) {
62
+ return { success: false, message: '用户不存在或已被禁用' };
63
+ }
64
+
65
+ // 检查密码是否过期
66
+ const passwordAge = Date.now() - new Date(user.lastPasswordChange).getTime();
67
+ const passwordExpireMs = authConfig.passwordPolicy.passwordExpireDays * 24 * 60 * 60 * 1000;
68
+
69
+ if (passwordAge > passwordExpireMs) {
70
+ return { success: false, message: '密码已过期,请联系管理员重置' };
71
+ }
72
+
73
+ // 验证密码
74
+ if (bcrypt.compareSync(password, user.passwordHash)) {
75
+ return { success: true, user: user };
76
+ } else {
77
+ return { success: false, message: '密码错误' };
78
+ }
79
+ }
80
+
81
+ // 检查登录尝试限制
82
+ function checkLoginAttempts(clientIP) {
83
+ const attempts = loginAttempts.get(clientIP) || { count: 0, lastAttempt: 0 };
84
+ const now = Date.now();
85
+ const lockoutTime = authConfig.sessionConfig.lockoutDuration * 60 * 1000;
86
+
87
+ // 如果在锁定期内
88
+ if (attempts.count >= authConfig.sessionConfig.maxFailedAttempts) {
89
+ if (now - attempts.lastAttempt < lockoutTime) {
90
+ const remainingTime = Math.ceil((lockoutTime - (now - attempts.lastAttempt)) / 1000 / 60);
91
+ return {
92
+ allowed: false,
93
+ message: `由于多次登录失败,账户已被锁定,请${remainingTime}分钟后再试`
94
+ };
95
+ } else {
96
+ // 锁定期已过,重置计数
97
+ loginAttempts.set(clientIP, { count: 0, lastAttempt: now });
98
+ }
99
+ }
100
+
101
+ return { allowed: true };
102
+ }
103
+
104
+ // 记录登录失败
105
+ function recordLoginFailure(clientIP) {
106
+ const attempts = loginAttempts.get(clientIP) || { count: 0, lastAttempt: 0 };
107
+ attempts.count++;
108
+ attempts.lastAttempt = Date.now();
109
+ loginAttempts.set(clientIP, attempts);
110
+ }
111
+
112
+ // 清除登录失败记录
113
+ function clearLoginFailures(clientIP) {
114
+ loginAttempts.delete(clientIP);
115
+ }
116
+
117
+ // 日志级别定义
118
+ const LOG_LEVELS = {
119
+ error: 0,
120
+ warn: 1,
121
+ info: 2,
122
+ debug: 3
123
+ };
124
+
125
+ // 读取配置文件
126
+ function loadConfig() {
127
+ try {
128
+ const configPath = path.join(__dirname, 'config.json');
129
+ const configData = fs.readFileSync(configPath, 'utf8');
130
+ const loadedConfig = JSON.parse(configData);
131
+
132
+ // 验证配置
133
+ if (!loadedConfig.mappings || !Array.isArray(loadedConfig.mappings)) {
134
+ loadedConfig.mappings = [];
135
+ }
136
+
137
+ // 添加默认WebUI配置
138
+ if (!loadedConfig.webui) {
139
+ loadedConfig.webui = {
140
+ enabled: true,
141
+ port: 9999,
142
+ host: '0.0.0.0'
143
+ };
144
+ }
145
+
146
+ return loadedConfig;
147
+ } catch (error) {
148
+ console.error('读取配置文件失败:', error.message);
149
+ // 返回默认配置
150
+ return {
151
+ mappings: [],
152
+ webui: {
153
+ enabled: true,
154
+ port: 9999,
155
+ host: '0.0.0.0'
156
+ },
157
+ logging: {
158
+ enabled: true,
159
+ level: 'info'
160
+ }
161
+ };
162
+ }
163
+ }
164
+
165
+ // 保存配置文件
166
+ function saveConfig(newConfig) {
167
+ try {
168
+ const configPath = path.join(__dirname, 'config.json');
169
+ fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
170
+ config = newConfig;
171
+ return true;
172
+ } catch (error) {
173
+ console.error('保存配置文件失败:', error.message);
174
+ return false;
175
+ }
176
+ }
177
+
178
+ // 日志输出函数
179
+ function log(level, message) {
180
+ if (config.logging && config.logging.enabled) {
181
+ const configLevel = config.logging.level || 'info';
182
+ const currentLevelValue = LOG_LEVELS[level] || 999;
183
+ const configLevelValue = LOG_LEVELS[configLevel] || 2;
184
+
185
+ // 只有当前日志级别的优先级 <= 配置级别优先级时才输出
186
+ if (currentLevelValue <= configLevelValue) {
187
+ const timestamp = new Date().toISOString();
188
+ console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
189
+ }
190
+ }
191
+ }
192
+
193
+ // 生成连接ID
194
+ function generateConnectionId() {
195
+ return Math.random().toString(36).substring(2, 15);
196
+ }
197
+
198
+ // 生成映射唯一ID
199
+ function generateMappingId() {
200
+ return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
201
+ }
202
+
203
+ // 验证端口号
204
+ function isValidPort(port) {
205
+ const p = parseInt(port);
206
+ return Number.isInteger(p) && p >= 1 && p <= 65535;
207
+ }
208
+
209
+ // 验证IP地址或主机名
210
+ function isValidHost(host) {
211
+ if (!host || typeof host !== 'string') return false;
212
+ // 允许 0.0.0.0, localhost, IP地址, 或域名
213
+ const ipPattern = /^(\d{1,3}\.){3}\d{1,3}$/;
214
+ const hostnamePattern = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
215
+ return ipPattern.test(host) || hostnamePattern.test(host);
216
+ }
217
+
218
+ // 验证映射配置
219
+ function validateMappingInput(mapping) {
220
+ const errors = [];
221
+
222
+ if (!mapping.name || mapping.name.trim().length === 0) {
223
+ errors.push('映射名称不能为空');
224
+ }
225
+
226
+ if (!mapping.listen || !isValidPort(mapping.listen.port)) {
227
+ errors.push('监听端口必须在 1-65535 范围内');
228
+ }
229
+
230
+ if (!mapping.target || !isValidHost(mapping.target.host)) {
231
+ errors.push('目标主机地址格式无效');
232
+ }
233
+
234
+ if (!mapping.target || !isValidPort(mapping.target.port)) {
235
+ errors.push('目标端口必须在 1-65535 范围内');
236
+ }
237
+
238
+ return {
239
+ isValid: errors.length === 0,
240
+ errors: errors
241
+ };
242
+ }
243
+
244
+ // 默认连接超时(毫秒)
245
+ const CONNECTION_TIMEOUT = 30000;
246
+ const IDLE_TIMEOUT = 300000; // 5分钟空闲超时
247
+
248
+ // 创建转发连接
249
+ function createForwardConnection(clientSocket, mapping) {
250
+ const connectionId = generateConnectionId();
251
+ const clientInfo = `${clientSocket.remoteAddress}:${clientSocket.remotePort}`;
252
+
253
+ // 记录连接信息
254
+ connections.set(connectionId, {
255
+ id: connectionId,
256
+ mappingName: mapping.name,
257
+ sourcePort: mapping.listen.port,
258
+ clientAddress: clientInfo,
259
+ targetAddress: `${mapping.target.host}:${mapping.target.port}`,
260
+ startTime: new Date(),
261
+ lastActivity: Date.now(),
262
+ bytesReceived: 0,
263
+ bytesSent: 0,
264
+ status: 'connecting'
265
+ });
266
+
267
+ const targetSocket = new net.Socket();
268
+
269
+ // 设置连接超时
270
+ targetSocket.setTimeout(CONNECTION_TIMEOUT);
271
+
272
+ // 设置socket超时(空闲超时)
273
+ clientSocket.setTimeout(IDLE_TIMEOUT);
274
+ targetSocket.setTimeout(IDLE_TIMEOUT);
275
+
276
+ // 更新活动时间
277
+ const updateActivity = () => {
278
+ const conn = connections.get(connectionId);
279
+ if (conn) {
280
+ conn.lastActivity = Date.now();
281
+ }
282
+ };
283
+
284
+ // 连接到目标服务器
285
+ targetSocket.connect(mapping.target.port, mapping.target.host, () => {
286
+ log('info', `[${mapping.name}] 已连接到目标服务器 ${mapping.target.host}:${mapping.target.port}`);
287
+ const conn = connections.get(connectionId);
288
+ if (conn) {
289
+ conn.status = 'connected';
290
+ connections.set(connectionId, conn);
291
+ }
292
+ });
293
+
294
+ // 处理超时
295
+ targetSocket.on('timeout', () => {
296
+ log('warn', `[${mapping.name}] 目标连接超时`);
297
+ connections.delete(connectionId);
298
+ targetSocket.destroy();
299
+ clientSocket.destroy();
300
+ });
301
+
302
+ clientSocket.on('timeout', () => {
303
+ log('warn', `[${mapping.name}] 客户端连接空闲超时`);
304
+ connections.delete(connectionId);
305
+ targetSocket.destroy();
306
+ clientSocket.destroy();
307
+ });
308
+
309
+ // 处理目标服务器连接错误
310
+ targetSocket.on('error', (err) => {
311
+ log('error', `[${mapping.name}] 目标服务器连接错误: ${err.message}`);
312
+ connections.delete(connectionId);
313
+ clientSocket.destroy();
314
+ });
315
+
316
+ // 处理目标服务器连接关闭
317
+ targetSocket.on('close', () => {
318
+ log('info', `[${mapping.name}] 目标服务器连接已关闭`);
319
+ connections.delete(connectionId);
320
+ clientSocket.destroy();
321
+ });
322
+
323
+ // 处理客户端连接关闭
324
+ clientSocket.on('close', () => {
325
+ log('info', `[${mapping.name}] 客户端连接已关闭`);
326
+ connections.delete(connectionId);
327
+ targetSocket.destroy();
328
+ });
329
+
330
+ // 处理客户端连接错误
331
+ clientSocket.on('error', (err) => {
332
+ log('error', `[${mapping.name}] 客户端连接错误: ${err.message}`);
333
+ connections.delete(connectionId);
334
+ targetSocket.destroy();
335
+ });
336
+
337
+ // 数据转发:客户端 -> 目标服务器
338
+ clientSocket.on('data', (data) => {
339
+ log('debug', `[${mapping.name}] 转发数据到目标服务器,大小: ${data.length} 字节`);
340
+ updateActivity();
341
+ const conn = connections.get(connectionId);
342
+ if (conn) {
343
+ conn.bytesReceived += data.length;
344
+ connections.set(connectionId, conn);
345
+ }
346
+ targetSocket.write(data);
347
+ });
348
+
349
+ // 数据转发:目标服务器 -> 客户端
350
+ targetSocket.on('data', (data) => {
351
+ log('debug', `[${mapping.name}] 转发数据到客户端,大小: ${data.length} 字节`);
352
+ updateActivity();
353
+ const conn = connections.get(connectionId);
354
+ if (conn) {
355
+ conn.bytesSent += data.length;
356
+ connections.set(connectionId, conn);
357
+ }
358
+ clientSocket.write(data);
359
+ });
360
+
361
+ return targetSocket;
362
+ }
363
+
364
+ // 创建单个端口转发服务器
365
+ function createForwardingServer(mapping) {
366
+ const server = net.createServer((clientSocket) => {
367
+ const clientInfo = `${clientSocket.remoteAddress}:${clientSocket.remotePort}`;
368
+ log('info', `[${mapping.name}] 新客户端连接: ${clientInfo}`);
369
+
370
+ // 创建转发连接
371
+ createForwardConnection(clientSocket, mapping);
372
+ });
373
+
374
+ // 处理服务器错误
375
+ server.on('error', (err) => {
376
+ log('error', `[${mapping.name}] 服务器错误: ${err.message}`);
377
+ if (err.code === 'EADDRINUSE') {
378
+ console.error(`端口 ${mapping.listen.port} 已被占用,请检查配置或停止占用该端口的程序`);
379
+ }
380
+ });
381
+
382
+ // 启动服务器
383
+ server.listen(mapping.listen.port, mapping.listen.host, () => {
384
+ log('info', `[${mapping.name}] 转发服务器已启动,监听地址: ${mapping.listen.host}:${mapping.listen.port}`);
385
+ log('info', `[${mapping.name}] 目标服务器: ${mapping.target.host}:${mapping.target.port}`);
386
+ console.log(`✓ [${mapping.name}] ${mapping.listen.port} -> ${mapping.target.host}:${mapping.target.port}`);
387
+ });
388
+
389
+ return server;
390
+ }
391
+
392
+ // 启动端口映射
393
+ function startMapping(mapping) {
394
+ const key = `${mapping.listen.host}:${mapping.listen.port}`;
395
+
396
+ if (servers.has(key)) {
397
+ return { success: false, message: '端口映射已存在' };
398
+ }
399
+
400
+ try {
401
+ const server = createForwardingServer(mapping);
402
+ servers.set(key, { server, mapping });
403
+ return { success: true, message: '端口映射已启动' };
404
+ } catch (error) {
405
+ return { success: false, message: error.message };
406
+ }
407
+ }
408
+
409
+ // 停止端口映射
410
+ function stopMapping(host, port) {
411
+ const key = `${host}:${port}`;
412
+
413
+ if (!servers.has(key)) {
414
+ return { success: false, message: '端口映射不存在' };
415
+ }
416
+
417
+ try {
418
+ const { server } = servers.get(key);
419
+ server.close();
420
+ servers.delete(key);
421
+
422
+ // 清理相关连接
423
+ for (const [id, conn] of connections) {
424
+ if (conn.sourcePort === port) {
425
+ connections.delete(id);
426
+ }
427
+ }
428
+
429
+ return { success: true, message: '端口映射已停止' };
430
+ } catch (error) {
431
+ return { success: false, message: error.message };
432
+ }
433
+ }
434
+
435
+ // 启动所有端口映射
436
+ function startAllMappings() {
437
+ config.mappings.forEach((mapping, index) => {
438
+ if (!mapping.listen || !mapping.target) {
439
+ console.error(`映射配置 ${index + 1} 缺少必要的 listen 或 target 配置`);
440
+ return;
441
+ }
442
+
443
+ if (!mapping.name) {
444
+ mapping.name = `映射${index + 1}`;
445
+ }
446
+
447
+ startMapping(mapping);
448
+ });
449
+ }
450
+
451
+ // 生成或读取 session secret
452
+ function getSessionSecret() {
453
+ const secretPath = path.join(__dirname, '.session-secret');
454
+ try {
455
+ if (fs.existsSync(secretPath)) {
456
+ return fs.readFileSync(secretPath, 'utf8').trim();
457
+ }
458
+ } catch (error) {
459
+ // 忽略读取错误
460
+ }
461
+ // 生成新的随机 secret
462
+ const crypto = require('crypto');
463
+ const secret = crypto.randomBytes(32).toString('hex');
464
+ try {
465
+ fs.writeFileSync(secretPath, secret, { mode: 0o600 });
466
+ } catch (error) {
467
+ console.warn('警告: 无法保存 session secret,重启后会话将失效');
468
+ }
469
+ return secret;
470
+ }
471
+
472
+ // 中间件配置
473
+ app.use(bodyParser.json());
474
+ app.use(bodyParser.urlencoded({ extended: true }));
475
+ app.use(session({
476
+ secret: getSessionSecret(),
477
+ resave: false,
478
+ saveUninitialized: false,
479
+ cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 } // 24小时
480
+ }));
481
+
482
+ // 静态文件服务
483
+ app.use(express.static('public'));
484
+
485
+ // 认证中间件
486
+ function requireAuth(req, res, next) {
487
+ if (req.session && req.session.authenticated) {
488
+ return next();
489
+ } else {
490
+ return res.status(401).json({ success: false, message: '需要登录' });
491
+ }
492
+ }
493
+
494
+ // 路由定义
495
+
496
+ // 主页
497
+ app.get('/', (req, res) => {
498
+ if (req.session && req.session.authenticated) {
499
+ res.redirect('/dashboard');
500
+ } else {
501
+ res.redirect('/login');
502
+ }
503
+ });
504
+
505
+ // 登录页面
506
+ app.get('/login', (req, res) => {
507
+ res.sendFile(path.join(__dirname, 'public', 'login.html'));
508
+ });
509
+
510
+ // 管理面板
511
+ app.get('/dashboard', requireAuth, (req, res) => {
512
+ res.sendFile(path.join(__dirname, 'public', 'dashboard.html'));
513
+ });
514
+
515
+ // API: 登录
516
+ app.post('/api/login', (req, res) => {
517
+ const { username, password } = req.body;
518
+ const clientIP = req.ip || req.connection.remoteAddress;
519
+
520
+ // 检查登录尝试限制
521
+ const attemptCheck = checkLoginAttempts(clientIP);
522
+ if (!attemptCheck.allowed) {
523
+ return res.status(429).json({ success: false, message: attemptCheck.message });
524
+ }
525
+
526
+ // 验证用户凭据
527
+ const validation = validateUserCredentials(username, password);
528
+
529
+ if (validation.success) {
530
+ // 登录成功
531
+ req.session.authenticated = true;
532
+ req.session.username = username;
533
+ req.session.userRole = validation.user.role;
534
+
535
+ // 清除登录失败记录
536
+ clearLoginFailures(clientIP);
537
+
538
+ log('info', `用户 ${username} 从 ${clientIP} 登录成功`);
539
+ res.json({ success: true, message: '登录成功' });
540
+ } else {
541
+ // 登录失败
542
+ recordLoginFailure(clientIP);
543
+
544
+ log('warn', `用户 ${username} 从 ${clientIP} 登录失败: ${validation.message}`);
545
+ res.status(401).json({ success: false, message: validation.message });
546
+ }
547
+ });
548
+
549
+ // API: 登出
550
+ app.post('/api/logout', (req, res) => {
551
+ req.session.destroy();
552
+ res.json({ success: true, message: '已登出' });
553
+ });
554
+
555
+ // 获取指定端口的连接数量
556
+ function getConnectionCountByPort(port) {
557
+ let count = 0;
558
+ for (const conn of connections.values()) {
559
+ if (conn.sourcePort === port) {
560
+ count++;
561
+ }
562
+ }
563
+ return count;
564
+ }
565
+
566
+ // 获取指定端口的连接统计
567
+ function getConnectionStatsByPort(port) {
568
+ let count = 0;
569
+ let bytesReceived = 0;
570
+ let bytesSent = 0;
571
+ for (const conn of connections.values()) {
572
+ if (conn.sourcePort === port) {
573
+ count++;
574
+ bytesReceived += conn.bytesReceived;
575
+ bytesSent += conn.bytesSent;
576
+ }
577
+ }
578
+ return { count, bytesReceived, bytesSent };
579
+ }
580
+
581
+ // API: 获取端口映射列表
582
+ app.get('/api/mappings', requireAuth, (req, res) => {
583
+ const mappings = config.mappings.map(mapping => {
584
+ const stats = getConnectionStatsByPort(mapping.listen.port);
585
+ return {
586
+ ...mapping,
587
+ status: servers.has(`${mapping.listen.host}:${mapping.listen.port}`) ? 'running' : 'stopped',
588
+ connectionCount: stats.count,
589
+ bytesReceived: stats.bytesReceived,
590
+ bytesSent: stats.bytesSent
591
+ };
592
+ });
593
+ res.json({ success: true, data: mappings });
594
+ });
595
+
596
+ // API: 添加端口映射
597
+ app.post('/api/mappings', requireAuth, (req, res) => {
598
+ const { name, listen, target } = req.body;
599
+
600
+ const mapping = {
601
+ name: name || '',
602
+ listen: {
603
+ host: (listen && listen.host) || '0.0.0.0',
604
+ port: listen ? parseInt(listen.port) : 0
605
+ },
606
+ target: {
607
+ host: (target && target.host) || '',
608
+ port: target ? parseInt(target.port) : 0
609
+ }
610
+ };
611
+
612
+ // 输入验证
613
+ const validation = validateMappingInput(mapping);
614
+ if (!validation.isValid) {
615
+ return res.status(400).json({ success: false, message: validation.errors.join('; ') });
616
+ }
617
+
618
+ // 检查端口是否已被使用
619
+ const exists = config.mappings.some(m =>
620
+ m.listen.host === mapping.listen.host && m.listen.port === mapping.listen.port
621
+ );
622
+
623
+ if (exists) {
624
+ return res.status(400).json({ success: false, message: '端口映射已存在' });
625
+ }
626
+
627
+ // 添加唯一ID
628
+ mapping.id = generateMappingId();
629
+
630
+ config.mappings.push(mapping);
631
+
632
+ if (saveConfig(config)) {
633
+ const result = startMapping(mapping);
634
+ res.json({ ...result, data: mapping });
635
+ } else {
636
+ res.status(500).json({ success: false, message: '保存配置失败' });
637
+ }
638
+ });
639
+
640
+ // API: 删除端口映射 (支持ID或索引)
641
+ app.delete('/api/mappings/:identifier', requireAuth, (req, res) => {
642
+ const identifier = req.params.identifier;
643
+ let mappingIndex = -1;
644
+
645
+ // 先尝试通过ID查找
646
+ mappingIndex = config.mappings.findIndex(m => m.id === identifier);
647
+
648
+ // 如果没找到,尝试作为索引使用(向后兼容)
649
+ if (mappingIndex === -1) {
650
+ const index = parseInt(identifier);
651
+ if (!isNaN(index) && index >= 0 && index < config.mappings.length) {
652
+ mappingIndex = index;
653
+ }
654
+ }
655
+
656
+ if (mappingIndex === -1) {
657
+ return res.status(404).json({ success: false, message: '映射不存在' });
658
+ }
659
+
660
+ const mapping = config.mappings[mappingIndex];
661
+
662
+ // 停止服务器
663
+ stopMapping(mapping.listen.host, mapping.listen.port);
664
+
665
+ // 从配置中移除
666
+ config.mappings.splice(mappingIndex, 1);
667
+
668
+ if (saveConfig(config)) {
669
+ res.json({ success: true, message: '端口映射已删除' });
670
+ } else {
671
+ res.status(500).json({ success: false, message: '保存配置失败' });
672
+ }
673
+ });
674
+
675
+ // API: 启动单个映射
676
+ app.post('/api/mappings/:identifier/start', requireAuth, (req, res) => {
677
+ const identifier = req.params.identifier;
678
+ let mapping = config.mappings.find(m => m.id === identifier);
679
+
680
+ // 向后兼容:尝试索引
681
+ if (!mapping) {
682
+ const index = parseInt(identifier);
683
+ if (!isNaN(index) && index >= 0 && index < config.mappings.length) {
684
+ mapping = config.mappings[index];
685
+ }
686
+ }
687
+
688
+ if (!mapping) {
689
+ return res.status(404).json({ success: false, message: '映射不存在' });
690
+ }
691
+
692
+ const result = startMapping(mapping);
693
+ res.json(result);
694
+ });
695
+
696
+ // API: 停止单个映射
697
+ app.post('/api/mappings/:identifier/stop', requireAuth, (req, res) => {
698
+ const identifier = req.params.identifier;
699
+ let mapping = config.mappings.find(m => m.id === identifier);
700
+
701
+ // 向后兼容:尝试索引
702
+ if (!mapping) {
703
+ const index = parseInt(identifier);
704
+ if (!isNaN(index) && index >= 0 && index < config.mappings.length) {
705
+ mapping = config.mappings[index];
706
+ }
707
+ }
708
+
709
+ if (!mapping) {
710
+ return res.status(404).json({ success: false, message: '映射不存在' });
711
+ }
712
+
713
+ const result = stopMapping(mapping.listen.host, mapping.listen.port);
714
+ res.json(result);
715
+ });
716
+
717
+ // API: 更新映射配置
718
+ app.put('/api/mappings/:identifier', requireAuth, (req, res) => {
719
+ const identifier = req.params.identifier;
720
+ let mappingIndex = config.mappings.findIndex(m => m.id === identifier);
721
+
722
+ // 向后兼容:尝试索引
723
+ if (mappingIndex === -1) {
724
+ const index = parseInt(identifier);
725
+ if (!isNaN(index) && index >= 0 && index < config.mappings.length) {
726
+ mappingIndex = index;
727
+ }
728
+ }
729
+
730
+ if (mappingIndex === -1) {
731
+ return res.status(404).json({ success: false, message: '映射不存在' });
732
+ }
733
+
734
+ const oldMapping = config.mappings[mappingIndex];
735
+ const { name, listen, target } = req.body;
736
+
737
+ const newMapping = {
738
+ id: oldMapping.id || generateMappingId(),
739
+ name: name || oldMapping.name,
740
+ listen: {
741
+ host: (listen && listen.host) || oldMapping.listen.host,
742
+ port: listen ? parseInt(listen.port) : oldMapping.listen.port
743
+ },
744
+ target: {
745
+ host: (target && target.host) || oldMapping.target.host,
746
+ port: target ? parseInt(target.port) : oldMapping.target.port
747
+ }
748
+ };
749
+
750
+ // 验证新配置
751
+ const validation = validateMappingInput(newMapping);
752
+ if (!validation.isValid) {
753
+ return res.status(400).json({ success: false, message: validation.errors.join('; ') });
754
+ }
755
+
756
+ // 检查新端口是否与其他映射冲突(排除自身)
757
+ const portChanged = oldMapping.listen.port !== newMapping.listen.port ||
758
+ oldMapping.listen.host !== newMapping.listen.host;
759
+ if (portChanged) {
760
+ const exists = config.mappings.some((m, i) =>
761
+ i !== mappingIndex &&
762
+ m.listen.host === newMapping.listen.host &&
763
+ m.listen.port === newMapping.listen.port
764
+ );
765
+ if (exists) {
766
+ return res.status(400).json({ success: false, message: '端口映射已存在' });
767
+ }
768
+ }
769
+
770
+ // 停止旧的映射
771
+ stopMapping(oldMapping.listen.host, oldMapping.listen.port);
772
+
773
+ // 更新配置
774
+ config.mappings[mappingIndex] = newMapping;
775
+
776
+ if (saveConfig(config)) {
777
+ // 启动新的映射
778
+ const result = startMapping(newMapping);
779
+ res.json({ ...result, data: newMapping });
780
+ } else {
781
+ res.status(500).json({ success: false, message: '保存配置失败' });
782
+ }
783
+ });
784
+
785
+ // API: 获取连接状态
786
+ app.get('/api/connections', requireAuth, (req, res) => {
787
+ const connectionList = Array.from(connections.values());
788
+
789
+ // 按端口分组统计
790
+ const stats = {};
791
+ connectionList.forEach(conn => {
792
+ if (!stats[conn.sourcePort]) {
793
+ stats[conn.sourcePort] = {
794
+ port: conn.sourcePort,
795
+ mappingName: conn.mappingName,
796
+ totalConnections: 0,
797
+ connections: []
798
+ };
799
+ }
800
+ stats[conn.sourcePort].totalConnections++;
801
+ stats[conn.sourcePort].connections.push(conn);
802
+ });
803
+
804
+ res.json({
805
+ success: true,
806
+ data: {
807
+ total: connectionList.length,
808
+ byPort: Object.values(stats)
809
+ }
810
+ });
811
+ });
812
+
813
+ // API: 获取系统状态
814
+ app.get('/api/status', requireAuth, (req, res) => {
815
+ res.json({
816
+ success: true,
817
+ data: {
818
+ totalMappings: config.mappings.length,
819
+ activeMappings: servers.size,
820
+ totalConnections: connections.size,
821
+ uptime: process.uptime(),
822
+ memoryUsage: process.memoryUsage()
823
+ }
824
+ });
825
+ });
826
+
827
+ // API: 健康检查(无需认证,用于监控)
828
+ app.get('/api/health', (req, res) => {
829
+ res.json({
830
+ status: 'ok',
831
+ timestamp: new Date().toISOString(),
832
+ uptime: process.uptime(),
833
+ activeMappings: servers.size,
834
+ activeConnections: connections.size
835
+ });
836
+ });
837
+
838
+ // 启动程序
839
+ function startServer() {
840
+ // 加载配置
841
+ config = loadConfig();
842
+
843
+ // 加载认证配置
844
+ authConfig = loadAuthConfig();
845
+
846
+ // 启动TCP转发服务器
847
+ console.log('==========================================');
848
+ console.log('TCP转发服务器正在启动...');
849
+ console.log('端口映射配置:');
850
+ startAllMappings();
851
+ console.log('==========================================');
852
+
853
+ // 启动WebUI服务器
854
+ if (config.webui && config.webui.enabled) {
855
+ const webPort = config.webui.port || 9999;
856
+ const webHost = config.webui.host || '0.0.0.0';
857
+
858
+ app.listen(webPort, webHost, () => {
859
+ console.log(`WebUI管理界面已启动: http://${webHost}:${webPort}`);
860
+ console.log('==========================================');
861
+ });
862
+ }
863
+ }
864
+
865
+ // 优雅关闭
866
+ function gracefulShutdown() {
867
+ log('info', '收到停止信号,正在关闭所有服务器...');
868
+ console.log('正在停止所有服务器...');
869
+
870
+ // 停止所有TCP转发服务器
871
+ for (const [key, { server }] of servers) {
872
+ server.close();
873
+ }
874
+
875
+ // 清理连接
876
+ connections.clear();
877
+
878
+ console.log('所有服务器已停止');
879
+ process.exit(0);
880
+ }
881
+
882
+ process.on('SIGINT', gracefulShutdown);
883
+ process.on('SIGTERM', gracefulShutdown);
884
+
885
+ // 启动服务器
886
+ startServer();