feat(fuzzer): DPI-детектор, авто-применение, история результатов

- DPI-детектор: перед бенчмарком определяет тип блокировки (RST/throttle/DNS)
  и переранжирует стратегии по приоритету (badseq для RST, multisplit для
  throttle, fake+TTL для DNS-блока)
- Авто-применение: галочка «Auto-apply best strategy» автоматически
  применяет выигравшую стратегию после завершения бенчмарка
- История: результаты бенчмарков сохраняются в /etc/tachyon/fuzzer_history.json
  (до 50 записей) — новая вкладка «History» в UI
- CLI: новые команды detect_dpi, auto_apply, history, clear_history
- Локализация: русские и английские строки для всех новых элементов UI
- Frontend: вкладка History, баннер DPI-детекции, чекбокс авто-применения
This commit is contained in:
Dushnilin
2026-08-27 15:29:07 +03:00
parent 4f486d368c
commit b620886b9e
8 changed files with 1179 additions and 7 deletions
+72
View File
@@ -7614,3 +7614,75 @@ msgstr ""
#: src/tachyon/tabs/diagnostic/checks/runZapret2Check.ts:107
msgid "Zapret2 sing-box outbound is not configured"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "DPI Detected"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "confidence"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Recommended"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Auto-apply best strategy"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Benchmark History"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Refresh"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Clear History"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "No benchmark history yet. Run a benchmark to see results here."
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Failed to load history"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Date"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "DPI Type"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Best Strategy"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Working"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Auto-applied best strategy"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Auto-apply failed"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Failed to detect DPI type"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "Failed to auto-apply strategy"
msgstr ""
#: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts
msgid "History cleared"
msgstr ""
+54
View File
@@ -5092,3 +5092,57 @@ msgstr "Восстановление прямого интернета..."
msgid "Find optimal DPI bypass strategies with isolated live probing"
msgstr "Автоматический подбор оптимальных параметров обхода DPI в изолированном сэндбоксе"
msgid "DPI Detected"
msgstr "DPI обнаружен"
msgid "confidence"
msgstr "уверенность"
msgid "Recommended"
msgstr "Рекомендовано"
msgid "Auto-apply best strategy"
msgstr "Авто-применение лучшей стратегии"
msgid "Benchmark History"
msgstr "История бенчмарков"
msgid "Refresh"
msgstr "Обновить"
msgid "Clear History"
msgstr "Очистить историю"
msgid "No benchmark history yet. Run a benchmark to see results here."
msgstr "История бенчмарков пуста. Запустите бенчмарк, чтобы увидеть результаты здесь."
msgid "Failed to load history"
msgstr "Не удалось загрузить историю"
msgid "Date"
msgstr "Дата"
msgid "DPI Type"
msgstr "Тип DPI"
msgid "Best Strategy"
msgstr "Лучшая стратегия"
msgid "Working"
msgstr "Рабочих"
msgid "Auto-applied best strategy"
msgstr "Лучшая стратегия автоматически применена"
msgid "Auto-apply failed"
msgstr "Авто-применение не удалось"
msgid "Failed to detect DPI type"
msgstr "Не удалось определить тип DPI"
msgid "Failed to auto-apply strategy"
msgstr "Не удалось автоматически применить стратегию"
msgid "History cleared"
msgstr "История очищена"
@@ -1352,4 +1352,106 @@ export const TachyonShellMethods = {
_('Failed to reset fuzzer patterns'),
};
},
detectFuzzerDpi: async (target: string, customUrl?: string) => {
const response = await executeShellCommand({
command: '/usr/bin/tachyon',
args: [
Tachyon.AvailableMethods.FUZZER_DETECT_DPI,
target,
...(customUrl ? [customUrl] : []),
],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput<Tachyon.FuzzerDetectDpiResponse>(
response.stdout,
);
if ((response.code ?? 0) !== 0 || !parsed) {
return {
success: false,
error: response.stderr || _('Failed to detect DPI type'),
};
}
return {
success: true,
data: parsed,
};
},
autoApplyFuzzerStrategy: async (targetRule?: string) => {
const response = await executeShellCommand({
command: '/usr/bin/tachyon',
args: [
Tachyon.AvailableMethods.FUZZER_AUTO_APPLY,
...(targetRule ? [targetRule] : []),
],
timeout: UI_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput<Tachyon.FuzzerAutoApplyResponse>(
response.stdout,
);
if ((response.code ?? 0) !== 0 || !parsed?.success) {
return {
success: false,
error:
parsed?.error ||
response.stderr ||
_('Failed to auto-apply strategy'),
};
}
return {
success: true,
data: parsed,
};
},
getFuzzerHistory: async (limit?: number) => {
const response = await executeShellCommand({
command: '/usr/bin/tachyon',
args: [
Tachyon.AvailableMethods.FUZZER_HISTORY,
String(limit || 20),
],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput<Tachyon.FuzzerHistoryResult>(
response.stdout,
);
if ((response.code ?? 0) !== 0 || !parsed) {
return {
success: false,
error: response.stderr || _('Failed to get fuzzer history'),
};
}
return {
success: true,
data: parsed,
};
},
clearFuzzerHistory: async () => {
const response = await executeShellCommand({
command: '/usr/bin/tachyon',
args: [Tachyon.AvailableMethods.FUZZER_CLEAR_HISTORY],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS,
});
const parsed = parseJsonObjectOutput<{
success: boolean;
error?: string;
}>(response.stdout);
if ((response.code ?? 0) !== 0 || !parsed?.success) {
return {
success: false,
error: response.stderr || _('Failed to clear fuzzer history'),
};
}
return {
success: true,
data: { message: _('History cleared') },
};
},
};
@@ -8,7 +8,8 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
let isPolling = false;
// Modal State
let activeTab: 'benchmark' | 'patterns' | 'custom' | 'ai' = 'benchmark';
let activeTab: 'benchmark' | 'patterns' | 'custom' | 'ai' | 'history' =
'benchmark';
let selectedEngine: Tachyon.FuzzerEngine = 'zapret2';
let selectedTarget: Tachyon.FuzzerTarget = 'youtube_suite';
let customUrl = '';
@@ -17,6 +18,8 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
let isRunning = false;
let currentState: Tachyon.FuzzerState | null = null;
let resultFilter: 'all' | 'success' | 'fast' = 'all';
let autoApplyEnabled = false;
let currentDpiDetection: Tachyon.FuzzerDpiDetection | null = null;
// Patterns Config State
let patternsConfig: Tachyon.FuzzerPatternsConfig = {
@@ -56,7 +59,7 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
const tabButtons: Record<string, HTMLElement> = {};
const createTabButton = (
id: 'benchmark' | 'patterns' | 'custom' | 'ai',
id: 'benchmark' | 'patterns' | 'custom' | 'ai' | 'history',
icon: string,
label: string,
) => {
@@ -89,6 +92,7 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
createTabButton('patterns', '🛠️', _('Pattern Builder')),
createTabButton('custom', '➕', _('My Strategies')),
createTabButton('ai', '🧠', _('AI Doctor RAG')),
createTabButton('history', '📋', _('History')),
],
);
@@ -104,6 +108,9 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
const tabContentAi = E('div', {
style: 'display: none; flex-direction: column; gap: 14px;',
});
const tabContentHistory = E('div', {
style: 'display: none; flex-direction: column; gap: 14px;',
});
const updateTabVisibility = () => {
for (const key in tabButtons) {
@@ -116,9 +123,12 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
activeTab === 'patterns' ? 'flex' : 'none';
tabContentCustom.style.display = activeTab === 'custom' ? 'flex' : 'none';
tabContentAi.style.display = activeTab === 'ai' ? 'flex' : 'none';
tabContentHistory.style.display =
activeTab === 'history' ? 'flex' : 'none';
if (activeTab === 'patterns') renderPatternsTab();
if (activeTab === 'custom') renderCustomTab();
if (activeTab === 'history') renderHistoryTab();
};
// ═════════════════════════════════════════════════════════════════════════════
@@ -283,9 +293,43 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
ruleSelect,
],
);
controlsGrid.append(engineGroup, targetGroup, modeGroup, ruleGroup);
// Auto-Apply Toggle
const autoApplyCheckbox = E('input', {
type: 'checkbox',
id: 'tachyon-fuzzer-auto-apply',
style: 'margin: 0;',
});
autoApplyCheckbox.addEventListener('change', () => {
autoApplyEnabled = (autoApplyCheckbox as HTMLInputElement).checked;
});
const autoApplyGroup = E(
'div',
{
style:
'display: flex; align-items: end; padding: 4px 0; grid-column: 1 / -1;',
},
[
E(
'div',
{
style: 'font-size: 11px; cursor: pointer; display: flex; align-items: center; gap: 4px;',
},
[autoApplyCheckbox, E('span', {}, _('Auto-apply best strategy'))],
),
],
);
controlsGrid.append(autoApplyGroup);
// DPI Detection Banner
const dpiDetectionBanner = E('div', {
id: 'tachyon-fuzzer-dpi-banner',
style:
'display: none; padding: 10px 14px; border-radius: 6px; font-size: 12px; line-height: 1.4;',
});
// Progress Bar
const progressContainer = E(
'div',
@@ -505,6 +549,7 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
tabContentBenchmark.append(
controlsGrid,
dpiDetectionBanner,
progressContainer,
filterToolbar,
resultsContainer,
@@ -1044,6 +1089,148 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
tabContentAi.append(aiSynthesizerCard);
// ═════════════════════════════════════════════════════════════════════════════
// TAB 5: HISTORY
// ═════════════════════════════════════════════════════════════════════════════
const renderHistoryTab = async () => {
tabContentHistory.replaceChildren();
const historyHeader = E(
'div',
{
style:
'display: flex; justify-content: space-between; align-items: center;',
},
[
E(
'div',
{ style: 'font-size: 13px; font-weight: bold;' },
'📋 ' + _('Benchmark History'),
),
E('div', { style: 'display: flex; gap: 8px;' }, [
renderButton({
text: _('🔄 Refresh'),
classNames: ['cbi-button-neutral'],
onClick: () => renderHistoryTab(),
}),
renderButton({
text: _('🗑️ Clear History'),
classNames: ['cbi-button-reset'],
onClick: async () => {
await TachyonShellMethods.clearFuzzerHistory();
showToast(_('History cleared'), 'success');
renderHistoryTab();
},
}),
]),
],
);
const historyContainer = E('div', {
style:
'border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;',
});
try {
const res = await TachyonShellMethods.getFuzzerHistory(20);
const entries = res.data?.entries || [];
if (entries.length === 0) {
historyContainer.appendChild(
E(
'div',
{
style:
'padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;',
},
_('No benchmark history yet. Run a benchmark to see results here.'),
),
);
} else {
const table = E(
'table',
{
class: 'table',
style: 'width: 100%; margin: 0; font-size: 12px;',
},
[
E('thead', {}, [
E('tr', {}, [
E('th', { style: 'padding: 8px;' }, _('Date')),
E('th', { style: 'padding: 8px;' }, _('Engine')),
E('th', { style: 'padding: 8px;' }, _('Target')),
E('th', { style: 'padding: 8px;' }, _('DPI Type')),
E('th', { style: 'padding: 8px;' }, _('Best Strategy')),
E('th', { style: 'padding: 8px; width: 60px;' }, _('Score')),
E('th', { style: 'padding: 8px; width: 70px;' }, _('Working')),
]),
]),
E(
'tbody',
{},
entries.reverse().map((entry) => {
const date = new Date(entry.timestamp * 1000);
const dateStr = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
const dpiType = entry.dpi_detection
? `${entry.dpi_detection.type} (${entry.dpi_detection.confidence}%)`
: '—';
const bestName = entry.best_strategy
? entry.best_strategy.name
: '—';
const bestScore = entry.best_strategy
? String(entry.best_strategy.score)
: '0';
const working = `${entry.working_count}/${entry.total_tested}`;
return E('tr', {}, [
E('td', { style: 'padding: 8px;' }, dateStr),
E(
'td',
{
style: 'padding: 8px; font-family: monospace;',
},
entry.engine,
),
E('td', { style: 'padding: 8px;' }, entry.target),
E('td', { style: 'padding: 8px;' }, dpiType),
E(
'td',
{
style:
'padding: 8px; font-size: 11px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
},
bestName,
),
E(
'td',
{ style: 'padding: 8px; font-weight: bold;' },
bestScore,
),
E('td', { style: 'padding: 8px;' }, working),
]);
}),
),
],
);
historyContainer.appendChild(table);
}
} catch {
historyContainer.appendChild(
E(
'div',
{
style:
'padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;',
},
_('Failed to load history'),
),
);
}
tabContentHistory.append(historyHeader, historyContainer);
};
// ── Results Rendering ─────────────────────────────────────────────────────
const renderResults = (state: Tachyon.FuzzerState) => {
const tbody = document.getElementById('tachyon-fuzzer-results-tbody');
@@ -1228,6 +1415,29 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
stopPolling();
startBtn.innerText = _('🚀 Start Benchmark');
(startBtn as HTMLButtonElement).disabled = false;
// Auto-apply best strategy if enabled
if (autoApplyEnabled && currentState?.best_strategy) {
try {
const applyRes =
await TachyonShellMethods.autoApplyFuzzerStrategy(
selectedRuleSection || undefined,
);
if (applyRes.success) {
showToast(
`${_('✅ Auto-applied best strategy')}: ${currentState.best_strategy.name}`,
'success',
);
} else {
showToast(
`${_('Auto-apply failed')}: ${applyRes.error || _('Unknown error')}`,
'error',
);
}
} catch {
showToast(_('Auto-apply failed'), 'error');
}
}
}
}
} catch {
@@ -1250,6 +1460,64 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
}
};
const updateDpiBanner = (detection: Tachyon.FuzzerDpiDetection | null) => {
const banner = document.getElementById('tachyon-fuzzer-dpi-banner');
if (!banner) return;
if (!detection) {
banner.style.display = 'none';
return;
}
const typeColors: Record<string, { bg: string; border: string; icon: string }> = {
rst: { bg: 'rgba(220, 53, 69, 0.12)', border: '#dc3545', icon: '🔴' },
throttle: {
bg: 'rgba(255, 193, 7, 0.12)',
border: '#ffc107',
icon: '🟡',
},
dns_block: {
bg: 'rgba(23, 162, 184, 0.12)',
border: '#17a2b8',
icon: '🔵',
},
unknown: {
bg: 'rgba(108, 117, 125, 0.12)',
border: '#6c757d',
icon: '⚪',
},
none: {
bg: 'rgba(40, 167, 69, 0.12)',
border: '#28a745',
icon: '🟢',
},
};
const typeLabels: Record<string, string> = {
rst: 'TCP Reset Injection',
throttle: 'Throttling / Deep Inspection',
dns_block: 'DNS Blocking',
unknown: 'Unknown DPI Pattern',
none: 'No Blocking Detected',
};
const colors = typeColors[detection.type] || typeColors.unknown;
const label = typeLabels[detection.type] || detection.type;
banner.style.display = 'block';
banner.style.background = colors.bg;
banner.style.borderLeft = `3px solid ${colors.border}`;
banner.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: start;">
<div>
<span style="font-weight: bold;">${colors.icon} ${_('DPI Detected')}: ${label}</span>
<span style="opacity: 0.7; margin-left: 8px;">${detection.confidence}% ${_('confidence')}</span>
</div>
${detection.recommended_engines.length > 0 ? `<div style="font-size: 11px; opacity: 0.7;">${_('Recommended')}: ${detection.recommended_engines.join(', ')}</div>` : ''}
</div>
<div style="margin-top: 4px; opacity: 0.8; font-size: 11px;">${detection.details}</div>
`;
};
const handleToggleRun = async () => {
if (isRunning) {
await TachyonShellMethods.stopFuzzer();
@@ -1279,6 +1547,20 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
'Preparing strategies and isolated nftables queue...',
);
// Run DPI detection before starting benchmark
try {
const dpiRes = await TachyonShellMethods.detectFuzzerDpi(
selectedTarget,
selectedTarget === 'custom' ? customUrl : undefined,
);
if (dpiRes.success && dpiRes.data) {
currentDpiDetection = dpiRes.data;
updateDpiBanner(dpiRes.data);
}
} catch {
// DPI detection is non-critical, continue with benchmark
}
const tbody = document.getElementById('tachyon-fuzzer-results-tbody');
if (tbody) {
tbody.replaceChildren(
@@ -1453,6 +1735,7 @@ export function renderStrategyFuzzerModal(ruleNames: string[] = []) {
tabContentPatterns,
tabContentCustom,
tabContentAi,
tabContentHistory,
);
// Check initial state
+66
View File
@@ -107,6 +107,10 @@ export namespace Tachyon {
FUZZER_GET_PATTERNS = 'fuzzer_get_patterns',
FUZZER_SAVE_PATTERNS = 'fuzzer_save_patterns',
FUZZER_RESET_PATTERNS = 'fuzzer_reset_patterns',
FUZZER_DETECT_DPI = 'fuzzer_detect_dpi',
FUZZER_AUTO_APPLY = 'fuzzer_auto_apply',
FUZZER_HISTORY = 'fuzzer_history',
FUZZER_CLEAR_HISTORY = 'fuzzer_clear_history',
}
export enum AvailableClashAPIMethods {
@@ -905,6 +909,7 @@ export namespace Tachyon {
error?: string | null;
started_at: number;
finished_at: number;
dpi_detection?: FuzzerDpiDetection | null;
}
export interface FuzzerStartResponse {
@@ -978,4 +983,65 @@ export namespace Tachyon {
name: string;
urls: FuzzerTargetSuiteItem[];
}
export interface FuzzerDpiDetection {
type: 'rst' | 'throttle' | 'dns_block' | 'unknown' | 'none';
confidence: number;
details: string;
recommended_engines: string[];
probe_metrics: {
http_code: number;
handshake_ms: number;
ttfb_ms: number;
speed_kbps: number;
error: string;
};
}
export interface FuzzerHistoryEntry {
timestamp: number;
engine: string;
target: string;
mode: string;
best_strategy: {
id: string;
name: string;
engine: string;
args: string;
score: number;
ttfb_ms: number;
speed_kbps: number;
} | null;
total_tested: number;
working_count: number;
dpi_detection: FuzzerDpiDetection | null;
duration_sec: number;
}
export interface FuzzerHistoryResult {
success: boolean;
entries: FuzzerHistoryEntry[];
}
export interface FuzzerDetectDpiResponse {
type: 'rst' | 'throttle' | 'dns_block' | 'unknown' | 'none';
confidence: number;
details: string;
recommended_engines: string[];
probe_metrics: {
http_code: number;
handshake_ms: number;
ttfb_ms: number;
speed_kbps: number;
error: string;
};
}
export interface FuzzerAutoApplyResponse {
success: boolean;
engine: string;
applied_to: string;
args: string;
error?: string;
}
}
@@ -3123,6 +3123,10 @@ var Tachyon;
AvailableMethods2["FUZZER_GET_PATTERNS"] = "fuzzer_get_patterns";
AvailableMethods2["FUZZER_SAVE_PATTERNS"] = "fuzzer_save_patterns";
AvailableMethods2["FUZZER_RESET_PATTERNS"] = "fuzzer_reset_patterns";
AvailableMethods2["FUZZER_DETECT_DPI"] = "fuzzer_detect_dpi";
AvailableMethods2["FUZZER_AUTO_APPLY"] = "fuzzer_auto_apply";
AvailableMethods2["FUZZER_HISTORY"] = "fuzzer_history";
AvailableMethods2["FUZZER_CLEAR_HISTORY"] = "fuzzer_clear_history";
})(AvailableMethods = Tachyon2.AvailableMethods || (Tachyon2.AvailableMethods = {}));
let AvailableClashAPIMethods;
((AvailableClashAPIMethods2) => {
@@ -4160,6 +4164,94 @@ var TachyonShellMethods = {
success: false,
error: parsed?.error || response.stderr || _("Failed to reset fuzzer patterns")
};
},
detectFuzzerDpi: async (target, customUrl) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.FUZZER_DETECT_DPI,
target,
...customUrl ? [customUrl] : []
],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS
});
const parsed = parseJsonObjectOutput(
response.stdout
);
if ((response.code ?? 0) !== 0 || !parsed) {
return {
success: false,
error: response.stderr || _("Failed to detect DPI type")
};
}
return {
success: true,
data: parsed
};
},
autoApplyFuzzerStrategy: async (targetRule) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.FUZZER_AUTO_APPLY,
...targetRule ? [targetRule] : []
],
timeout: UI_ACTION_RPC_TIMEOUT_MS
});
const parsed = parseJsonObjectOutput(
response.stdout
);
if ((response.code ?? 0) !== 0 || !parsed?.success) {
return {
success: false,
error: parsed?.error || response.stderr || _("Failed to auto-apply strategy")
};
}
return {
success: true,
data: parsed
};
},
getFuzzerHistory: async (limit) => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [
Tachyon.AvailableMethods.FUZZER_HISTORY,
String(limit || 20)
],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS
});
const parsed = parseJsonObjectOutput(
response.stdout
);
if ((response.code ?? 0) !== 0 || !parsed) {
return {
success: false,
error: response.stderr || _("Failed to get fuzzer history")
};
}
return {
success: true,
data: parsed
};
},
clearFuzzerHistory: async () => {
const response = await executeShellCommand({
command: "/usr/bin/tachyon",
args: [Tachyon.AvailableMethods.FUZZER_CLEAR_HISTORY],
timeout: COMPONENT_ACTION_RPC_TIMEOUT_MS
});
const parsed = parseJsonObjectOutput(response.stdout);
if ((response.code ?? 0) !== 0 || !parsed?.success) {
return {
success: false,
error: response.stderr || _("Failed to clear fuzzer history")
};
}
return {
success: true,
data: { message: _("History cleared") }
};
}
};
@@ -11831,6 +11923,8 @@ function renderStrategyFuzzerModal(ruleNames = []) {
let isRunning = false;
let currentState = null;
let resultFilter = "all";
let autoApplyEnabled = false;
let currentDpiDetection = null;
let patternsConfig = {
zapret2: {
splits: ["1", "2", "3", "midsld", "sniext+4", "1,midsld", "1,sniext+2"],
@@ -11888,7 +11982,8 @@ function renderStrategyFuzzerModal(ruleNames = []) {
createTabButton("benchmark", "\u26A1", _("Benchmark & Test")),
createTabButton("patterns", "\u{1F6E0}\uFE0F", _("Pattern Builder")),
createTabButton("custom", "\u2795", _("My Strategies")),
createTabButton("ai", "\u{1F9E0}", _("AI Doctor RAG"))
createTabButton("ai", "\u{1F9E0}", _("AI Doctor RAG")),
createTabButton("history", "\u{1F4CB}", _("History"))
]
);
const tabContentBenchmark = E("div", {
@@ -11903,6 +11998,9 @@ function renderStrategyFuzzerModal(ruleNames = []) {
const tabContentAi = E("div", {
style: "display: none; flex-direction: column; gap: 14px;"
});
const tabContentHistory = E("div", {
style: "display: none; flex-direction: column; gap: 14px;"
});
const updateTabVisibility = () => {
for (const key in tabButtons) {
tabButtons[key].className = `cbi-button ${activeTab2 === key ? "cbi-button-action" : "cbi-button-neutral"}`;
@@ -11911,8 +12009,10 @@ function renderStrategyFuzzerModal(ruleNames = []) {
tabContentPatterns.style.display = activeTab2 === "patterns" ? "flex" : "none";
tabContentCustom.style.display = activeTab2 === "custom" ? "flex" : "none";
tabContentAi.style.display = activeTab2 === "ai" ? "flex" : "none";
tabContentHistory.style.display = activeTab2 === "history" ? "flex" : "none";
if (activeTab2 === "patterns") renderPatternsTab();
if (activeTab2 === "custom") renderCustomTab();
if (activeTab2 === "history") renderHistoryTab();
};
const controlsGrid = E("div", {
style: "display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; align-items: end;"
@@ -12053,6 +12153,34 @@ function renderStrategyFuzzerModal(ruleNames = []) {
]
);
controlsGrid.append(engineGroup, targetGroup, modeGroup, ruleGroup);
const autoApplyCheckbox = E("input", {
type: "checkbox",
id: "tachyon-fuzzer-auto-apply",
style: "margin: 0;"
});
autoApplyCheckbox.addEventListener("change", () => {
autoApplyEnabled = autoApplyCheckbox.checked;
});
const autoApplyGroup = E(
"div",
{
style: "display: flex; align-items: end; padding: 4px 0; grid-column: 1 / -1;"
},
[
E(
"div",
{
style: "font-size: 11px; cursor: pointer; display: flex; align-items: center; gap: 4px;"
},
[autoApplyCheckbox, E("span", {}, _("Auto-apply best strategy"))]
)
]
);
controlsGrid.append(autoApplyGroup);
const dpiDetectionBanner = E("div", {
id: "tachyon-fuzzer-dpi-banner",
style: "display: none; padding: 10px 14px; border-radius: 6px; font-size: 12px; line-height: 1.4;"
});
const progressContainer = E(
"div",
{
@@ -12245,6 +12373,7 @@ function renderStrategyFuzzerModal(ruleNames = []) {
);
tabContentBenchmark.append(
controlsGrid,
dpiDetectionBanner,
progressContainer,
filterToolbar,
resultsContainer,
@@ -12720,6 +12849,126 @@ function renderStrategyFuzzerModal(ruleNames = []) {
]
);
tabContentAi.append(aiSynthesizerCard);
const renderHistoryTab = async () => {
tabContentHistory.replaceChildren();
const historyHeader = E(
"div",
{
style: "display: flex; justify-content: space-between; align-items: center;"
},
[
E(
"div",
{ style: "font-size: 13px; font-weight: bold;" },
"\u{1F4CB} " + _("Benchmark History")
),
E("div", { style: "display: flex; gap: 8px;" }, [
renderButton({
text: _("\u{1F504} Refresh"),
classNames: ["cbi-button-neutral"],
onClick: () => renderHistoryTab()
}),
renderButton({
text: _("\u{1F5D1}\uFE0F Clear History"),
classNames: ["cbi-button-reset"],
onClick: async () => {
await TachyonShellMethods.clearFuzzerHistory();
showToast(_("History cleared"), "success");
renderHistoryTab();
}
})
])
]
);
const historyContainer = E("div", {
style: "border: 1px solid var(--border-color, rgba(255,255,255,0.1)); border-radius: 6px; overflow: hidden;"
});
try {
const res = await TachyonShellMethods.getFuzzerHistory(20);
const entries = res.data?.entries || [];
if (entries.length === 0) {
historyContainer.appendChild(
E(
"div",
{
style: "padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;"
},
_("No benchmark history yet. Run a benchmark to see results here.")
)
);
} else {
const table = E(
"table",
{
class: "table",
style: "width: 100%; margin: 0; font-size: 12px;"
},
[
E("thead", {}, [
E("tr", {}, [
E("th", { style: "padding: 8px;" }, _("Date")),
E("th", { style: "padding: 8px;" }, _("Engine")),
E("th", { style: "padding: 8px;" }, _("Target")),
E("th", { style: "padding: 8px;" }, _("DPI Type")),
E("th", { style: "padding: 8px;" }, _("Best Strategy")),
E("th", { style: "padding: 8px; width: 60px;" }, _("Score")),
E("th", { style: "padding: 8px; width: 70px;" }, _("Working"))
])
]),
E(
"tbody",
{},
entries.reverse().map((entry) => {
const date = new Date(entry.timestamp * 1e3);
const dateStr = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
const dpiType = entry.dpi_detection ? `${entry.dpi_detection.type} (${entry.dpi_detection.confidence}%)` : "\u2014";
const bestName = entry.best_strategy ? entry.best_strategy.name : "\u2014";
const bestScore = entry.best_strategy ? String(entry.best_strategy.score) : "0";
const working = `${entry.working_count}/${entry.total_tested}`;
return E("tr", {}, [
E("td", { style: "padding: 8px;" }, dateStr),
E(
"td",
{
style: "padding: 8px; font-family: monospace;"
},
entry.engine
),
E("td", { style: "padding: 8px;" }, entry.target),
E("td", { style: "padding: 8px;" }, dpiType),
E(
"td",
{
style: "padding: 8px; font-size: 11px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
},
bestName
),
E(
"td",
{ style: "padding: 8px; font-weight: bold;" },
bestScore
),
E("td", { style: "padding: 8px;" }, working)
]);
})
)
]
);
historyContainer.appendChild(table);
}
} catch {
historyContainer.appendChild(
E(
"div",
{
style: "padding: 24px; text-align: center; opacity: 0.6; font-size: 12px;"
},
_("Failed to load history")
)
);
}
tabContentHistory.append(historyHeader, historyContainer);
};
const renderResults = (state) => {
const tbody = document.getElementById("tachyon-fuzzer-results-tbody");
if (!tbody) return;
@@ -12875,6 +13124,26 @@ function renderStrategyFuzzerModal(ruleNames = []) {
stopPolling();
startBtn.innerText = _("\u{1F680} Start Benchmark");
startBtn.disabled = false;
if (autoApplyEnabled && currentState?.best_strategy) {
try {
const applyRes = await TachyonShellMethods.autoApplyFuzzerStrategy(
selectedRuleSection || void 0
);
if (applyRes.success) {
showToast(
`${_("\u2705 Auto-applied best strategy")}: ${currentState.best_strategy.name}`,
"success"
);
} else {
showToast(
`${_("Auto-apply failed")}: ${applyRes.error || _("Unknown error")}`,
"error"
);
}
} catch {
showToast(_("Auto-apply failed"), "error");
}
}
}
}
} catch {
@@ -12893,6 +13162,59 @@ function renderStrategyFuzzerModal(ruleNames = []) {
pollingInterval = null;
}
};
const updateDpiBanner = (detection) => {
const banner = document.getElementById("tachyon-fuzzer-dpi-banner");
if (!banner) return;
if (!detection) {
banner.style.display = "none";
return;
}
const typeColors = {
rst: { bg: "rgba(220, 53, 69, 0.12)", border: "#dc3545", icon: "\u{1F534}" },
throttle: {
bg: "rgba(255, 193, 7, 0.12)",
border: "#ffc107",
icon: "\u{1F7E1}"
},
dns_block: {
bg: "rgba(23, 162, 184, 0.12)",
border: "#17a2b8",
icon: "\u{1F535}"
},
unknown: {
bg: "rgba(108, 117, 125, 0.12)",
border: "#6c757d",
icon: "\u26AA"
},
none: {
bg: "rgba(40, 167, 69, 0.12)",
border: "#28a745",
icon: "\u{1F7E2}"
}
};
const typeLabels = {
rst: "TCP Reset Injection",
throttle: "Throttling / Deep Inspection",
dns_block: "DNS Blocking",
unknown: "Unknown DPI Pattern",
none: "No Blocking Detected"
};
const colors = typeColors[detection.type] || typeColors.unknown;
const label = typeLabels[detection.type] || detection.type;
banner.style.display = "block";
banner.style.background = colors.bg;
banner.style.borderLeft = `3px solid ${colors.border}`;
banner.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: start;">
<div>
<span style="font-weight: bold;">${colors.icon} ${_("DPI Detected")}: ${label}</span>
<span style="opacity: 0.7; margin-left: 8px;">${detection.confidence}% ${_("confidence")}</span>
</div>
${detection.recommended_engines.length > 0 ? `<div style="font-size: 11px; opacity: 0.7;">${_("Recommended")}: ${detection.recommended_engines.join(", ")}</div>` : ""}
</div>
<div style="margin-top: 4px; opacity: 0.8; font-size: 11px;">${detection.details}</div>
`;
};
const handleToggleRun = async () => {
if (isRunning) {
await TachyonShellMethods.stopFuzzer();
@@ -12918,6 +13240,17 @@ function renderStrategyFuzzerModal(ruleNames = []) {
currentStratEl.innerText = _(
"Preparing strategies and isolated nftables queue..."
);
try {
const dpiRes = await TachyonShellMethods.detectFuzzerDpi(
selectedTarget,
selectedTarget === "custom" ? customUrl : void 0
);
if (dpiRes.success && dpiRes.data) {
currentDpiDetection = dpiRes.data;
updateDpiBanner(dpiRes.data);
}
} catch {
}
const tbody = document.getElementById("tachyon-fuzzer-results-tbody");
if (tbody) {
tbody.replaceChildren(
@@ -13064,7 +13397,8 @@ function renderStrategyFuzzerModal(ruleNames = []) {
tabContentBenchmark,
tabContentPatterns,
tabContentCustom,
tabContentAi
tabContentAi,
tabContentHistory
);
pollStatus();
ui.showModal(_("\u26A1 Strategy Fuzzer & Auto-Tuner"), modalContainer);
+4
View File
@@ -268,6 +268,10 @@ function command_spec(command) {
fuzzer_get_patterns: [ "diagnostics/fuzzer.uc", "get_patterns", 0 ],
fuzzer_save_patterns: [ "diagnostics/fuzzer.uc", "save_patterns", 1 ],
fuzzer_reset_patterns: [ "diagnostics/fuzzer.uc", "reset_patterns", 0 ],
fuzzer_detect_dpi: [ "diagnostics/fuzzer.uc", "detect_dpi", 2 ],
fuzzer_auto_apply: [ "diagnostics/fuzzer.uc", "auto_apply", 1 ],
fuzzer_history: [ "diagnostics/fuzzer.uc", "history", 1 ],
fuzzer_clear_history: [ "diagnostics/fuzzer.uc", "clear_history", 0 ],
generate_warp: [ "service/warp_generator.uc", "", 1 ],
package_prerm: [ "service/package.uc", "prerm", 1 ],
package_postinst: [ "service/package.uc", "postinst", 0 ],
+259 -2
View File
@@ -21,6 +21,7 @@ const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
const STATE_DIR = getenv("TACHYON_FUZZER_STATE_DIR") || "/var/run/tachyon";
const STATE_FILE = STATE_DIR + "/fuzzer-state.json";
const PID_FILE = STATE_DIR + "/fuzzer-worker.pid";
const HISTORY_FILE = "/etc/tachyon/fuzzer_history.json";
const BYEDPI_PORT = 11089;
const NFQUEUE_QNUM_ZAPRET = 298;
const NFQUEUE_QNUM_ZAPRET2 = 299;
@@ -1078,6 +1079,203 @@ function parse_curl_output(output, result) {
return result;
}
// ── DPI Type Detection ──────────────────────────────────────────────────────
// Probes the target without any bypass to determine how it's being blocked.
// Returns: { type: "rst"|"throttle"|"dns_block"|"unknown"|"none",
// confidence: 0-100, details: string, recommended_engines: string[] }
function detect_dpi_type(target_key, custom_url) {
let urls_list = resolve_target_urls_list(target_key, custom_url);
let target_url = urls_list[0] ? urls_list[0].url : "https://www.google.com";
let result = {
type: "unknown",
confidence: 0,
details: "",
recommended_engines: [],
probe_metrics: { http_code: 0, handshake_ms: 0, ttfb_ms: 0, speed_kbps: 0, error: "" }
};
// Direct probe without any bypass
let curl_cmd = sprintf(
"curl -so /dev/null -w '%%{http_code}\\t%%{time_appconnect}\\t%%{time_starttransfer}\\t%%{speed_download}' -L --connect-timeout 3 --max-time 5 %s 2>&1",
shell_quote(target_url)
);
let pipe = fs.popen(curl_cmd, "r");
let output = pipe ? pipe.read("all") : "";
if (pipe) pipe.close();
output = trim(output);
let metrics = {};
parse_curl_output(output, metrics);
result.probe_metrics = {
http_code: metrics.http_code || 0,
handshake_ms: metrics.handshake_ms || 0,
ttfb_ms: metrics.ttfb_ms || 0,
speed_kbps: metrics.speed_kbps || 0,
error: metrics.error || ""
};
// Also check for DNS-level blocking
let domain = target_url;
let dm = match(domain, /https?:\/\/([^/]+)/);
if (dm && dm[1]) domain = dm[1];
let dns_cmd = sprintf("nslookup %s 2>&1", shell_quote(domain));
let dns_pipe = fs.popen(dns_cmd, "r");
let dns_out = dns_pipe ? dns_pipe.read("all") : "";
if (dns_pipe) dns_pipe.close();
let dns_blocked = false;
if (index(dns_out, "NXDOMAIN") >= 0 || index(dns_out, "can't resolve") >= 0 || index(dns_out, "** server can't find") >= 0) {
dns_blocked = true;
}
// Analyze failure patterns
let http_code = metrics.http_code || 0;
let handshake = metrics.handshake_ms || 0;
let ttfb = metrics.ttfb_ms || 0;
let error_str = metrics.error || "";
if (dns_blocked) {
result.type = "dns_block";
result.confidence = 90;
result.details = sprintf("DNS resolution failed for %s — likely DNS-level blocking or hijacking", domain);
result.recommended_engines = ["byedpi", "zapret2"];
} else if (index(error_str, "Connection reset") >= 0 || index(error_str, "ECONNRESET") >= 0) {
result.type = "rst";
result.confidence = 85;
result.details = sprintf("TCP RST received from DPI — active TCP reset injection detected");
result.recommended_engines = ["zapret2", "zapret"];
} else if (http_code == 0 || index(error_str, "Connection refused") >= 0 || index(error_str, "ECONNREFUSED") >= 0) {
result.type = "rst";
result.confidence = 70;
result.details = sprintf("Connection refused — likely RST or blackhole by DPI");
result.recommended_engines = ["zapret2", "zapret"];
} else if (http_code >= 400 && http_code < 500) {
result.type = "throttle";
result.confidence = 60;
result.details = sprintf("HTTP %d returned — DPI may be injecting HTTP errors or throttling", http_code);
result.recommended_engines = ["zapret2", "byedpi"];
} else if (handshake > 2000) {
result.type = "throttle";
result.confidence = 75;
result.details = sprintf("Very slow TLS handshake (%dms) — likely DPI deep inspection causing delay", handshake);
result.recommended_engines = ["zapret2", "zapret"];
} else if (ttfb > 3000 && http_code >= 200 && http_code < 400) {
result.type = "throttle";
result.confidence = 65;
result.details = sprintf("High TTFB (%dms) despite successful connection — likely bandwidth throttling", ttfb);
result.recommended_engines = ["zapret2", "byedpi"];
} else if (http_code >= 200 && http_code < 400) {
result.type = "none";
result.confidence = 95;
result.details = sprintf("Target accessible — no DPI blocking detected (HTTP %d, TTFB %dms)", http_code, ttfb);
result.recommended_engines = [];
} else {
result.type = "unknown";
result.confidence = 30;
result.details = sprintf("Inconclusive — HTTP %d, error: %s", http_code, error_str != "" ? error_str : "none");
result.recommended_engines = ["zapret2", "zapret", "byedpi"];
}
return result;
}
// ── History Persistence ──────────────────────────────────────────────────────
function load_history() {
let data = read_json_file(HISTORY_FILE);
if (data && type(data) == "object" && data.entries && type(data.entries) == "array") {
return data;
}
return { entries: [] };
}
function save_history(history) {
common.ensure_dir("/etc/tachyon");
while (length(history.entries) > 50) {
shift(history.entries);
}
write_json_file(HISTORY_FILE, history);
}
function append_history(entry) {
let history = load_history();
push(history.entries, {
timestamp: entry.timestamp || clock()[0],
engine: entry.engine || "unknown",
target: entry.target || "unknown",
mode: entry.mode || "presets",
best_strategy: entry.best_strategy || null,
total_tested: entry.total_tested || 0,
working_count: entry.working_count || 0,
dpi_detection: entry.dpi_detection || null,
duration_sec: entry.duration_sec || 0
});
save_history(history);
}
function get_history(limit) {
let history = load_history();
let entries = history.entries || [];
let n = int(limit) || 0;
if (n > 0 && length(entries) > n) {
let start = length(entries) - n;
let sliced = [];
for (let i = start; i < length(entries); i++) {
push(sliced, entries[i]);
}
entries = sliced;
}
return entries;
}
// ── Strategy Priority Reranking (based on DPI type) ─────────────────────────
function rerank_strategies_by_dpi(strategies, dpi_type) {
if (!dpi_type || dpi_type.type == "none" || dpi_type.type == "unknown")
return strategies;
let priority_ids = [];
if (dpi_type.type == "rst") {
priority_ids = ["badseq", "md5sig", "multisplit", "disorder"];
} else if (dpi_type.type == "throttle") {
priority_ids = ["multisplit", "seqovl", "wsize", "split2"];
} else if (dpi_type.type == "dns_block") {
priority_ids = ["fake", "ttl=3", "ttl=4", "sniext"];
}
if (length(priority_ids) == 0)
return strategies;
let scored = [];
for (let s in strategies) {
let score = 0;
let args_lower = lc(as_string(s.args));
let name_lower = lc(as_string(s.name));
for (let pid in priority_ids) {
if (index(args_lower, pid) >= 0 || index(name_lower, pid) >= 0) {
score += 10;
}
}
push(scored, { strat: s, score: score });
}
for (let i = 0; i < length(scored) - 1; i++) {
for (let j = i + 1; j < length(scored); j++) {
if (scored[j].score > scored[i].score) {
let tmp = scored[i];
scored[i] = scored[j];
scored[j] = tmp;
}
}
}
let result = [];
for (let item in scored) {
push(result, item.strat);
}
return result;
}
function run_probe(engine, args_str, target_key, custom_url) {
cleanup_temp_daemons();
@@ -1256,6 +1454,10 @@ function run_probe(engine, args_str, target_key, custom_url) {
function run_fuzzer_worker(engine, target, custom_url, rule_section, custom_file, mode) {
let target_url = resolve_target_url(target, custom_url);
// ── Pre-fuzz DPI detection ────────────────────────────────────────────
let dpi_detection = detect_dpi_type(target, custom_url);
let strategies = null;
if (custom_file && custom_file != "" && fs.stat(custom_file) != null) {
strategies = common.read_json_file(custom_file);
@@ -1263,6 +1465,10 @@ function run_fuzzer_worker(engine, target, custom_url, rule_section, custom_file
if (!strategies || type(strategies) != "array" || length(strategies) == 0) {
strategies = get_strategies_for_engine(engine, mode);
}
// Rerank strategies based on detected DPI type
strategies = rerank_strategies_by_dpi(strategies, dpi_detection);
let total = length(strategies);
let state = {
@@ -1282,7 +1488,8 @@ function run_fuzzer_worker(engine, target, custom_url, rule_section, custom_file
best_strategy: null,
error: null,
started_at: clock()[0],
finished_at: 0
finished_at: 0,
dpi_detection: dpi_detection
};
save_fuzzer_state(state);
@@ -1290,6 +1497,7 @@ function run_fuzzer_worker(engine, target, custom_url, rule_section, custom_file
try {
let highest_score = -1;
let best = null;
let working_count = 0;
for (let i = 0; i < total; i++) {
let strat = strategies[i];
@@ -1318,6 +1526,8 @@ function run_fuzzer_worker(engine, target, custom_url, rule_section, custom_file
badge: ""
};
if (item_result.success) working_count++;
if (item_result.score > highest_score && item_result.success) {
highest_score = item_result.score;
best = item_result;
@@ -1351,6 +1561,28 @@ function run_fuzzer_worker(engine, target, custom_url, rule_section, custom_file
state.current_strategy = null;
state.finished_at = clock()[0];
save_fuzzer_state(state);
// ── Persist to history ────────────────────────────────────────────
let duration = state.finished_at - state.started_at;
append_history({
timestamp: state.finished_at,
engine: engine,
target: target,
mode: mode,
best_strategy: best ? {
id: best.id,
name: best.name,
engine: best.engine,
args: best.args,
score: best.score,
ttfb_ms: best.ttfb_ms,
speed_kbps: best.speed_kbps
} : null,
total_tested: total,
working_count: working_count,
dpi_detection: dpi_detection,
duration_sec: int(duration)
});
} catch (err) {
state.running = false;
state.error = as_string(err);
@@ -1456,6 +1688,21 @@ function apply_strategy(engine, args_val, target_rule) {
}));
}
function auto_apply_best(target_rule) {
let state = get_fuzzer_state();
if (!state.best_strategy || state.best_strategy.score <= 0) {
print(sprintf("%J\n", { success: false, error: "No winning strategy found — run a benchmark first" }));
return;
}
let best = state.best_strategy;
apply_strategy(best.engine, best.args, target_rule);
}
function clear_history() {
save_history({ entries: [] });
print(sprintf("%J\n", { success: true, message: "Fuzzer history cleared" }));
}
// CLI Dispatcher
let op = ARGV[0] || "status";
@@ -1478,6 +1725,16 @@ if (op == "start") {
reset_patterns_config();
} else if (op == "ai_synthesize" || op == "synthesize") {
synthesize_ai_strategies(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
} else if (op == "detect_dpi") {
let detection = detect_dpi_type(ARGV[1], ARGV[2]);
print(sprintf("%J\n", detection));
} else if (op == "auto_apply") {
auto_apply_best(ARGV[1]);
} else if (op == "history") {
let entries = get_history(ARGV[1]);
print(sprintf("%J\n", { success: true, entries: entries }));
} else if (op == "clear_history") {
clear_history();
} else if (op == "generate" || op == "strategies_generate") {
print(sprintf("%J\n", get_strategies_for_engine(ARGV[1], ARGV[2] || "combinatorial")));
} else if (op == "strategies") {
@@ -1491,7 +1748,7 @@ if (op == "start") {
byedpi: get_strategies_for_engine("byedpi", strat_mode)
}));
} else {
warn("Usage: fuzzer.uc [start|status|stop|apply|strategies|generate|get_patterns|save_patterns|reset_patterns|ai_synthesize|worker] ...\n");
warn("Usage: fuzzer.uc [start|status|stop|apply|strategies|generate|get_patterns|save_patterns|reset_patterns|ai_synthesize|detect_dpi|auto_apply|history|clear_history|worker] ...\n");
exit(1);
}