Fix TCP socket API and skip HTTP port testing in network diagnostic
- Import connect from cloudflare:sockets instead of globalThis - Fix socket.opened / readable / writable API usage - Skip TCP test for HTTP ports (80/443) since fetch HEAD already covers them; only test user-specified non-HTTP ports (e.g. SSH, DB) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -163,7 +163,7 @@ export class TroubleshootAgent extends BaseAgent<TroubleshootSession> {
|
|||||||
ports: {
|
ports: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: { type: 'number' },
|
items: { type: 'number' },
|
||||||
description: '추가로 테스트할 TCP 포트 목록 (기본: 80, 443)',
|
description: '추가로 TCP 연결 테스트할 포트 목록 (예: [22, 3306, 5432]). HTTP 포트(80/443)는 HTTP 체크로 커버되므로 제외됨',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ['domain'],
|
required: ['domain'],
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
* - TCP 포트 연결 테스트
|
* - TCP 포트 연결 테스트
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { connect } from 'cloudflare:sockets';
|
||||||
import type { NetworkDiagnosticReport, DnsResolverResult, IspDnsBlockResult, HttpCheckResult, TcpCheckResult } from '../types';
|
import type { NetworkDiagnosticReport, DnsResolverResult, IspDnsBlockResult, HttpCheckResult, TcpCheckResult } from '../types';
|
||||||
import { createLogger } from '../utils/logger';
|
import { createLogger } from '../utils/logger';
|
||||||
|
|
||||||
@@ -223,20 +224,17 @@ function parseDnsResponse(raw: Uint8Array): string[] {
|
|||||||
* TCP 소켓으로 DNS 서버에 질의
|
* TCP 소켓으로 DNS 서버에 질의
|
||||||
*/
|
*/
|
||||||
async function queryDnsTcp(serverIp: string, query: Uint8Array): Promise<string[]> {
|
async function queryDnsTcp(serverIp: string, query: Uint8Array): Promise<string[]> {
|
||||||
const socket = (globalThis as unknown as {
|
const socket = connect({ hostname: serverIp, port: 53 });
|
||||||
connect: (opts: { hostname: string; port: number }) => {
|
|
||||||
opened: Promise<{ readable: ReadableStream; writable: WritableStream }>;
|
|
||||||
close: () => void;
|
|
||||||
};
|
|
||||||
}).connect({ hostname: serverIp, port: 53 });
|
|
||||||
|
|
||||||
const { readable, writable } = await socket.opened;
|
// Wait for connection to establish
|
||||||
|
await socket.opened;
|
||||||
|
|
||||||
const writer = writable.getWriter();
|
// readable/writable are on the socket object directly
|
||||||
|
const writer = socket.writable.getWriter();
|
||||||
await writer.write(query);
|
await writer.write(query);
|
||||||
writer.releaseLock();
|
writer.releaseLock();
|
||||||
|
|
||||||
const reader = readable.getReader();
|
const reader = socket.readable.getReader();
|
||||||
const chunks: Uint8Array[] = [];
|
const chunks: Uint8Array[] = [];
|
||||||
let totalLength = 0;
|
let totalLength = 0;
|
||||||
|
|
||||||
@@ -256,7 +254,7 @@ async function queryDnsTcp(serverIp: string, query: Uint8Array): Promise<string[
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
reader.releaseLock();
|
reader.releaseLock();
|
||||||
socket.close();
|
await socket.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine chunks
|
// Combine chunks
|
||||||
@@ -357,15 +355,21 @@ async function checkHttp(domain: string): Promise<HttpCheckResult[]> {
|
|||||||
// TCP Port Test (connect API — ping substitute)
|
// TCP Port Test (connect API — ping substitute)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const DEFAULT_PORTS = [80, 443];
|
// Cloudflare Workers의 connect()는 HTTP 포트(80/443)에 직접 연결 불가
|
||||||
|
// (프록시 차단됨). HTTP 체크로 커버되므로 TCP는 사용자 지정 포트만 테스트.
|
||||||
|
const HTTP_PORTS = new Set([80, 443, 8080, 8443]);
|
||||||
|
|
||||||
async function checkTcpPorts(domain: string, ports?: number[]): Promise<TcpCheckResult[]> {
|
async function checkTcpPorts(domain: string, ports?: number[]): Promise<TcpCheckResult[]> {
|
||||||
|
// 사용자가 포트를 지정하지 않으면 TCP 테스트 스킵 (HTTP 체크로 충분)
|
||||||
|
const filteredPorts = (ports || []).filter((p) => !HTTP_PORTS.has(p));
|
||||||
|
if (filteredPorts.length === 0) return [];
|
||||||
|
|
||||||
// Resolve domain first to get IP for connect()
|
// Resolve domain first to get IP for connect()
|
||||||
const dohResult = await resolveDoh(domain);
|
const dohResult = await resolveDoh(domain);
|
||||||
const ip = dohResult[0]?.records[0]?.value;
|
const ip = dohResult[0]?.records[0]?.value;
|
||||||
|
|
||||||
if (!ip) {
|
if (!ip) {
|
||||||
return (ports || DEFAULT_PORTS).map((port) => ({
|
return filteredPorts.map((port) => ({
|
||||||
host: domain,
|
host: domain,
|
||||||
port,
|
port,
|
||||||
connected: false,
|
connected: false,
|
||||||
@@ -375,22 +379,17 @@ async function checkTcpPorts(domain: string, ports?: number[]): Promise<TcpCheck
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
(ports || DEFAULT_PORTS).map(async (port) => {
|
filteredPorts.map(async (port) => {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
try {
|
try {
|
||||||
const timeoutPromise = new Promise<never>((_, reject) =>
|
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('timeout')), CHECK_TIMEOUT_MS)
|
setTimeout(() => reject(new Error('timeout')), CHECK_TIMEOUT_MS)
|
||||||
);
|
);
|
||||||
|
|
||||||
const socket = (globalThis as unknown as {
|
const socket = connect({ hostname: ip, port });
|
||||||
connect: (opts: { hostname: string; port: number }) => {
|
|
||||||
opened: Promise<unknown>;
|
|
||||||
close: () => void;
|
|
||||||
};
|
|
||||||
}).connect({ hostname: ip, port });
|
|
||||||
|
|
||||||
await Promise.race([socket.opened, timeoutPromise]);
|
await Promise.race([socket.opened, timeoutPromise]);
|
||||||
socket.close();
|
await socket.close();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
host: domain,
|
host: domain,
|
||||||
|
|||||||
Reference in New Issue
Block a user