update
This commit is contained in:
1 parent
da8fb57bfb
commit
bba75c7dc6
35 files changed
+92592
-500
No files matched your search
@@ -0,0 +1,21 @@
|
||||
const db = require('../database');
|
||||
|
||||
const d = db.getDB();
|
||||
|
||||
const tables = d.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all();
|
||||
console.log('=== ALL TABLES ===');
|
||||
tables.forEach(t => {
|
||||
try {
|
||||
const cnt = d.prepare('SELECT COUNT(*) as c FROM ' + t.name).get().c;
|
||||
const cols = d.prepare('PRAGMA table_info(' + t.name + ')').all().map(c => c.name);
|
||||
console.log(t.name + ' (' + cnt + ' rows)');
|
||||
console.log(' cols: ' + cols.join(', '));
|
||||
if (cnt > 0) {
|
||||
const sample = d.prepare('SELECT * FROM ' + t.name + ' LIMIT 1').get();
|
||||
console.log(' sample: ' + JSON.stringify(sample));
|
||||
}
|
||||
console.log('');
|
||||
} catch(e) {
|
||||
console.log(t.name + ': ERROR ' + e.message);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,199 @@
|
||||
// TDD Test: Verifikasi fix untuk Bug #1 (Devices) dan Bug #2 (Flows)
|
||||
// Jalankan: node backend/tests/test_bug_fixes.js
|
||||
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../.env.local') });
|
||||
|
||||
const API_KEY = process.env.NETIFY_API_KEY;
|
||||
const SITE_UUID = process.env.NETIFY_SITE_UUID;
|
||||
const BASE = 'https://informatics.netify.ai/api/v1';
|
||||
const axios = require('axios');
|
||||
|
||||
const headers = {
|
||||
'x-api-key': API_KEY,
|
||||
'x-net-site': SITE_UUID,
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(condition, message) {
|
||||
if (condition) {
|
||||
console.log(` ✅ PASS: ${message}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.error(` ❌ FAIL: ${message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
async function testDevicesBugFix() {
|
||||
console.log('\n=== BUG #1: DEVICES - Download/Upload/Type/OS Fix ===');
|
||||
|
||||
const [dlResp, ulResp, devResp, flowsResp] = await Promise.all([
|
||||
axios.get(`${BASE}/data/stats/top/local_ip/download`, { headers, params: { filter_interval: 1440, settings_limit: 500 }, timeout: 15000 }),
|
||||
axios.get(`${BASE}/data/stats/top/local_ip/upload`, { headers, params: { filter_interval: 1440, settings_limit: 500 }, timeout: 15000 }),
|
||||
axios.get(`${BASE}/intelligence/discovery/devices`, { headers, params: { settings_limit: 500 }, timeout: 15000 }),
|
||||
axios.get(`${BASE}/data/flows`, { headers, params: { settings_limit: 500 }, timeout: 15000 }),
|
||||
]);
|
||||
|
||||
const dlData = dlResp.data?.data || [];
|
||||
const ulData = ulResp.data?.data || [];
|
||||
const devData = devResp.data?.data || [];
|
||||
const flowData = flowsResp.data?.data || [];
|
||||
|
||||
console.log(` Total IPs in download stats: ${dlData.length}`);
|
||||
console.log(` Total IPs in upload stats: ${ulData.length}`);
|
||||
console.log(` Total discovered devices: ${devData.length}`);
|
||||
console.log(` Total flows: ${flowData.length}`);
|
||||
|
||||
// Build maps
|
||||
const dlMap = {};
|
||||
for (const r of dlData) {
|
||||
if (r.local_ip?.address) dlMap[r.local_ip.address] = r.download;
|
||||
}
|
||||
const ulMap = {};
|
||||
for (const r of ulData) {
|
||||
if (r.local_ip?.address) ulMap[r.local_ip.address] = r.upload;
|
||||
}
|
||||
// Flows fallback map
|
||||
const dlFlowMap = {};
|
||||
const ulFlowMap = {};
|
||||
for (const f of flowData) {
|
||||
const ip = f.local_ip?.address;
|
||||
if (!ip) continue;
|
||||
if (!dlMap[ip]) dlFlowMap[ip] = (dlFlowMap[ip] || 0) + (f.download || 0);
|
||||
if (!ulMap[ip]) ulFlowMap[ip] = (ulFlowMap[ip] || 0) + (f.upload || 0);
|
||||
}
|
||||
|
||||
// Test: devices dengan non-zero bandwidth setelah enrichment
|
||||
const enriched = devData.map(r => {
|
||||
const ip = r.ip?.address;
|
||||
const dl = ip ? (dlMap[ip] || dlFlowMap[ip] || 0) : 0;
|
||||
const ul = ip ? (ulMap[ip] || ulFlowMap[ip] || 0) : 0;
|
||||
const type = r.discovery_type?.label;
|
||||
const os = r.discovery_os?.label;
|
||||
return { ip, dl, ul, type, os };
|
||||
}).sort((a, b) => (b.dl + b.ul) - (a.dl + a.ul));
|
||||
|
||||
const withBandwidth = enriched.filter(d => d.dl > 0 || d.ul > 0);
|
||||
const withType = enriched.filter(d => d.type && d.type !== 'Unknown' && d.type !== 'Unclassified');
|
||||
const withOs = enriched.filter(d => d.os && d.os !== 'Unknown' && d.os !== 'Unclassified');
|
||||
|
||||
assert(enriched.length > 0, `Devices tersedia: ${enriched.length} entries`);
|
||||
assert(withBandwidth.length > 0, `Devices dengan bandwidth non-zero: ${withBandwidth.length}/${enriched.length}`);
|
||||
assert(withType.length > 0, `Devices dengan type terdeteksi: ${withType.length}/${enriched.length}`);
|
||||
assert(withOs.length > 0, `Devices dengan OS terdeteksi: ${withOs.length}/${enriched.length}`);
|
||||
|
||||
// Print top 5 devices
|
||||
console.log('\n Top 5 devices after fix:');
|
||||
for (const d of enriched.slice(0, 5)) {
|
||||
console.log(` IP=${d.ip}, DL=${(d.dl / 1024 / 1024).toFixed(2)}MB, UL=${(d.ul / 1024 / 1024).toFixed(2)}MB, type=${d.type || '-'}, os=${d.os || '-'}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testFlowsBugFix() {
|
||||
console.log('\n=== BUG #2: FLOWS - App/Domain Column Fix ===');
|
||||
|
||||
const [flowsResp, tslSniResp] = await Promise.all([
|
||||
axios.get(`${BASE}/data/flows`, { headers, params: { settings_limit: 50 }, timeout: 15000 }),
|
||||
axios.get(`${BASE}/data/stats/top/tls_sni/download`, { headers, params: { filter_interval: 1440, settings_limit: 50 }, timeout: 15000 }),
|
||||
]);
|
||||
|
||||
const flowData = flowsResp.data?.data || [];
|
||||
const sniData = tslSniResp.data?.data || [];
|
||||
|
||||
const PORT_SERVICE_MAP = {
|
||||
80: 'HTTP', 443: 'HTTPS / TLS', 8080: 'HTTP Alt', 8443: 'HTTPS Alt',
|
||||
53: 'DNS', 5353: 'mDNS', 853: 'DNS-over-TLS',
|
||||
22: 'SSH', 23: 'Telnet', 3389: 'RDP', 5900: 'VNC',
|
||||
9993: 'ZeroTier VPN', 3478: 'STUN/TURN', 5004: 'RTP Media',
|
||||
5060: 'SIP', 5061: 'SIP TLS', 1194: 'OpenVPN', 51820: 'WireGuard',
|
||||
500: 'IPSec IKE', 4500: 'IPSec NAT-T', 1723: 'PPTP', 1701: 'L2TP',
|
||||
67: 'DHCP', 68: 'DHCP Client', 123: 'NTP', 161: 'SNMP',
|
||||
};
|
||||
|
||||
const sniList = sniData.map(r => r.tls_sni || '').filter(s => s.trim() !== '');
|
||||
console.log(` TLS SNI hostnames available: ${sniList.length}`);
|
||||
|
||||
const enrichedFlows = flowData.map(r => {
|
||||
const port = r.remote_port ?? null;
|
||||
const portService = port ? (PORT_SERVICE_MAP[port] ?? `Port ${port}`) : null;
|
||||
let domain = null;
|
||||
let appLabel = portService;
|
||||
if ((port === 443 || port === 8443) && sniList.length > 0) {
|
||||
domain = sniList[0];
|
||||
}
|
||||
return { src: r.local_ip?.address, dst: r.remote_ip?.address, port, appLabel, domain };
|
||||
});
|
||||
|
||||
const withAppLabel = enrichedFlows.filter(f => f.appLabel !== null);
|
||||
|
||||
assert(enrichedFlows.length > 0, `Flows tersedia: ${enrichedFlows.length} entries`);
|
||||
assert(withAppLabel.length > 0, `Flows dengan app_label terisi (port-based): ${withAppLabel.length}/${enrichedFlows.length}`);
|
||||
|
||||
// Count unique app labels
|
||||
const labelCounts = {};
|
||||
for (const f of enrichedFlows) {
|
||||
const k = f.appLabel || 'Unknown';
|
||||
labelCounts[k] = (labelCounts[k] || 0) + 1;
|
||||
}
|
||||
console.log(' App label distribution:', JSON.stringify(labelCounts));
|
||||
|
||||
// Print sample flows
|
||||
console.log('\n Sample flows after fix:');
|
||||
for (const f of enrichedFlows.slice(0, 5)) {
|
||||
console.log(` ${f.src} -> ${f.dst}:${f.port} | app="${f.appLabel || '-'}" domain="${f.domain || '-'}"`);
|
||||
}
|
||||
}
|
||||
|
||||
async function testBackendAPI() {
|
||||
console.log('\n=== BACKEND API VERIFICATION ===');
|
||||
try {
|
||||
const r = await axios.get('http://localhost:3001/api/dashboard/flows?limit=10', { timeout: 5000 });
|
||||
const flows = r.data?.data || [];
|
||||
assert(r.data?.ok === true, 'Backend /flows endpoint responds OK');
|
||||
assert(flows.length > 0, `Flows dari backend: ${flows.length} entries`);
|
||||
const withApp = flows.filter(f => f.app_label !== null);
|
||||
assert(withApp.length > 0, `Flows dengan app_label dari backend: ${withApp.length}/${flows.length}`);
|
||||
console.log(' Sample from backend:');
|
||||
for (const f of flows.slice(0, 3)) {
|
||||
console.log(` ${f.src_ip} -> ${f.dst_ip}:${f.dst_port} | app="${f.app_label || '-'}" domain="${f.domain || '-'}" dl=${f.bytes_download} ul=${f.bytes_upload}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(` Backend API test skipped: ${e.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const r = await axios.get('http://localhost:3001/api/dashboard/devices?limit=10', { timeout: 5000 });
|
||||
const devs = r.data?.data || [];
|
||||
assert(r.data?.ok === true, 'Backend /devices endpoint responds OK');
|
||||
assert(devs.length > 0, `Devices dari backend: ${devs.length} entries`);
|
||||
const withType = devs.filter(d => d.device_type !== null);
|
||||
const withOs = devs.filter(d => d.os_label !== null);
|
||||
const withBw = devs.filter(d => d.download > 0 || d.upload > 0);
|
||||
assert(withBw.length > 0, `Devices dengan bandwidth non-zero dari backend: ${withBw.length}/${devs.length}`);
|
||||
console.log(' Sample devices from backend:');
|
||||
for (const d of devs.slice(0, 3)) {
|
||||
console.log(` IP=${d.ip_address}, DL=${(d.download / 1024 / 1024).toFixed(2)}MB, type=${d.device_type || '-'}, os=${d.os_label || '-'}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(` Backend /devices test skipped: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log('=== TDD: Bug Fix Verification Tests ===\n');
|
||||
await testDevicesBugFix();
|
||||
await testFlowsBugFix();
|
||||
await testBackendAPI();
|
||||
|
||||
console.log(`\n=== RESULT: ${passed} passed, ${failed} failed ===`);
|
||||
if (failed > 0) process.exit(1);
|
||||
}
|
||||
|
||||
run().catch(e => {
|
||||
console.error('FATAL:', e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* TDD Test Suite: Validasi output file netify_data_export.txt
|
||||
*
|
||||
* Test ini mendefinisikan ATURAN yang harus dipenuhi oleh file output:
|
||||
* 1. File harus ada
|
||||
* 2. File harus tidak kosong
|
||||
* 3. File harus berisi section untuk setiap tabel DB
|
||||
* 4. Setiap section harus memiliki header, jumlah baris, dan data
|
||||
* 5. Data harus berupa JSON valid per baris (JSONL)
|
||||
* 6. File harus berisi metadata (tanggal export, versi, jumlah tabel)
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const OUTPUT_FILE = path.join(__dirname, '../../netify_data_export.txt');
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const results = [];
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(' ✅ PASS:', name);
|
||||
passed++;
|
||||
results.push({ name, status: 'PASS' });
|
||||
} catch (e) {
|
||||
console.log(' ❌ FAIL:', name);
|
||||
console.log(' Reason:', e.message);
|
||||
failed++;
|
||||
results.push({ name, status: 'FAIL', reason: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) throw new Error(message || 'Assertion failed');
|
||||
}
|
||||
|
||||
function assertEqual(a, b, message) {
|
||||
if (a !== b) throw new Error(message || `Expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}`);
|
||||
}
|
||||
|
||||
function assertGreaterThan(a, b, message) {
|
||||
if (a <= b) throw new Error(message || `Expected ${a} > ${b}`);
|
||||
}
|
||||
|
||||
// ─── Required tables that must appear in the export ─────────────────────────
|
||||
const REQUIRED_TABLES = [
|
||||
'app_categories',
|
||||
'bandwidth_apps',
|
||||
'bandwidth_countries',
|
||||
'bandwidth_protocols',
|
||||
'bandwidth_timeline',
|
||||
'bittorrent_hashes',
|
||||
'cities',
|
||||
'continents',
|
||||
'devices',
|
||||
'dhcp_fingerprints',
|
||||
'discovery_os',
|
||||
'dns_queries',
|
||||
'events',
|
||||
'flow_origins',
|
||||
'flow_types',
|
||||
'flows',
|
||||
'http_user_agents',
|
||||
'intel_crypto_mining',
|
||||
'intel_device_discovery',
|
||||
'intel_encryption_audit',
|
||||
'intel_insecure_protocols',
|
||||
'intel_ip_reputation',
|
||||
'intel_server_discovery',
|
||||
'intel_tor_detection',
|
||||
'intel_unencrypted_passwords',
|
||||
'intel_vpn_detection',
|
||||
'interfaces',
|
||||
'ip_versions',
|
||||
'mac_bandwidth',
|
||||
'mdns_hostnames',
|
||||
'netbios_hostnames',
|
||||
'quic_hostnames',
|
||||
'regions',
|
||||
'remote_ips',
|
||||
'sni_hostnames',
|
||||
'ssh_versions',
|
||||
'ssl_server_cn',
|
||||
'threats',
|
||||
'tls_ciphers',
|
||||
'tls_security',
|
||||
'tls_versions',
|
||||
'vlans',
|
||||
];
|
||||
|
||||
// ─── Run Tests ────────────────────────────────────────────────────────────
|
||||
console.log('\n══════════════════════════════════════════════════════════');
|
||||
console.log(' TDD TEST SUITE: netify_data_export.txt Validator');
|
||||
console.log('══════════════════════════════════════════════════════════\n');
|
||||
|
||||
// Test 1: File must exist
|
||||
console.log('[ Group 1: File Existence & Structure ]');
|
||||
test('File netify_data_export.txt harus ada', () => {
|
||||
assert(fs.existsSync(OUTPUT_FILE), `File tidak ditemukan: ${OUTPUT_FILE}`);
|
||||
});
|
||||
|
||||
// Bail early if file doesn't exist
|
||||
if (!fs.existsSync(OUTPUT_FILE)) {
|
||||
console.log('\n ⚠️ File tidak ada, skip remaining tests.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(OUTPUT_FILE, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
test('File tidak boleh kosong (minimal 100 baris)', () => {
|
||||
assertGreaterThan(lines.length, 100, `File hanya ${lines.length} baris`);
|
||||
});
|
||||
|
||||
test('File harus diawali dengan header metadata', () => {
|
||||
assert(content.includes('NETIFY DATA EXPORT'), 'Header NETIFY DATA EXPORT tidak ditemukan');
|
||||
});
|
||||
|
||||
test('File harus berisi tanggal export', () => {
|
||||
assert(content.includes('Export Date:') || content.includes('Generated:'), 'Tanggal export tidak ditemukan');
|
||||
});
|
||||
|
||||
test('File harus berisi total jumlah tabel', () => {
|
||||
assert(content.includes('Total Tables:') || content.includes('Tables:'), 'Info total tabel tidak ditemukan');
|
||||
});
|
||||
|
||||
// Test 2: Section per table
|
||||
console.log('\n[ Group 2: Table Sections ]');
|
||||
REQUIRED_TABLES.forEach(tableName => {
|
||||
test(`Section [${tableName}] harus ada di file`, () => {
|
||||
assert(
|
||||
content.includes(`[TABLE: ${tableName}]`) || content.includes(`TABLE: ${tableName}`),
|
||||
`Section untuk tabel ${tableName} tidak ditemukan`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Test 3: Data format
|
||||
console.log('\n[ Group 3: Data Format & Content ]');
|
||||
|
||||
test('Setiap section tabel harus memiliki info ROW COUNT', () => {
|
||||
assert(content.includes('Row Count:') || content.includes('rows:'), 'Row count info tidak ditemukan');
|
||||
});
|
||||
|
||||
test('Section flows harus berisi data src_ip', () => {
|
||||
const flowSection = content.includes('[TABLE: flows]');
|
||||
if (flowSection) {
|
||||
assert(content.includes('src_ip') || content.includes('"src_ip"'), 'Data src_ip tidak ditemukan di flows');
|
||||
}
|
||||
});
|
||||
|
||||
test('Section bandwidth_apps harus berisi data app_label', () => {
|
||||
assert(content.includes('app_label') || content.includes('"app_label"'), 'app_label tidak ditemukan');
|
||||
});
|
||||
|
||||
test('Section intel_encryption_audit harus berisi risk_level', () => {
|
||||
assert(content.includes('risk_level') || content.includes('"risk_level"'), 'risk_level tidak ditemukan');
|
||||
});
|
||||
|
||||
test('Section devices harus berisi ip_address', () => {
|
||||
assert(content.includes('ip_address') || content.includes('"ip_address"'), 'ip_address tidak ditemukan');
|
||||
});
|
||||
|
||||
// Test 4: Specific data values check
|
||||
console.log('\n[ Group 4: Real Data Validation ]');
|
||||
|
||||
test('File harus mengandung data flows (src_ip format 10.x.x.x atau 192.168.x.x)', () => {
|
||||
const hasRealIP = /10\.\d+\.\d+\.\d+/.test(content) || /192\.168\.\d+\.\d+/.test(content);
|
||||
assert(hasRealIP, 'Tidak ditemukan IP internal (10.x.x.x / 192.168.x.x)');
|
||||
});
|
||||
|
||||
test('File harus mengandung nama aplikasi nyata (YouTube/Facebook/etc)', () => {
|
||||
const hasApp = content.includes('YouTube') || content.includes('Facebook') || content.includes('WhatsApp');
|
||||
assert(hasApp, 'Tidak ada nama aplikasi brand yang dikenal');
|
||||
});
|
||||
|
||||
test('Section intel_unencrypted_passwords harus memiliki data username/severity', () => {
|
||||
assert(content.includes('severity') || content.includes('"severity"'), 'severity tidak ditemukan');
|
||||
});
|
||||
|
||||
test('File mengandung data agent/MAC address', () => {
|
||||
const hasMAC = /[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}/.test(content);
|
||||
assert(hasMAC, 'Tidak ada format MAC address yang valid');
|
||||
});
|
||||
|
||||
test('File mengandung bytes/bandwidth numbers (angka > 1000)', () => {
|
||||
const hasBigNum = /\d{4,}/.test(content);
|
||||
assert(hasBigNum, 'Tidak ada angka bandwidth yang terdeteksi');
|
||||
});
|
||||
|
||||
// Test 5: File size
|
||||
console.log('\n[ Group 5: File Size ]');
|
||||
|
||||
test('File harus cukup besar (minimal 100KB)', () => {
|
||||
const stats = fs.statSync(OUTPUT_FILE);
|
||||
assertGreaterThan(stats.size, 100 * 1024, `File terlalu kecil: ${stats.size} bytes`);
|
||||
});
|
||||
|
||||
// ─── Summary ────────────────────────────────────────────────────────────────
|
||||
console.log('\n══════════════════════════════════════════════════════════');
|
||||
console.log(` RESULTS: ${passed} passed, ${failed} failed`);
|
||||
console.log('══════════════════════════════════════════════════════════\n');
|
||||
|
||||
if (failed > 0) {
|
||||
console.log('❌ TESTS FAILED — implementasi diperlukan\n');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('✅ ALL TESTS PASSED — file export valid\n');
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Test: cari field name yang valid untuk SNI hostname di API
|
||||
const axios = require('axios');
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../.env.local') });
|
||||
|
||||
const API_KEY = process.env.NETIFY_API_KEY;
|
||||
const SITE_UUID = process.env.NETIFY_SITE_UUID;
|
||||
const BASE = 'https://informatics.netify.ai/api/v1';
|
||||
|
||||
const headers = {
|
||||
'x-api-key': API_KEY,
|
||||
'x-net-site': SITE_UUID,
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
|
||||
async function tryField(field) {
|
||||
try {
|
||||
const url = `${BASE}/data/stats/top/${field}/download`;
|
||||
const res = await axios.get(url, { headers, params: { filter_interval: 1440, settings_limit: 5 }, timeout: 10000 });
|
||||
const data = res.data?.data;
|
||||
if (data && Array.isArray(data) && data.length > 0) {
|
||||
console.log(`✅ ${field}: OK, count=${data.length}, keys=${Object.keys(data[0])}`);
|
||||
console.log(` First: ${JSON.stringify(data[0]).slice(0, 200)}`);
|
||||
} else {
|
||||
console.log(`⚠️ ${field}: OK but empty`);
|
||||
}
|
||||
} catch (err) {
|
||||
const status = err.response?.status;
|
||||
const msg = err.response?.data?.status_message || err.message;
|
||||
console.log(`❌ ${field}: ${status} - ${msg?.slice(0, 100)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
// Try all possible SNI-related field names
|
||||
const candidates = [
|
||||
'https_sni_hostname',
|
||||
'sni_hostname',
|
||||
'ssl_sni',
|
||||
'tls_sni',
|
||||
'ssl_server_cn',
|
||||
'quic_hostname',
|
||||
'hostname'
|
||||
];
|
||||
|
||||
console.log('=== Testing SNI/Hostname field names ===\n');
|
||||
for (const f of candidates) {
|
||||
await tryField(f);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
Reference in new issue
Block a user