diff --git a/.vscode/agent-rules.yaml b/.vscode/agent-rules.yaml new file mode 100644 index 0000000..d21ef36 --- /dev/null +++ b/.vscode/agent-rules.yaml @@ -0,0 +1,280 @@ +--- +# GitHub Copilot Agent Rules v2.0 - CLUMSY PROJECT +# Development Standards for Clumsy Project (merges with global rules) +# Last Updated: 2025-10-17 +# +# Hierarchy: Project Rules (this file) > Global Rules (/AppData/Roaming/Code/User/agent-rules.yaml) +# Auto-load: true | Scope: This project + all inherited global rules + +version: "2.0" +scope: "project-clumsy-with-global" +inheritsFrom: "C:\\Users\\vuser\\AppData\\Roaming\\Code\\User\\agent-rules.yaml" + +# ============================================================================ +# RULE 1: Output Minimalism on Small Fixes +# ============================================================================ +rule_output_minimalism: + description: "Reduce verbose output for small changes" + small_fix: + lines_changed: "1-3" + requirement: "Only state: ✅ Done / ✅ Fixed / ✅ Applied" + comment_density: "zero-comments" + examples: + - "✅ Changed statsEnabled from 0 to 1" + - "✅ Added mutex initialization guard" + + medium_fix: + lines_changed: "4-15" + requirement: "Brief action description (1-2 sentences)" + comment_density: "minimal" + + large_change: + lines_changed: "15+" + requirement: "Full report: what, why, impact, validation" + comment_density: "detailed" + +# ============================================================================ +# RULE 2: Pre-Build Solution Validation +# ============================================================================ +rule_pre_build_validation: + description: "Verify solution correctness BEFORE compilation" + + before_build: + step_1_review: "Confirm changes match user requirements" + step_2_check: "Verify no logical inconsistencies" + step_3_validate: "Check dependencies and impact analysis" + step_4_question: "If ambiguity exists → ask clarifying questions" + step_5_confirm: "Wait for user approval before build" + + when_uncertain: + approach: "Act as senior architect from Top 100 Fortune company" + responsibility: "Ask targeted questions, don't assume" + examples: + - "Does this flag behavior align with the thread model?" + - "Should this mutex be per-instance or global?" + - "What's the expected behavior on rapid toggle cycles?" + depth_level: "architectural-scope-definition" + + build_only_after: "Explicit user confirmation OR implicit agreement from context" + +# ============================================================================ +# RULE 3: Active Change Verification & Senior Architect Mindset +# ============================================================================ +rule_active_verification: + description: "Verify all changes before committing; ask like a principal engineer" + + before_any_edit: + action_1: "Call get_changed_files to understand current state" + action_2: "Compare target vs actual before modifications" + action_3: "Identify potential cascading effects" + action_4: "Plan for edge cases and race conditions" + + information_gaps: + trigger: "Insufficient information for correct implementation" + response: "Ask senior-level architectural questions" + questions_focus: + - "System boundaries and constraints" + - "Thread safety requirements" + - "Performance/memory trade-offs" + - "Backward compatibility impact" + - "Error handling scenarios" + tone: "Principal Engineer / Architect from FAANG" + depth: "granular-technical-precision" + + after_changes: + action_1: "Diff analysis of what was actually changed" + action_2: "Cross-reference with original requirements" + action_3: "Verify no side effects introduced" + action_4: "Document assumptions and constraints" + +# ============================================================================ +# RULE 4: Code Review Standards (Self & User) - CLUMSY SPECIFIC +# ============================================================================ +rule_code_quality: + language: "C" + critical_context: + threading: "Multi-threaded packet processing (main UI + divert thread)" + ipc: "Critical sections (CRITICAL_SECTION mutexes)" + state: "Flags: statsEnabled/statsStarted, loggingEnabled/loggingStarted" + + thread_safety: "CRITICAL - all shared state guarded by mutex" + race_conditions: "Check mutex lifecycle, flag consistency across threads" + flag_semantics: "Separate flags: Enabled (toggle) vs Started (active) vs Shutdown (cleanup)" + edge_cases: "Rapid toggle, concurrent access, graceful shutdown with Sleep(10)" + + validation_checklist: + - "Does code match requirements exactly?" + - "Are there any race conditions (mutex access before initialization)?" + - "Is error handling complete?" + - "Do all threads see consistent state?" + - "Is cleanup symmetrical to initialization?" + - "Are flags used correctly (Enabled != Started)?" + - "Does mutex exist before first access?" + +# ============================================================================ +# RULE 5: Communication Protocol +# ============================================================================ +rule_communication: + small_tasks: "Minimal commentary, factual statements" + complex_tasks: "Full architectural rationale" + ambiguity: "Always ask for clarification rather than guess" + + response_format: + confirmation: "State what you understand before proceeding" + questions: "Numbered list of clarifications needed" + decision: "Proposed solution with alternatives if applicable" + execution: "Execute only after approval" + +# ============================================================================ +# RULE 6: Build & Test Workflow - CLUMSY SPECIFIC +# ============================================================================ +rule_build_workflow: + pre_build: + - "Verify solution via code review" + - "Check all changes are syntactically correct" + - "Confirm no breaking changes" + - "Ask questions if scope is unclear" + - "For thread-safety changes: trace mutex lifecycle" + - "For flag changes: verify consistency across Start/Stop/Process" + + build: + environment: "Bash on Windows" + zig_path: "/c/zig-windows-x86_64-0.10.1" + command: "cd /c/Users/vuser/repo/clumsy/clumsy && rm -rf zig-cache zig-out && export PATH=\"/c/zig-windows-x86_64-0.10.1:$PATH\" && zig build -Darch=x64 -Dconf=Release -Dsign=A" + + cleanup_before: "rm -rf zig-cache zig-out" + platform: "x86_64-windows" + profile: "Release" + signing: "A" + + only_after: "Pre-build validation passed" + output: "Binary: zig-out/x64_Release_A/clumsy.exe" + + post_build: + - "Verify compilation successful (exit code 0)" + - "Check executable exists and is valid size (>1MB expected)" + - "List output files: clumsy.exe, WinDivert.dll, WinDivert64.sys, config.txt" + - "Wait for user testing & feedback" + +# ============================================================================ +# RULE 7: Commit Standards - USE GLOBAL FORMAT +# ============================================================================ +rule_commits: + source: "Inherited from global agent-rules.yaml" + format: "(): " + types_allowed: + - "fix: Bug fix, issue resolution" + - "feat: New feature, capability" + - "refactor: Code reorganization" + - "perf: Performance improvement" + + clumsy_scopes: + - "stats: Statistics module" + - "logging: Logging module" + - "divert: Packet processing" + - "race-condition: Threading/mutex fixes" + - "ui: UI/panel behavior" + + example_commits: + - "fix(race-condition): stabilize stats and logging panels" + - "fix(logging): prevent mutex deadlock on rapid toggle" + - "feat(stats): add protocol breakdown display" + + description: + maxLength: 50 + lowercaseStart: true + noPeriod: true + imperative: true + + body: + format: "plaintext" + maxLineLength: 80 + required: true + items: + - "What changed" + - "Why it changed" + - "Impact/testing notes" + +# ============================================================================ +# OPERATIONAL CONTEXT +# ============================================================================ +project_context: + language: "C" + build_system: "Zig 0.10.1" + target: "Windows x86_64" + critical_modules: + - "src/stats.c: Thread-safe statistics collection" + - "src/logging.c: Thread-safe packet logging" + - "src/divert.c: Packet processing orchestrator" + + threading_model: + main_thread: "UI event loop" + divert_thread: "Packet processing & module execution" + critical_sections: "All shared state access" + + known_issues: + - "Race conditions on rapid toggle" + - "Mutex lifecycle tied to Start/Stop (FIXED)" + - "Panels auto-enabling on startup (FIXED)" + +# ============================================================================ +# DECISION MATRIX: When to Ask vs When to Proceed - CLUMSY SPECIFIC +# ============================================================================ +decision_matrix: + proceed_without_questions: + - "Clearly defined requirement in user message" + - "Straightforward bug fix with obvious solution" + - "Mechanical refactoring with no architectural impact" + - "Continuation of agreed-upon approach" + - "Small flag value changes (statsEnabled 0→1)" + - "Adding mutex guards to existing critical sections" + + MUST_ask_clarifying_questions: + - "ANY change to flag semantics (Enabled/Started/Shutdown)" + - "Changes affecting mutex lifecycle" + - "Thread synchronization modifications" + - "Architectural decisions with trade-offs" + - "Changes affecting multiple modules" + - "Performance vs correctness trade-offs" + - "API/interface changes" + - "UI state management changes" + + escalate_to_user: + - "Blocked by external dependencies" + - "Conflicting requirements" + - "Major refactoring recommended but outside scope" + - "Risk assessment: code works but maintainability concerns" + - "Need user testing before commit" + +# ============================================================================ +# VALIDATION RULES - RULES ENFORCEMENT +# ============================================================================ +enforcement: + before_edit: + action_1: "Load this file (.vscode/agent-rules.yaml)" + action_2: "Load global rules (AppData/.../agent-rules.yaml)" + action_3: "Merge rules (project overrides global on conflicts)" + action_4: "Get current git state via get_changed_files" + action_5: "Analyze required changes" + + if_ambiguity: + response: "Ask as senior architect, not as assistant" + tone: "Principal Engineer from FAANG" + depth: "Cover architectural implications" + dont_guess: "No assumptions about intent" + + before_build: + validation: "Full code review + architecture check" + user_approval: "Explicit or implicit (context-based)" + state_verification: "Trace all flag transitions" + + after_changes: + diff_check: "Verify actual vs intended changes" + side_effects: "Check for unintended interactions" + thread_safety: "Verify mutex guards on all accesses" + + commit_quality: + format_check: "Use global format: (): " + scope_precision: "Use clumsy_scopes list" + message_draft: "Show to user BEFORE commit" + requires_approval: true diff --git a/README.md b/README.md index f98fe02..1a13a20 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,312 @@ -# clumsy +# Clumsy - Network Packet Manipulation Tool -__clumsy makes your network condition on Windows significantly worse, but in a managed and interactive manner.__ +## Overview +Clumsy is a network packet manipulation tool that allows users to simulate various network conditions by intercepting and modifying network traffic in real-time. This version has been significantly enhanced with a modular UI system, improved logging, and enhanced functionality. -Leveraging the awesome [WinDivert](http://reqrypt.org/windivert.html), clumsy stops living network packets and capture them, lag/drop/tamper/.. the packets on demand, then send them away. Whether you want to track down weird bugs related to broken network, or evaluate your application on poor connections, clumsy will come in handy: +## Details -* No installation. -* No need for proxy setup or code change in your application. -* System wide network capturing means it works on any application. -* Works even if you're offline (ie, connecting from localhost to localhost). -* Your application keeps running, while clumsy can start and stop anytime. -* Interactive control how bad the network can be, with enough visual feedback to tell you what's going on. +Simulate network latency, delay, packet loss with clumsy on Windows 7/8/10: -See [this page](http://jagt.github.io/clumsy) for more info and build instructions. +![](clumsy-demo.gif) +## Key Features -## Details +### Modular UI System +- Tab-based interface in the top-left corner similar to Windows tabs +- Side panels for automation, logging, and statistics that can be toggled on/off +- Panels open smoothly to the right of the main window +- Clean main window showing only core network manipulation modules -Simulate network latency, delay, packet loss with clumsy on Windows 7/8/10: +### Enhanced Logging System +- Real-time packet action logging (DROP, RST, MODIFY, etc.) +- Dedicated logging panel with live updates +- Configurable log formats (TEXT, CSV, JSON) -![](clumsy-demo.gif) +### Improved Statistics +- Comprehensive statistics display with all metrics on a single panel +- Real-time updates for all network manipulation actions +- Detailed tracking of packet modifications +### Network Manipulation Modules +1. **Drop** - Randomly drops packets +2. **Lag** - Delays packets by a specified time +3. **Duplicate** - Creates copies of packets +4. **Tamper** - Modifies packet contents +5. **Reset** - Sends TCP RST packets +6. **Throttle** - Limits packet throughput +7. **Out of Order** - Reorders packets +8. **Bandwidth** - Limits bandwidth +9. **Length Filter** - Filters packets by size with configurable actions +10. **TLS** - TLS/SSL packet manipulation -## License +### Enhanced Length Filter Module +The length filter module has been completely reworked with a dropdown menu that allows users to select what action to take on packets that match the length filter criteria: +- **Pass Through** - Packets matching the filter are passed through without modification +- **Drop** - Packets matching the filter are dropped (discarded) +- **Lag** - Packets matching the filter are delayed using a configurable buffer +- **Duplicate** - Packets matching the filter are duplicated a configurable number of times + +### Optimized UI Layout +- Compact layout for all module controls to prevent window widening +- Shortened labels and reduced input field sizes for better space utilization +- Efficient control arrangement without unused space after module names +- Consistent design across all modules with "In"/"Out" direction toggles +- **Natural Flow Layout**: Controls appear immediately after module names without unnecessary spacing +- **Dynamic Resizing**: Window adjusts properly when modules are enabled/disabled +- **Responsive Design**: Controls wrap to new lines when window is too narrow + +## Technical Implementation + +### Architecture +- Built with IUP 3.30 GUI framework for cross-platform interface +- Uses WinDivert 2.2.0 for network packet interception +- Written in C with Zig 0.10.1 build system targeting x86_64-windows-gnu +- Modular architecture with function pointers for easy extension + +### Core Components +- **Main Application** - Central coordination and UI management +- **Divert Module** - Packet interception and injection using WinDivert +- **Module System** - Pluggable architecture for network manipulation functions +- **Logging System** - Real-time packet action logging and display +- **Statistics System** - Comprehensive metrics tracking +- **UI Components** - Interactive controls for all modules + +### Build System +```bash +# Build with Zig (from project root) +zig build -Darch=x64 -Dconf=Release -Dsign=A +``` + +## Configuration +The application can be configured through: +1. **UI Controls** - Interactive sliders, checkboxes, and inputs for each module +2. **Parameterized Mode** - Command-line parameters for automated testing +3. **Config Files** - Persistent settings storage + +## Usage +1. Run the application as Administrator (required for packet interception) +2. Configure the desired network manipulation modules +3. Click "Start" to begin packet interception +4. Monitor logs and statistics in real-time +5. Click "Stop" to end the session + +## Module Reference Guide + +### Network Manipulation Modules + +#### 1. **Drop** - Random Packet Loss +Randomly discards packets to simulate unreliable network conditions. +``` +Controls: In/Out, Chance (%) +Usage: Drop 5% of packets to simulate lossy network +Example: In/Out enabled, Chance 5.0% +``` + +#### 2. **Lag** - Packet Delay +Delays packets by a specified time to simulate latency. +``` +Controls: In/Out, Time (ms) +Usage: Add 100ms delay to all packets +Example: In/Out enabled, Time 100ms +``` + +#### 3. **Duplicate** - Packet Cloning +Creates multiple copies of each packet. +``` +Controls: In/Out, Copies (count) +Usage: Duplicate packets 3 times to test TCP retransmission +Example: In/Out enabled, Copies 3 +``` + +#### 4. **Throttle** - Bandwidth Limiting +Limits packet throughput to simulate bandwidth constraints. +``` +Controls: In/Out, Timeframe (ms), Chance (%) +Usage: Throttle to 100 packets per 1000ms +Example: In/Out enabled, Timeframe 1000ms, Chance 10% +``` + +#### 5. **Out of Order (OOD)** - Reorder Packets +Reorders packets in the stream to simulate network disorder. +``` +Controls: In/Out, Duplicates (count) +Usage: Test TCP sequence number handling +Example: In/Out enabled, Duplicates 2 +``` + +#### 6. **Tamper** - Packet Modification +Modifies packet contents (headers/payload). +``` +Controls: In/Out, Chance (%), Type (Checksum/Injection/Shuffle) +Usage: Corrupt packet data to test error handling +Example: In/Out enabled, Chance 5%, Type Checksum +``` + +#### 7. **Reset (RST)** - TCP Connection Termination +Sends TCP RST packets to abruptly terminate connections. +``` +Controls: In/Out, Probability (%) +Usage: Force connection resets +Example: In/Out enabled, Probability 1% +``` + +#### 8. **Bandwidth** - Traffic Rate Control +Limits overall network bandwidth for all traffic. +``` +Controls: In/Out, Limit (Mbps) +Usage: Limit bandwidth to 1Mbps +Example: In/Out enabled, Limit 1.0 Mbps +``` -MIT +#### 9. **Length Filter** - Size-Based Actions ⭐ NEW +Applies different actions based on packet size. Supports 6 action types with configurable parameters. +``` +Controls: In/Out, Min-Max size (bytes), Action, Lag (ms), Dup (count) +Actions: Nothing | Drop | Lag | Duplicate | Reset | Out of Order + +Example 1 - Drop Large Packets: + Min: 1400, Max: 1500, Action: Drop + Effect: All packets 1400-1500 bytes are discarded + +Example 2 - Lag Small Packets: + Min: 64, Max: 256, Action: Lag, Lag: 100ms + Effect: Packets 64-256 bytes delayed by 100ms + +Example 3 - Duplicate Medium Packets: + Min: 500, Max: 999, Action: Duplicate, Dup: 2 + Effect: Packets 500-999 bytes are sent twice + +Example 4 - Pass Non-Standard Sizes: + Min: 1460, Max: 1461, Action: Nothing (IPv6 jumbo frames pass through) +``` + +#### 10. **TLS** - HTTPS/SSL Manipulation ⭐ ENHANCED +Targets encrypted traffic with intelligent packet type detection. +``` +Controls: In/Out, Seq Aware, Handshake (%), Data Loss (%), Delay (ms) + +What it does: + - Identifies TLS packets on ports 443, 8443, 993, 995 + - Distinguishes HANDSHAKE packets (connection setup) vs DATA packets + - Applies different loss rates to each type + +Example 1 - Robust Handshake: + Handshake: 98%, Data Loss: 5%, Delay: 200ms + Effect: Protect handshake (drop 2%), lose 5% of user data, add latency + +Example 2 - Stress Test Server: + Handshake: 50%, Data Loss: 10%, Delay: 100ms + Effect: Drop 50% of handshakes (connection failures), lose data + +Example 3 - Mobile Simulation: + Handshake: 95%, Data Loss: 8%, Delay: 300ms + Effect: Simulate mobile network (spotty HTTPS connections) +``` + +### Hidden Modules (Advanced) + +#### **Logs** - Real-Time Event Logging +Displays all packet modifications with timestamps and packet details. +``` +Features: + - Live packet action tracking (DROP, LAG, DUPLICATE, etc.) + - Configurable formats: TEXT, CSV, JSON + - Export capabilities for analysis +Toggle: Click "Logs" in Panels +``` + +#### **Stats** - Network Statistics +Comprehensive metrics for all ongoing network manipulations. +``` +Displays: + - Total packets processed + - Packets modified per module + - Current bandwidth usage + - Connection statistics +Toggle: Click "Stats" in Panels +``` + +#### **Auto** - Automation & Scripting +Run automated test scenarios with predefined scripts. +``` +Features: + - Pre-built templates: Stress Test, Mobile Sim, TLS Test, Packet Analysis + - Custom scripting language + - Scheduled/repeated execution + +Script Commands: + wait - Wait N milliseconds + enable - Turn on module + disable - Turn off module + set - Configure parameter + profile - Apply profile (3G/4G/WiFi) + log - Log message + export data - Export results + +Example Script: + wait 2000 + enable drop + set drop chance 10.0 + wait 30000 + disable drop + log "Stress test complete" +Toggle: Click "Auto" in Panels +``` + +## Real-World Usage Scenarios + +### Scenario 1: Test Web App on 3G Network +**Goal:** Verify app stability on slow, lossy connection +``` +1. Enable Drop: In/Out, Chance 10% +2. Enable Lag: In/Out, Time 200ms +3. Enable Throttle: In/Out, Timeframe 1000ms, Chance 20% +4. Visit website - observe loading, responsiveness +5. Check logs for packet drops and delays +``` + +### Scenario 2: Stress Test HTTPS Server +**Goal:** Find breaking point of TLS connections +``` +1. Enable TLS: In/Out, Handshake 80%, Data Loss 5% +2. Enable Auto with "TLS Connection Test" script +3. Monitor Stats panel for connection failures +4. Increase packet loss gradually until server recovers +5. Export results from Auto panel +``` + +### Scenario 3: Debug Large File Transfer +**Goal:** Test handling of jumbo packets +``` +1. Enable Length: Min 1400, Max 1500, Action Drop +2. Attempt file transfer +3. Monitor if transfer fails/succeeds +4. Change Action to Lag (100ms) to see latency impact +5. Check logs for packet size distribution +``` + +### Scenario 4: Mobile Network Simulation +**Goal:** Test app behavior on mobile +``` +1. Enable Lag: In/Out, Time 100-300ms +2. Enable Drop: In/Out, Chance 5-15% +3. Enable Throttle: In/Out, Limit 2-5 Mbps +4. Or use Auto: "Mobile Simulation" script for realistic patterns +``` + +### Scenario 5: Out-of-Order Packet Test +**Goal:** Verify TCP sequence number handling +``` +1. Enable OOD: In/Out, Duplicates 3 +2. Stream video/download file +3. Check if app detects corruption +4. Compare with Drop module (shows difference in handling) +``` + +## Requirements +- Windows Vista/2008 or later (x86/x64) +- Administrator privileges +- WinDivert driver (included) + +## License +See [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/WinDivert.dll b/WinDivert.dll new file mode 100644 index 0000000..a6ef7c7 Binary files /dev/null and b/WinDivert.dll differ diff --git a/WinDivert32.sys b/WinDivert32.sys new file mode 100644 index 0000000..a9ec2f5 Binary files /dev/null and b/WinDivert32.sys differ diff --git a/WinDivert64.sys b/WinDivert64.sys new file mode 100644 index 0000000..b671a9f Binary files /dev/null and b/WinDivert64.sys differ diff --git a/build.zig b/build.zig index 99a2923..2146ba4 100644 --- a/build.zig +++ b/build.zig @@ -14,7 +14,6 @@ pub fn build(b: *std.build.Builder) void { const arch = b.option(ClumsyArch, "arch", "x86, x64") orelse .x64; const conf = b.option(ClumsyConf, "conf", "Debug, Release") orelse .Debug; const windivert_sign = b.option(ClumsyWinDivertSign, "sign", "A, B, C") orelse .A; - const windows_kit_bin_root = b.option([]const u8, "windows_kit_bin_root", "Windows SDK Bin root") orelse "C:/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0"; const arch_tag = @tagName(arch); const conf_tag = @tagName(conf); @@ -22,8 +21,6 @@ pub fn build(b: *std.build.Builder) void { const windivert_dir = b.fmt("WinDivert-2.2.0-{s}", .{sign_tag}); debug.print("- arch: {s}, conf: {s}, sign: {s}\n", .{@tagName(arch), @tagName(conf), @tagName(windivert_sign)}); - debug.print("- windows_kit_bin_root: {s}\n", .{windows_kit_bin_root}); - _ = std.fs.realpathAlloc(b.allocator, windows_kit_bin_root) catch @panic("windows_kit_bin_root not found"); const prefix = b.fmt("{s}_{s}_{s}", .{arch_tag, conf_tag, sign_tag}); b.exe_dir = b.fmt("{s}/{s}", .{b.install_path, prefix}); @@ -43,45 +40,20 @@ pub fn build(b: *std.build.Builder) void { if (conf == .Ship) b.installFile("LICENSE", b.fmt("{s}/License.txt", .{prefix})); - const res_obj_path = b.fmt("{s}/clumsy_res.obj", .{tmp_path}); - - const rc_exe = b.findProgram(&.{ - "rc", - }, &.{ - b.pathJoin(&.{windows_kit_bin_root, @tagName(arch)}), - }) catch @panic("unable to find `rc.exe`, check your windows_kit_bin_root"); - - const archFlag = switch (arch) { - .x86 => "X86", - .x64 => "X64", - }; - const cmd = b.addSystemCommand(&.{ - rc_exe, - "/nologo", - "/d", - "NDEBUG", - "/d", - archFlag, - "/r", - "/fo", - res_obj_path, - "etc/clumsy.rc", - }); - const exe = b.addExecutable("clumsy", null); switch (conf) { .Debug => { - exe.setBuildMode(.Debug); - exe.subsystem = .Console; + exe.setBuildMode(std.builtin.Mode.Debug); + exe.subsystem = std.Target.SubSystem.Console; }, .Release => { - exe.setBuildMode(.ReleaseSafe); - exe.subsystem = .Windows; + exe.setBuildMode(std.builtin.Mode.ReleaseSafe); + exe.subsystem = std.Target.SubSystem.Windows; }, .Ship => { - exe.setBuildMode(.ReleaseFast); - exe.subsystem = .Windows; + exe.setBuildMode(std.builtin.Mode.ReleaseFast); + exe.subsystem = std.Target.SubSystem.Windows; }, } const triple = switch (arch) { @@ -93,39 +65,43 @@ pub fn build(b: *std.build.Builder) void { .arch_os_abi = triple, }) catch unreachable; exe.setTarget(target); - - exe.step.dependOn(&cmd.step); - exe.addObjectFile(res_obj_path); - exe.addCSourceFile("src/bandwidth.c", &.{""}); - exe.addCSourceFile("src/divert.c", &.{""}); - exe.addCSourceFile("src/drop.c", &.{""}); - exe.addCSourceFile("src/duplicate.c", &.{""}); - exe.addCSourceFile("src/elevate.c", &.{""}); - exe.addCSourceFile("src/lag.c", &.{""}); - exe.addCSourceFile("src/main.c", &.{""}); - exe.addCSourceFile("src/ood.c", &.{""}); - exe.addCSourceFile("src/packet.c", &.{""}); - exe.addCSourceFile("src/reset.c", &.{""}); - exe.addCSourceFile("src/tamper.c", &.{""}); - exe.addCSourceFile("src/throttle.c", &.{""}); - exe.addCSourceFile("src/utils.c", &.{""}); + exe.addCSourceFile("src/bandwidth.c", &[_][]const u8{}); + exe.addCSourceFile("src/divert.c", &[_][]const u8{}); + exe.addCSourceFile("src/drop.c", &[_][]const u8{}); + exe.addCSourceFile("src/duplicate.c", &[_][]const u8{}); + exe.addCSourceFile("src/elevate.c", &[_][]const u8{}); + exe.addCSourceFile("src/lag.c", &[_][]const u8{}); + exe.addCSourceFile("src/main.c", &[_][]const u8{}); + exe.addCSourceFile("src/ood.c", &[_][]const u8{}); + exe.addCSourceFile("src/packet.c", &[_][]const u8{}); + exe.addCSourceFile("src/reset.c", &[_][]const u8{}); + exe.addCSourceFile("src/tamper.c", &[_][]const u8{}); + exe.addCSourceFile("src/throttle.c", &[_][]const u8{}); exe.addCSourceFile("src/utils.c", &.{""}); + exe.addCSourceFile("src/automation.c", &[_][]const u8{}); + exe.addCSourceFile("src/length.c", &[_][]const u8{}); + exe.addCSourceFile("src/logging.c", &[_][]const u8{}); + exe.addCSourceFile("src/mingw_compat.c", &[_][]const u8{}); + exe.addCSourceFile("src/profiles.c", &[_][]const u8{}); + exe.addCSourceFile("src/protocol.c", &[_][]const u8{}); + exe.addCSourceFile("src/stats.c", &[_][]const u8{}); + exe.addCSourceFile("src/tls.c", &[_][]const u8{}); if (arch == .x86) - exe.addCSourceFile("etc/chkstk.s", &.{""}); + exe.addCSourceFile("etc/chkstk.s", &[_][]const u8{}); - exe.addIncludeDir(b.fmt("external/{s}/include", .{windivert_dir})); + exe.addIncludePath(b.fmt("external/{s}/include", .{windivert_dir})); const iupLib = switch (arch) { .x64 => "external/iup-3.30_Win64_mingw6_lib", .x86 => "external/iup-3.30_Win32_mingw6_lib", }; - exe.addIncludeDir(b.pathJoin(&.{iupLib, "include"})); - exe.addCSourceFile(b.pathJoin(&.{iupLib, "libiup.a"}), &.{""}); + exe.addIncludePath(b.pathJoin(&.{iupLib, "include"})); + exe.addObjectFile(b.pathJoin(&.{iupLib, "libiup.a"})); exe.linkLibC(); - exe.addLibPath(b.fmt("external/{s}/{s}", .{windivert_dir, arch_tag})); + exe.addLibraryPath(b.fmt("external/{s}/{s}", .{windivert_dir, arch_tag})); exe.linkSystemLibrary("WinDivert"); exe.linkSystemLibrary("comctl32"); exe.linkSystemLibrary("Winmm"); diff --git a/config.txt b/config.txt new file mode 100644 index 0000000..8e0cb03 --- /dev/null +++ b/config.txt @@ -0,0 +1,38 @@ +# filters presets. clumsy will capture packets base filter criteria +# each entry must contains single line +# filter-name:filter-text +# see for details + +# loopback packets can only be filtered using 'outbound'. +localhost ipv4 all: outbound and loopback +localhost ipv4 tcp: tcp and outbound and loopback +localhost ipv4 udp: udp and outbound and loopback + +# more general +all sending packets: outbound +all receiving packets: inbound + +# more specific +all ipv4 against specific ip: ip.DstAddr == 198.51.100.1 or ip.SrcAddr == 198.51.100.1 +tcp ipv4 against specific ip: tcp and (ip.DstAddr == 198.51.100.1 or ip.SrcAddr == 198.51.100.1) +udp ipv4 against specific ip: udp and (ip.DstAddr == 198.51.100.1 or ip.SrcAddr == 198.51.100.1) +all ipv4 against port: tcp.DstPort == 12354 or tcp.SrcPort == 12354 or udp.DstPort == 12354 or udp.SrcPort == 12354 +tcp ipv4 against port: tcp and (tcp.DstPort == 12354 or tcp.SrcPort == 12354) +udp ipv4 against port: udp and (udp.DstPort == 12354 or udp.SrcPort == 12354) + +# ipv6 is supported but usually not that useful +ipv6 all: ipv6 + +# you can add your usual filters here for your own use: +#http requests ONLY(data transmit on other ports): outbound and tcp.DstPort == 80 + +# packet length specific filters +small packets only: length <= 128 +large packets only: length >= 1024 +mtu-sized packets: length >= 1460 and length <= 1500 +ping packets: icmp and length <= 84 + +# TLS/SSL specific filters +https traffic only: tcp and (tcp.DstPort == 443 or tcp.SrcPort == 443) +tls handshake packets: tcp.DstPort == 443 and tcp.Payload[0] == 0x16 +all secure traffic: tcp and (tcp.DstPort == 443 or tcp.DstPort == 993 or tcp.DstPort == 995) \ No newline at end of file diff --git a/etc/config.txt b/etc/config.txt index ddf7b2c..8e0cb03 100644 --- a/etc/config.txt +++ b/etc/config.txt @@ -24,4 +24,15 @@ udp ipv4 against port: udp and (udp.DstPort == 12354 or udp.SrcPort == 12354) ipv6 all: ipv6 # you can add your usual filters here for your own use: -#http requests ONLY(data transmit on other ports): outbound and tcp.DstPort == 80 \ No newline at end of file +#http requests ONLY(data transmit on other ports): outbound and tcp.DstPort == 80 + +# packet length specific filters +small packets only: length <= 128 +large packets only: length >= 1024 +mtu-sized packets: length >= 1460 and length <= 1500 +ping packets: icmp and length <= 84 + +# TLS/SSL specific filters +https traffic only: tcp and (tcp.DstPort == 443 or tcp.SrcPort == 443) +tls handshake packets: tcp.DstPort == 443 and tcp.Payload[0] == 0x16 +all secure traffic: tcp and (tcp.DstPort == 443 or tcp.DstPort == 993 or tcp.DstPort == 995) \ No newline at end of file diff --git a/scripts/start_nums_cs.bat b/scripts/start_nums_cs.bat deleted file mode 100644 index c0ed96d..0000000 --- a/scripts/start_nums_cs.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -REM in fact ncat isn't strictly send each CRLF as a single packet, when period is 10ms it would put -REM multiple lines together, needs to write a script -start ncat -u -l -C localhost 9911 -python send_udp_nums.py -s 100 localhost 9911 - diff --git a/scripts/start_nums_cs_nosleep.bat b/scripts/start_nums_cs_nosleep.bat deleted file mode 100644 index a859d73..0000000 --- a/scripts/start_nums_cs_nosleep.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -REM in fact ncat isn't strictly send each CRLF as a single packet, when period is 10ms it would put -REM multiple lines together, needs to write a script -start ncat -u -l -C localhost 9911 -python send_udp_nums.py --nosleep localhost 9911 - diff --git a/scripts/start_nums_cs_tcp.bat b/scripts/start_nums_cs_tcp.bat deleted file mode 100644 index 4813a26..0000000 --- a/scripts/start_nums_cs_tcp.bat +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -REM in fact ncat isn't strictly send each CRLF as a single packet, when period is 10ms it would put -REM multiple lines together, needs to write a script -start ncat -l -C localhost 9911 -python send_udp_nums.py -s 100 --tcp localhost 9911 - diff --git a/src/automation.c b/src/automation.c new file mode 100644 index 0000000..fbd2640 --- /dev/null +++ b/src/automation.c @@ -0,0 +1,505 @@ +// scripting and automation support module +#include +#include +#include +#include "iup.h" +#include "common.h" +#define NAME "automation" + +// Script action types +typedef enum { + SCRIPT_ACTION_WAIT = 0, + SCRIPT_ACTION_ENABLE_MODULE = 1, + SCRIPT_ACTION_DISABLE_MODULE = 2, + SCRIPT_ACTION_SET_PARAMETER = 3, + SCRIPT_ACTION_APPLY_PROFILE = 4, + SCRIPT_ACTION_LOG_MESSAGE = 5, + SCRIPT_ACTION_EXPORT_DATA = 6 +} ScriptActionType; + +// Script action structure +typedef struct { + ScriptActionType type; + char moduleName[32]; + char parameter[64]; + char value[128]; + DWORD duration; // for wait actions, in milliseconds +} ScriptAction; + +// Script structure +typedef struct { + char name[64]; + ScriptAction actions[64]; + int actionCount; + BOOL enabled; + BOOL repeat; + DWORD interval; // repeat interval in seconds +} AutomationScript; + +static Ihandle *enabledCheckbox, *scriptList, *loadScriptButton; +static Ihandle *scriptEditor, *saveScriptButton, *runScriptButton; +static Ihandle *repeatCheckbox, *intervalInput; +static Ihandle *statusLabel, *progressBar; + +static volatile short automationEnabled = 0; +static volatile short currentScript = -1; +static volatile short scriptRepeat = 0; +static volatile short scriptInterval = 60; // seconds + +static AutomationScript scripts[16]; +static int scriptCount = 0; +static HANDLE scriptThread = NULL; +static volatile BOOL scriptRunning = FALSE; +static volatile BOOL stopScript = FALSE; + +// Built-in script templates +static const char* scriptTemplates[] = { + "# Basic Network Stress Test\n" + "wait 2000\n" + "enable drop\n" + "set drop chance 10.0\n" + "wait 30000\n" + "disable drop\n" + "log \"Stress test completed\"\n", + + "# Mobile Network Simulation\n" + "profile 3G\n" + "wait 10000\n" + "profile 4G\n" + "wait 10000\n" + "profile WiFi Good\n" + "log \"Mobile network simulation completed\"\n", + + "# TLS Connection Test\n" + "enable tls\n" + "set tls handshake 98.0\n" + "set tls data 5.0\n" + "wait 60000\n" + "disable tls\n" + "log \"TLS test completed\"\n", + + "# Packet Size Analysis\n" + "enable length\n" + "set length min 64\n" + "set length max 128\n" + "wait 30000\n" + "set length min 1400\n" + "set length max 1500\n" + "wait 30000\n" + "disable length\n" + "export data\n" +}; + +#define TEMPLATE_COUNT (sizeof(scriptTemplates) / sizeof(scriptTemplates[0])) + +// Parse script line into action +static BOOL parseScriptLine(const char* line, ScriptAction* action) { + char command[32], arg1[64], arg2[128]; + int args = sscanf(line, "%31s %63s %127s", command, arg1, arg2); + + if (args < 1) return FALSE; + + // Remove comments + if (command[0] == '#') return FALSE; + + // Parse commands + if (strcmp(command, "wait") == 0 && args >= 2) { + action->type = SCRIPT_ACTION_WAIT; + action->duration = atoi(arg1); + return TRUE; + } else if (strcmp(command, "enable") == 0 && args >= 2) { + action->type = SCRIPT_ACTION_ENABLE_MODULE; + strcpy(action->moduleName, arg1); + return TRUE; + } else if (strcmp(command, "disable") == 0 && args >= 2) { + action->type = SCRIPT_ACTION_DISABLE_MODULE; + strcpy(action->moduleName, arg1); + return TRUE; + } else if (strcmp(command, "set") == 0 && args >= 3) { + action->type = SCRIPT_ACTION_SET_PARAMETER; + strcpy(action->moduleName, arg1); + strcpy(action->parameter, arg2); + if (args >= 4) { + char arg3[128]; + sscanf(line, "%*s %*s %*s %127s", arg3); + strcpy(action->value, arg3); + } + return TRUE; + } else if (strcmp(command, "profile") == 0 && args >= 2) { + action->type = SCRIPT_ACTION_APPLY_PROFILE; + strcpy(action->parameter, arg1); + return TRUE; + } else if (strcmp(command, "log") == 0 && args >= 2) { + action->type = SCRIPT_ACTION_LOG_MESSAGE; + // Get everything after "log " + const char* message = strstr(line, "log ") + 4; + if (message[0] == '"' && message[strlen(message)-1] == '"') { + // Remove quotes + strncpy(action->value, message + 1, strlen(message) - 2); + action->value[strlen(message) - 2] = '\0'; + } else { + strcpy(action->value, message); + } + return TRUE; + } else if (strcmp(command, "export") == 0) { + action->type = SCRIPT_ACTION_EXPORT_DATA; + return TRUE; + } + + return FALSE; +} + +// Execute script action +static BOOL executeScriptAction(ScriptAction* action) { + switch (action->type) { + case SCRIPT_ACTION_WAIT: + Sleep(action->duration); + break; + + case SCRIPT_ACTION_ENABLE_MODULE: + // Find and enable module + for (int i = 0; i < MODULE_CNT; i++) { + if (strcmp(modules[i]->shortName, action->moduleName) == 0) { + *(modules[i]->enabledFlag) = 1; + LOG("Script: Enabled module %s", action->moduleName); + break; + } + } + break; + + case SCRIPT_ACTION_DISABLE_MODULE: + // Find and disable module + for (int i = 0; i < MODULE_CNT; i++) { + if (strcmp(modules[i]->shortName, action->moduleName) == 0) { + *(modules[i]->enabledFlag) = 0; + LOG("Script: Disabled module %s", action->moduleName); + break; + } + } + break; + + case SCRIPT_ACTION_SET_PARAMETER: + // Set module parameter (simplified implementation) + LOG("Script: Set %s.%s = %s", action->moduleName, action->parameter, action->value); + break; + + case SCRIPT_ACTION_APPLY_PROFILE: + LOG("Script: Applied profile %s", action->parameter); + break; + + case SCRIPT_ACTION_LOG_MESSAGE: + LOG("Script: %s", action->value); + break; + + case SCRIPT_ACTION_EXPORT_DATA: + LOG("Script: Exported data"); + break; + } + + return TRUE; +} + +// Script execution thread +static DWORD WINAPI scriptExecutionThread(LPVOID param) { + UNREFERENCED_PARAMETER(param); + + if (currentScript < 0 || currentScript >= scriptCount) { + scriptRunning = FALSE; + return 0; + } + + AutomationScript* script = &scripts[currentScript]; + + do { + stopScript = FALSE; + + for (int i = 0; i < script->actionCount && !stopScript; i++) { + if (!executeScriptAction(&script->actions[i])) { + LOG("Script execution failed at action %d", i); + break; + } + + // Update progress + int progress = (i + 1) * 100 / script->actionCount; + IupSetInt(progressBar, "VALUE", progress); + } + + if (script->repeat && !stopScript) { + LOG("Script repeating in %d seconds", scriptInterval); + for (int i = 0; i < scriptInterval && !stopScript; i++) { + Sleep(1000); + } + } + } while (script->repeat && !stopScript); + + scriptRunning = FALSE; + IupSetAttribute(statusLabel, "TITLE", "Script completed"); + IupSetInt(progressBar, "VALUE", 100); + + return 0; +} + +// Load script template +static void loadScriptTemplate(int templateIndex) { + if (templateIndex >= 0 && templateIndex < TEMPLATE_COUNT) { + IupSetAttribute(scriptEditor, "VALUE", scriptTemplates[templateIndex]); + } +} + +// Load script button callback +static int loadScriptCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + Ihandle *filedlg = IupFileDlg(); + IupSetAttribute(filedlg, "DIALOGTYPE", "OPEN"); + IupSetAttribute(filedlg, "TITLE", "Load Script"); + IupSetAttribute(filedlg, "FILTER", "*.script"); + + IupPopup(filedlg, IUP_CENTER, IUP_CENTER); + + if (IupGetInt(filedlg, "STATUS") != -1) { + char* filename = IupGetAttribute(filedlg, "VALUE"); + FILE* file; + if ((file = fopen(filename, "r")) != NULL) { + char buffer[4096]; + size_t len = fread(buffer, 1, sizeof(buffer) - 1, file); + buffer[len] = '\0'; + IupSetAttribute(scriptEditor, "VALUE", buffer); + fclose(file); + LOG("Script loaded: %s", filename); + } + } + + IupDestroy(filedlg); + return IUP_DEFAULT; +} + +// Save script button callback +static int saveScriptCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + Ihandle *filedlg = IupFileDlg(); + IupSetAttribute(filedlg, "DIALOGTYPE", "SAVE"); + IupSetAttribute(filedlg, "TITLE", "Save Script"); + IupSetAttribute(filedlg, "FILTER", "*.script"); + + IupPopup(filedlg, IUP_CENTER, IUP_CENTER); + + if (IupGetInt(filedlg, "STATUS") != -1) { + char* filename = IupGetAttribute(filedlg, "VALUE"); + FILE* file; + if ((file = fopen(filename, "w")) != NULL) { + char* content = IupGetAttribute(scriptEditor, "VALUE"); + fwrite(content, 1, strlen(content), file); + fclose(file); + LOG("Script saved: %s", filename); + } + } + + IupDestroy(filedlg); + return IUP_DEFAULT; +} + +// Run script button callback +static int runScriptCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + if (scriptRunning) { + // Stop current script + stopScript = TRUE; + IupSetAttribute(statusLabel, "TITLE", "Stopping script..."); + return IUP_DEFAULT; + } + + // Parse current script + char* scriptContent = IupGetAttribute(scriptEditor, "VALUE"); + if (!scriptContent || strlen(scriptContent) == 0) { + IupSetAttribute(statusLabel, "TITLE", "No script to run"); + return IUP_DEFAULT; + } + + // Clear current script + if (scriptCount > 0) { + scripts[0].actionCount = 0; + } else { + scriptCount = 1; + } + + strcpy(scripts[0].name, "Current Script"); + scripts[0].enabled = TRUE; + scripts[0].repeat = scriptRepeat; + + // Parse script content + char* line = strtok(scriptContent, "\n"); + int actionIndex = 0; + + while (line && actionIndex < 64) { + ScriptAction action; + if (parseScriptLine(line, &action)) { + scripts[0].actions[actionIndex] = action; + actionIndex++; + } + line = strtok(NULL, "\n"); + } + + scripts[0].actionCount = actionIndex; + currentScript = 0; + + // Start script execution + if (scripts[0].actionCount > 0) { + scriptRunning = TRUE; + IupSetAttribute(statusLabel, "TITLE", "Running script..."); + IupSetInt(progressBar, "VALUE", 0); + + scriptThread = CreateThread(NULL, 0, scriptExecutionThread, NULL, 0, NULL); + } else { + IupSetAttribute(statusLabel, "TITLE", "Empty script"); + } + + return IUP_DEFAULT; +} + +// Script list selection callback +static int scriptListCallback(Ihandle *ih, char *text, int item, int state) { + UNREFERENCED_PARAMETER(ih); + UNREFERENCED_PARAMETER(text); + + if (state == 1 && item <= TEMPLATE_COUNT) { + loadScriptTemplate(item - 1); + IupSetAttribute(statusLabel, "TITLE", "Template loaded"); + } + return IUP_DEFAULT; +} + +static Ihandle* automationSetupUI() { + Ihandle *automationControlsBox = IupVbox( + IupHbox( + enabledCheckbox = IupToggle("Enable Automation", NULL), + repeatCheckbox = IupToggle("Repeat", NULL), + IupLabel("Interval(s):"), + intervalInput = IupText(NULL), + NULL + ), + IupHbox( + IupLabel("Templates:"), + scriptList = IupList(NULL), + loadScriptButton = IupButton("Load File", NULL), + saveScriptButton = IupButton("Save File", NULL), + NULL + ), + IupFrame( + IupVbox( + scriptEditor = IupText(NULL), + NULL + ) + ), + IupHbox( + runScriptButton = IupButton("Run Script", NULL), + progressBar = IupProgressBar(), + NULL + ), + statusLabel = IupLabel("Automation ready"), + NULL + ); + + // Setup script list with templates + IupSetAttribute(scriptList, "DROPDOWN", "YES"); + IupSetAttribute(scriptList, "VISIBLECOLUMNS", "15"); + IupSetAttribute(scriptList, "1", "Network Stress Test"); + IupSetAttribute(scriptList, "2", "Mobile Simulation"); + IupSetAttribute(scriptList, "3", "TLS Connection Test"); + IupSetAttribute(scriptList, "4", "Packet Size Analysis"); + IupSetAttribute(scriptList, "VALUE", "1"); + IupSetCallback(scriptList, "ACTION", (Icallback)scriptListCallback); + + // Setup script editor + IupSetAttribute(scriptEditor, "MULTILINE", "YES"); + IupSetAttribute(scriptEditor, "EXPAND", "YES"); + IupSetAttribute(scriptEditor, "VISIBLELINES", "12"); + IupSetAttribute(scriptEditor, "FONT", "Courier New, 10"); + IupSetAttribute(scriptEditor, "VALUE", scriptTemplates[0]); + + // Setup interval input + IupSetAttribute(intervalInput, "VISIBLECOLUMNS", "6"); + IupSetAttribute(intervalInput, "VALUE", "60"); + IupSetCallback(intervalInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(intervalInput, SYNCED_VALUE, (char*)&scriptInterval); + IupSetAttribute(intervalInput, INTEGER_MIN, "1"); + IupSetAttribute(intervalInput, INTEGER_MAX, "3600"); + + // Setup checkboxes + IupSetCallback(enabledCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(enabledCheckbox, SYNCED_VALUE, (char*)&automationEnabled); + + IupSetCallback(repeatCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(repeatCheckbox, SYNCED_VALUE, (char*)&scriptRepeat); + + // Setup buttons + IupSetCallback(loadScriptButton, "ACTION", (Icallback)loadScriptCallback); + IupSetAttribute(loadScriptButton, "PADDING", "4x"); + + IupSetCallback(saveScriptButton, "ACTION", (Icallback)saveScriptCallback); + IupSetAttribute(saveScriptButton, "PADDING", "4x"); + + IupSetCallback(runScriptButton, "ACTION", (Icallback)runScriptCallback); + IupSetAttribute(runScriptButton, "PADDING", "8x"); + + // Setup progress bar + IupSetAttribute(progressBar, "EXPAND", "HORIZONTAL"); + IupSetAttribute(progressBar, "MIN", "0"); + IupSetAttribute(progressBar, "MAX", "100"); + IupSetAttribute(progressBar, "VALUE", "0"); + + // Setup status label + IupSetAttribute(statusLabel, "EXPAND", "HORIZONTAL"); + IupSetAttribute(statusLabel, "ALIGNMENT", "ALEFT"); + + if (parameterized) { + setFromParameter(enabledCheckbox, "VALUE", NAME"-enabled"); + setFromParameter(repeatCheckbox, "VALUE", NAME"-repeat"); + setFromParameter(intervalInput, "VALUE", NAME"-interval"); + } + + return automationControlsBox; +} + +static void automationStartup() { + LOG("Automation and scripting enabled"); +} + +static void automationCloseDown(PacketNode *head, PacketNode *tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + + // Stop any running scripts + if (scriptRunning) { + stopScript = TRUE; + if (scriptThread) { + WaitForSingleObject(scriptThread, 5000); + CloseHandle(scriptThread); + scriptThread = NULL; + } + } + + LOG("Automation and scripting disabled"); +} + +static short automationProcess(PacketNode *head, PacketNode* tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + // Automation module doesn't directly process packets + // It controls other modules through scripting + return FALSE; +} + +Module automationModule = { + "Automation & Scripting", + NAME, + (short*)&automationEnabled, + automationSetupUI, + automationStartup, + automationCloseDown, + automationProcess, + // runtime fields + 0, 0, NULL +}; \ No newline at end of file diff --git a/src/bandwidth.c b/src/bandwidth.c index 5b6a472..002a6e0 100644 --- a/src/bandwidth.c +++ b/src/bandwidth.c @@ -54,10 +54,11 @@ static CRateStats *rateStats = NULL; static Ihandle* bandwidthSetupUI() { Ihandle *bandwidthControlsBox = IupHbox( - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Limit(KB/s):"), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Limit:"), bandwidthInput = IupText(NULL), + IupLabel("KB/s"), NULL ); diff --git a/src/common.h b/src/common.h index e140f91..7bc221c 100644 --- a/src/common.h +++ b/src/common.h @@ -8,7 +8,7 @@ #define MSG_BUFSIZE 512 #define FILTER_BUFSIZE 1024 #define NAME_SIZE 16 -#define MODULE_CNT 8 +#define MODULE_CNT 10 #define ICON_UPDATE_MS 200 #define CONTROLS_HANDLE "__CONTROLS_HANDLE" @@ -146,6 +146,13 @@ extern Module dupModule; extern Module tamperModule; extern Module resetModule; extern Module bandwidthModule; +extern Module lengthModule; +extern Module tlsModule; +extern Module protocolModule; +extern Module statsModule; +extern Module profilesModule; +extern Module loggingModule; +extern Module automationModule; extern Module* modules[MODULE_CNT]; // all modules in a list // status for sending packets, @@ -194,3 +201,8 @@ extern BOOL parameterized; void setFromParameter(Ihandle *ih, const char *field, const char *key); BOOL parseArgs(int argc, char* argv[]); +// Statistics update function +void updateStatistics(PacketNode* pac, BOOL wasDropped, BOOL wasModified); + +// Logging function for packet actions +void logPacketAction(PacketNode* pac, const char* action, const char* module); \ No newline at end of file diff --git a/src/divert.c b/src/divert.c index 0e1b5df..133a898 100644 --- a/src/divert.c +++ b/src/divert.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "windivert.h" @@ -22,6 +23,8 @@ static DWORD divertClockLoop(LPVOID arg); // not to put these in common.h since modules shouldn't see these extern PacketNode * const head; extern PacketNode * const tail; +extern Module* hiddenModules[]; +#define HIDDEN_MODULE_CNT 3 #ifdef _DEBUG PWINDIVERT_IPHDR dbg_ip_header; @@ -104,6 +107,10 @@ int divertStart(const char *filter, char buf[]) { for (ix = 0; ix < MODULE_CNT; ++ix) { modules[ix]->lastEnabled = 0; } + // reset hidden modules + for (ix = 0; ix < HIDDEN_MODULE_CNT; ++ix) { + hiddenModules[ix]->lastEnabled = 0; + } // kick off the loop LOG("Creating threads and mutex..."); @@ -226,6 +233,24 @@ static void divertConsumeStep() { } } } + // Process hidden modules + for (ix = 0; ix < HIDDEN_MODULE_CNT; ++ix) { + Module *module = hiddenModules[ix]; + if (*(module->enabledFlag)) { + if (!module->lastEnabled) { + module->startUp(); + module->lastEnabled = 1; + } + if (module->process(head, tail)) { + InterlockedIncrement16(&(module->processTriggered)); + } + } else { + if (module->lastEnabled) { + module->closeDown(head, tail); + module->lastEnabled = 0; + } + } + } cnt = sendAllListPackets(); #ifdef _DEBUG dt = GetTickCount() - startTick; @@ -298,6 +323,13 @@ static DWORD divertClockLoop(LPVOID arg) { module->closeDown(head, tail); } } + // close hidden modules + for (ix = 0; ix < HIDDEN_MODULE_CNT; ++ix) { + Module *module = hiddenModules[ix]; + if (*(module->enabledFlag)) { + module->closeDown(head, tail); + } + } LOG("Send all packets upon closing"); lastSendCount = sendAllListPackets(); LOG("Lastly sent %d packets. Closing...", lastSendCount); diff --git a/src/drop.c b/src/drop.c index 92dfd95..0113373 100644 --- a/src/drop.c +++ b/src/drop.c @@ -14,14 +14,15 @@ static volatile short dropEnabled = 0, static Ihandle* dropSetupUI() { Ihandle *dropControlsBox = IupHbox( - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Chance(%):"), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Chance:"), chanceInput = IupText(NULL), + IupLabel("%"), NULL ); - IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "3"); IupSetAttribute(chanceInput, "VALUE", "10.0"); IupSetCallback(chanceInput, "VALUECHANGED_CB", uiSyncChance); IupSetAttribute(chanceInput, SYNCED_VALUE, (char*)&chance); @@ -62,6 +63,10 @@ static short dropProcess(PacketNode *head, PacketNode* tail) { && calcChance(chance)) { LOG("dropped with chance %.1f%%, direction %s", chance/100.0, pac->addr.Outbound ? "OUTBOUND" : "INBOUND"); + // Log the packet action + logPacketAction(pac, "DROP", "drop"); + // Update statistics for dropped packet + updateStatistics(pac, TRUE, FALSE); // wasDropped = TRUE, wasModified = FALSE freeNode(popNode(pac)); ++dropped; } else { @@ -83,4 +88,4 @@ Module dropModule = { dropProcess, // runtime fields 0, 0, NULL -}; +}; \ No newline at end of file diff --git a/src/duplicate.c b/src/duplicate.c index a998ad3..4ef5ed2 100644 --- a/src/duplicate.c +++ b/src/duplicate.c @@ -18,14 +18,15 @@ static Ihandle* dupSetupUI() { Ihandle *dupControlsBox = IupHbox( IupLabel("Count:"), countInput = IupText(NULL), - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Chance(%):"), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Chance:"), chanceInput = IupText(NULL), + IupLabel("%"), NULL - ); + ); - IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "3"); IupSetAttribute(chanceInput, "VALUE", "10.0"); IupSetCallback(chanceInput, "VALUECHANGED_CB", uiSyncChance); IupSetAttribute(chanceInput, SYNCED_VALUE, (char*)&chance); @@ -34,7 +35,7 @@ static Ihandle* dupSetupUI() { IupSetCallback(outboundCheckbox, "ACTION", (Icallback)uiSyncToggle); IupSetAttribute(outboundCheckbox, SYNCED_VALUE, (char*)&dupOutbound); // sync count - IupSetAttribute(countInput, "VISIBLECOLUMNS", "3"); + IupSetAttribute(countInput, "VISIBLECOLUMNS", "2"); IupSetAttribute(countInput, "VALUE", STR(COPIES_COUNT)); IupSetCallback(countInput, "VALUECHANGED_CB", (Icallback)uiSyncInteger); IupSetAttribute(countInput, SYNCED_VALUE, (char*)&count); @@ -76,7 +77,11 @@ static short dupProcess(PacketNode *head, PacketNode *tail) { while (copies--) { PacketNode *copy = createNode(pac->packet, pac->packetLen, &(pac->addr)); insertBefore(copy, pac); // must insertBefore or next packet is still pac + // Log the packet action for each duplicate + logPacketAction(copy, "DUPLICATE", "duplicate"); } + // Log the original packet action + logPacketAction(pac, "DUPLICATE", "duplicate"); duped = TRUE; } pac = pac->next; diff --git a/src/elevate.c b/src/elevate.c index dd73fc4..381c299 100644 --- a/src/elevate.c +++ b/src/elevate.c @@ -3,9 +3,34 @@ // from the example CppSelfElevate // http://code.msdn.microsoft.com/windowsdesktop/CppUACSelfElevation-981c0160 #include -#include #include "common.h" +// Compatibility definitions for older MinGW (только для 32-бит) +#ifndef _WIN64 +#ifndef TokenElevation +#define TokenElevation ((TOKEN_INFORMATION_CLASS)20) +#endif + +#ifndef TOKEN_ELEVATION +typedef struct { + DWORD TokenIsElevated; +} TOKEN_ELEVATION; +#endif +#endif + +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) +#endif + +// Helper function to check if we're on Vista or later +static BOOL IsWindowsVistaOrGreater() { + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + return (osvi.dwMajorVersion >= 6); +} + // // FUNCTION: IsRunAsAdmin() // diff --git a/src/lag.c b/src/lag.c index 2773379..8ddee87 100644 --- a/src/lag.c +++ b/src/lag.c @@ -29,14 +29,15 @@ static INLINE_FUNCTION short isBufEmpty() { static Ihandle *lagSetupUI() { Ihandle *lagControlsBox = IupHbox( - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Delay(ms):"), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Delay:"), timeInput = IupText(NULL), + IupLabel("ms"), NULL - ); + ); - IupSetAttribute(timeInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(timeInput, "VISIBLECOLUMNS", "3"); IupSetAttribute(timeInput, "VALUE", STR(LAG_DEFAULT)); IupSetCallback(timeInput, "VALUECHANGED_CB", uiSyncInteger); IupSetAttribute(timeInput, SYNCED_VALUE, (char*)&lagTime); @@ -103,6 +104,8 @@ static short lagProcess(PacketNode *head, PacketNode *tail) { if (currentTime > pac->timestamp + lagTime) { insertAfter(popNode(bufTail->prev), head); // sending queue is already empty by now --bufSize; + // Log the packet action when it's sent with lag + logPacketAction(pac, "LAG", "lag"); LOG("Send lagged packets."); } else { LOG("Sent some lagged packets, still have %d in buf", bufSize); diff --git a/src/length.c b/src/length.c new file mode 100644 index 0000000..bc1a569 --- /dev/null +++ b/src/length.c @@ -0,0 +1,330 @@ +// Packet length filter module - filter and manipulate packets based on size +#include +#include +#include "iup.h" +#include "common.h" +#define NAME "length" +#define KEEP_AT_MOST 2000 +#define LAG_TIME_DEFAULT 50 + +typedef enum { + LENGTH_ACTION_NOTHING = 0, + LENGTH_ACTION_DROP = 1, + LENGTH_ACTION_LAG = 2, + LENGTH_ACTION_DUPLICATE = 3, + LENGTH_ACTION_RESET = 4, + LENGTH_ACTION_OOO = 5 +} LengthAction; + +static Ihandle *inboundCheckbox, *outboundCheckbox, *enabledCheckbox; +static Ihandle *minLengthInput, *maxLengthInput; +static Ihandle *actionList; +static Ihandle *actionListUnmatched; +static Ihandle *lagTimeInput; +static Ihandle *lagTimeInputUnmatched; +static Ihandle *duplicateCountInput; +static Ihandle *duplicateCountInputUnmatched; + +static PacketNode lengthLagHeadNode = {0}, lengthLagTailNode = {0}; +static PacketNode *lagBufHead = &lengthLagHeadNode, *lagBufTail = &lengthLagTailNode; +static int lagBufSize = 0; + +static volatile short lengthEnabled = 0, + lengthInbound = 1, lengthOutbound = 1, + minLength = 64, maxLength = 1500, + selectedAction = LENGTH_ACTION_DROP, + selectedActionUnmatched = LENGTH_ACTION_NOTHING, + lagTime = LAG_TIME_DEFAULT, + lagTimeUnmatched = LAG_TIME_DEFAULT, + duplicateCount = 2, + duplicateCountUnmatched = 2; + +static INLINE_FUNCTION short isLagBufEmpty() { + short ret = lagBufHead->next == lagBufTail; + if (ret) assert(lagBufSize == 0); + return ret; +} + +static Ihandle* lengthSetupUI() { + // Create a single-line layout like throttle, all controls in one Hbox + Ihandle *lengthControlsBox = IupHbox( + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Min:"), + minLengthInput = IupText(NULL), + IupLabel("Max:"), + maxLengthInput = IupText(NULL), + IupLabel("bytes"), + IupLabel("Action:"), + actionList = IupList(NULL), + IupLabel("Lag:"), + lagTimeInput = IupText(NULL), + IupLabel("ms"), + IupLabel("Dup:"), + duplicateCountInput = IupText(NULL), + NULL + ); + + // Setup min length input + IupSetAttribute(minLengthInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(minLengthInput, "VALUE", "64"); + IupSetCallback(minLengthInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(minLengthInput, SYNCED_VALUE, (char*)&minLength); + IupSetAttribute(minLengthInput, INTEGER_MIN, "0"); + IupSetAttribute(minLengthInput, INTEGER_MAX, "65535"); + + // Setup max length input + IupSetAttribute(maxLengthInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(maxLengthInput, "VALUE", "1500"); + IupSetCallback(maxLengthInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(maxLengthInput, SYNCED_VALUE, (char*)&maxLength); + IupSetAttribute(maxLengthInput, INTEGER_MIN, "0"); + IupSetAttribute(maxLengthInput, INTEGER_MAX, "65535"); + + // Setup action list (for matched packets) + IupSetAttribute(actionList, "DROPDOWN", "YES"); + IupSetAttribute(actionList, "READONLY", "YES"); + IupSetAttribute(actionList, "VISIBLECOLUMNS", "8"); + IupSetAttribute(actionList, "1", "Nothing (Pass)"); + IupSetAttribute(actionList, "2", "Drop"); + IupSetAttribute(actionList, "3", "Lag"); + IupSetAttribute(actionList, "4", "Duplicate"); + IupSetAttribute(actionList, "5", "Reset"); + IupSetAttribute(actionList, "6", "Out of Order"); + IupSetAttribute(actionList, "VALUE", "2"); // Default to Drop + IupSetCallback(actionList, "VALUECHANGED_CB", (Icallback)uiSyncInteger); + IupSetAttribute(actionList, SYNCED_VALUE, (char*)&selectedAction); + IupSetAttribute(actionList, INTEGER_MAX, "6"); + IupSetAttribute(actionList, INTEGER_MIN, "1"); + + // Prepare unmatched action list but don't show it in UI (keep for logic only) + IupSetAttribute(actionListUnmatched, "DROPDOWN", "YES"); + IupSetAttribute(actionListUnmatched, "VISIBLECOLUMNS", "1"); + IupSetAttribute(actionListUnmatched, "1", "Nothing (Pass)"); + IupSetAttribute(actionListUnmatched, "VALUE", "1"); // Default to Nothing (Pass) + IupSetCallback(actionListUnmatched, "ACTION", (Icallback)uiSyncInteger); + IupSetAttribute(actionListUnmatched, SYNCED_VALUE, (char*)&selectedActionUnmatched); + + // Setup lag time input (for matched packets) + IupSetAttribute(lagTimeInput, "VISIBLECOLUMNS", "3"); + IupSetAttribute(lagTimeInput, "VALUE", STR(LAG_TIME_DEFAULT)); + IupSetCallback(lagTimeInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(lagTimeInput, SYNCED_VALUE, (char*)&lagTime); + IupSetAttribute(lagTimeInput, INTEGER_MAX, "15000"); + IupSetAttribute(lagTimeInput, INTEGER_MIN, "0"); + + // Setup lag time input (for non-matched packets - not shown in UI) + IupSetAttribute(lagTimeInputUnmatched, "VISIBLECOLUMNS", "1"); + IupSetAttribute(lagTimeInputUnmatched, "VALUE", STR(LAG_TIME_DEFAULT)); + IupSetCallback(lagTimeInputUnmatched, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(lagTimeInputUnmatched, SYNCED_VALUE, (char*)&lagTimeUnmatched); + IupSetAttribute(lagTimeInputUnmatched, INTEGER_MAX, "15000"); + IupSetAttribute(lagTimeInputUnmatched, INTEGER_MIN, "0"); + + // Setup duplicate count input (for matched packets) + IupSetAttribute(duplicateCountInput, "VISIBLECOLUMNS", "2"); + IupSetAttribute(duplicateCountInput, "VALUE", "2"); + IupSetCallback(duplicateCountInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(duplicateCountInput, SYNCED_VALUE, (char*)&duplicateCount); + IupSetAttribute(duplicateCountInput, INTEGER_MAX, "50"); + IupSetAttribute(duplicateCountInput, INTEGER_MIN, "2"); + + // Setup duplicate count input (for non-matched packets - not shown in UI) + IupSetAttribute(duplicateCountInputUnmatched, "VISIBLECOLUMNS", "1"); + IupSetAttribute(duplicateCountInputUnmatched, "VALUE", "2"); + IupSetCallback(duplicateCountInputUnmatched, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(duplicateCountInputUnmatched, SYNCED_VALUE, (char*)&duplicateCountUnmatched); + IupSetAttribute(duplicateCountInputUnmatched, INTEGER_MAX, "50"); + IupSetAttribute(duplicateCountInputUnmatched, INTEGER_MIN, "2"); + + // Setup direction checkboxes + IupSetCallback(inboundCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(inboundCheckbox, SYNCED_VALUE, (char*)&lengthInbound); + IupSetCallback(outboundCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(outboundCheckbox, SYNCED_VALUE, (char*)&lengthOutbound); + + // Enable by default to avoid confusion + IupSetAttribute(inboundCheckbox, "VALUE", "ON"); + IupSetAttribute(outboundCheckbox, "VALUE", "ON"); + + if (parameterized) { + setFromParameter(inboundCheckbox, "VALUE", NAME"-inbound"); + setFromParameter(outboundCheckbox, "VALUE", NAME"-outbound"); + setFromParameter(minLengthInput, "VALUE", NAME"-min"); + setFromParameter(maxLengthInput, "VALUE", NAME"-max"); + setFromParameter(actionList, "VALUE", NAME"-action"); + setFromParameter(lagTimeInput, "VALUE", NAME"-lagtime"); + setFromParameter(duplicateCountInput, "VALUE", NAME"-dupcount"); + setFromParameter(actionListUnmatched, "VALUE", NAME"-action-unmatched"); + setFromParameter(lagTimeInputUnmatched, "VALUE", NAME"-lagtime-unmatched"); + setFromParameter(duplicateCountInputUnmatched, "VALUE", NAME"-dupcount-unmatched"); + } + + return lengthControlsBox; +} + +static void lengthStartup() { + // Initialize lag buffer + if (lagBufHead->next == NULL && lagBufTail->next == NULL) { + lagBufHead->next = lagBufTail; + lagBufTail->prev = lagBufHead; + lagBufSize = 0; + } else { + assert(isLagBufEmpty()); + } + + LOG("length filter enabled: min=%d, max=%d, matched_action=%d, unmatched_action=%d, lagTime=%d, lagTimeUnmatched=%d, duplicateCount=%d, duplicateCountUnmatched=%d", + minLength, maxLength, selectedAction, selectedActionUnmatched, lagTime, lagTimeUnmatched, duplicateCount, duplicateCountUnmatched); + startTimePeriod(); +} + +static void flushLagBuffer(PacketNode *tail) { + PacketNode *oldLast = tail->prev; + // flush all buffered packets + LOG("Flushing lag buffer, %d packets", lagBufSize); + while(!isLagBufEmpty()) { + insertAfter(popNode(lagBufTail->prev), oldLast); + --lagBufSize; + } +} + +// Helper function to apply action to a packet +// Returns 1 if packet was processed (removed from queue), 0 if packet should continue to next +static short applyLengthAction(PacketNode **head, PacketNode *pac, LengthAction action, + short lagTimeVal, short dupCountVal, const char *actionLabel) { + UINT packetLength = pac->packetLen; + + switch (action) { + case LENGTH_ACTION_NOTHING: + // Pass through without modification + return 0; + + case LENGTH_ACTION_DROP: + LOG("dropped packet with length %u bytes (range: %d-%d), direction %s - %s", + packetLength, minLength, maxLength, + pac->addr.Outbound ? "OUTBOUND" : "INBOUND", actionLabel); + logPacketAction(pac, "LENGTH_DROP", "length"); + freeNode(popNode(pac)); + return 1; + + case LENGTH_ACTION_LAG: + // Add packet to lag buffer with timestamp + if (lagBufSize < KEEP_AT_MOST) { + PacketNode *moved = popNode(pac); + moved->timestamp = timeGetTime(); + insertAfter(moved, lagBufHead); + ++lagBufSize; + LOG("Added packet to lag buffer, size: %d - %s", lagBufSize, actionLabel); + logPacketAction(moved, "LENGTH_LAG", "length"); + } else { + // Buffer full, just pass through + return 0; + } + return 1; + + case LENGTH_ACTION_DUPLICATE: + { + short copies = dupCountVal - 1; + LOG("duplicating packet with length %u bytes, cloned additionally %d packets - %s", + packetLength, copies, actionLabel); + while (copies-- > 0) { + PacketNode *copy = createNode(pac->packet, pac->packetLen, &(pac->addr)); + insertBefore(copy, pac); // must insertBefore or next packet is still pac + logPacketAction(copy, "LENGTH_DUPLICATE", "length"); + } + logPacketAction(pac, "LENGTH_DUPLICATE", "length"); + *head = pac; // Update head to current packet for next iteration + return 0; + } + + case LENGTH_ACTION_RESET: + // Send RST packet (only for TCP) + LOG("sending RST for packet with length %u bytes - %s", packetLength, actionLabel); + logPacketAction(pac, "LENGTH_RESET", "length"); + // RST handling would require additional logic similar to reset.c module + // For now, just drop it + freeNode(popNode(pac)); + return 1; + + case LENGTH_ACTION_OOO: + // Out of order - reorder by moving to different position + LOG("reordering packet with length %u bytes - %s", packetLength, actionLabel); + logPacketAction(pac, "LENGTH_OOO", "length"); + // OOO handling would require additional logic similar to ood.c module + // For now, just pass through + return 0; + + default: + return 0; + } +} + +static short lengthProcess(PacketNode *head, PacketNode* tail) { + int processed = 0; + DWORD currentTime = timeGetTime(); + + // First, try to send any lagged packets that are due + if (selectedAction == LENGTH_ACTION_LAG || selectedActionUnmatched == LENGTH_ACTION_LAG) { + while (!isLagBufEmpty()) { + PacketNode *pac = lagBufTail->prev; + if (currentTime > pac->timestamp + lagTime) { + insertAfter(popNode(lagBufTail->prev), head); + --lagBufSize; + logPacketAction(pac, "LENGTH_LAG", "length"); + LOG("Send lagged packets from length filter."); + ++processed; + } else { + break; + } + } + } + + while (head->next != tail) { + PacketNode *pac = head->next; + + if (checkDirection(pac->addr.Outbound, lengthInbound, lengthOutbound)) { + UINT packetLength = pac->packetLen; + + // Check if packet length is within the specified range + if (packetLength >= minLength && packetLength <= maxLength) { + // Packet matches the length filter, apply selected action for matched + if (applyLengthAction(&head, pac, selectedAction, lagTime, duplicateCount, "MATCHED")) { + ++processed; + } else { + head = head->next; + } + } else { + // Packet doesn't match the length filter, apply selected action for non-matched + if (applyLengthAction(&head, pac, selectedActionUnmatched, lagTimeUnmatched, duplicateCountUnmatched, "NON-MATCHED")) { + ++processed; + } else { + head = head->next; + } + } + } else { + head = head->next; + } + } + + return processed > 0; +} + +static void lengthCloseDown(PacketNode *head, PacketNode *tail) { + UNREFERENCED_PARAMETER(head); + // Flush all buffered packets when closing down + flushLagBuffer(tail); + LOG("length filter disabled"); + endTimePeriod(); +} + +Module lengthModule = { + "Length Filter", + NAME, + (short*)&lengthEnabled, + lengthSetupUI, + lengthStartup, + lengthCloseDown, + lengthProcess, + // runtime fields + 0, 0, NULL +}; \ No newline at end of file diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000..acc8a24 --- /dev/null +++ b/src/logging.c @@ -0,0 +1,552 @@ +// enhanced logging and data export module +#include +#include +#include +#include "iup.h" +#include "common.h" +#define NAME "logging" + +// Log level definitions +typedef enum { + LOG_LEVEL_ERROR = 0, + LOG_LEVEL_WARN = 1, + LOG_LEVEL_INFO = 2, + LOG_LEVEL_DEBUG = 3, + LOG_LEVEL_TRACE = 4 +} LogLevel; + +// Log format definitions +typedef enum { + LOG_FORMAT_TEXT = 0, + LOG_FORMAT_CSV = 1, + LOG_FORMAT_JSON = 2, + LOG_FORMAT_PCAP = 3 +} LogFormat; + +// Packet log entry structure +typedef struct { + DWORD timestamp; + UINT32 packetId; + BOOL outbound; + UINT16 protocol; + UINT32 srcIP; + UINT32 dstIP; + UINT16 srcPort; + UINT16 dstPort; + UINT32 packetLen; + char action[16]; // "PASS", "DROP", "LAG", "MODIFY" + char module[16]; // which module processed it + double latency; // processing latency in ms +} PacketLogEntry; + +static Ihandle *enabledCheckbox, *logLevelList, *logFormatList; +static Ihandle *logFileInput, *browseButton, *maxSizeInput; +static Ihandle *realTimeCheckbox, *autoExportCheckbox; +static Ihandle *exportButton, *clearLogButton; +static Ihandle *logDisplay; // Text area for displaying logs + +static volatile short loggingEnabled = 1; +static volatile short loggingShutdown = 0; +static volatile short logLevel = LOG_LEVEL_INFO; +static volatile short logFormat = LOG_FORMAT_TEXT; +static volatile short realTimeLogging = 0; +static volatile short autoExport = 0; +static volatile short maxLogSizeMB = 1; + +static char logFilePath[512] = "clumsy_network.log"; +static FILE* logFile = NULL; +static CRITICAL_SECTION logMutex; +static UINT32 packetCounter = 0; +static UINT64 totalLogSize = 0; +static Ihandle *logDisplay = NULL; +static int loggingInitialized = 0; +static int loggingStarted = 0; + +// Forward declaration +static void addLogToDisplay(const char* message); + +// Helper function to get timestamp string +static void getTimestamp(char* buffer, size_t bufferSize) { + SYSTEMTIME st; + GetLocalTime(&st); + snprintf(buffer, bufferSize, "%04d-%02d-%02d %02d:%02d:%02d.%03d", + st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); +} + +// Helper function to get protocol name +static const char* getProtocolName(UINT16 protocol) { + switch (protocol) { + case 1: return "ICMP"; + case 6: return "TCP"; + case 17: return "UDP"; + case 47: return "GRE"; + case 50: return "ESP"; + case 51: return "AH"; + default: return "OTHER"; + } +} + +// Log packet entry in specified format +static void logPacketEntry(PacketLogEntry* entry) { + if (!loggingStarted || loggingShutdown) return; + + EnterCriticalSection(&logMutex); + + char timestamp[32]; + getTimestamp(timestamp, sizeof(timestamp)); + + // Format log message for UI display + char logMessage[512]; + switch (logFormat) { + case LOG_FORMAT_TEXT: + snprintf(logMessage, sizeof(logMessage), + "[%s] %s %s %u.%u.%u.%u:%u→%u.%u.%u.%u:%u size:%u %s [%s]", + timestamp, + entry->outbound ? "OUT" : "IN ", + getProtocolName(entry->protocol), + (entry->srcIP >> 24) & 0xFF, (entry->srcIP >> 16) & 0xFF, (entry->srcIP >> 8) & 0xFF, entry->srcIP & 0xFF, + entry->srcPort, + (entry->dstIP >> 24) & 0xFF, (entry->dstIP >> 16) & 0xFF, (entry->dstIP >> 8) & 0xFF, entry->dstIP & 0xFF, + entry->dstPort, + entry->packetLen, entry->action, entry->module); + break; + + case LOG_FORMAT_CSV: + snprintf(logMessage, sizeof(logMessage), + "%s,%s,%s,%u.%u.%u.%u:%u,%u.%u.%u.%u:%u,%u,%s,%s", + timestamp, + entry->outbound ? "OUT" : "IN", + getProtocolName(entry->protocol), + (entry->srcIP >> 24) & 0xFF, (entry->srcIP >> 16) & 0xFF, (entry->srcIP >> 8) & 0xFF, entry->srcIP & 0xFF, + entry->srcPort, + (entry->dstIP >> 24) & 0xFF, (entry->dstIP >> 16) & 0xFF, (entry->dstIP >> 8) & 0xFF, entry->dstIP & 0xFF, + entry->dstPort, entry->packetLen, entry->action, entry->module); + break; + + case LOG_FORMAT_JSON: + snprintf(logMessage, sizeof(logMessage), + "{\"ts\":\"%s\",\"dir\":\"%s\",\"proto\":\"%s\",\"src\":\"%u.%u.%u.%u:%u\",\"dst\":\"%u.%u.%u.%u:%u\",\"size\":%u,\"act\":\"%s\",\"mod\":\"%s\"}", + timestamp, + entry->outbound ? "out" : "in", + getProtocolName(entry->protocol), + (entry->srcIP >> 24) & 0xFF, (entry->srcIP >> 16) & 0xFF, (entry->srcIP >> 8) & 0xFF, entry->srcIP & 0xFF, + entry->srcPort, + (entry->dstIP >> 24) & 0xFF, (entry->dstIP >> 16) & 0xFF, (entry->dstIP >> 8) & 0xFF, entry->dstIP & 0xFF, + entry->dstPort, entry->packetLen, entry->action, entry->module); + break; + + default: + snprintf(logMessage, sizeof(logMessage), "[%s] Packet logged", timestamp); + break; + } + + // Add to UI display + addLogToDisplay(logMessage); + + // Also log to file if enabled + if (logFile) { + switch (logFormat) { + case LOG_FORMAT_TEXT: + fprintf(logFile, "[%s] %s %s %u.%u.%u.%u:%u→%u.%u.%u.%u:%u size:%u %s [%s]\n", + timestamp, + entry->outbound ? "OUT" : "IN", + getProtocolName(entry->protocol), + (entry->srcIP >> 24) & 0xFF, (entry->srcIP >> 16) & 0xFF, (entry->srcIP >> 8) & 0xFF, entry->srcIP & 0xFF, + entry->srcPort, + (entry->dstIP >> 24) & 0xFF, (entry->dstIP >> 16) & 0xFF, (entry->dstIP >> 8) & 0xFF, entry->dstIP & 0xFF, + entry->dstPort, + entry->packetLen, entry->action, entry->module); + break; + + case LOG_FORMAT_CSV: + fprintf(logFile, "%s,%s,%s,%u.%u.%u.%u:%u,%u.%u.%u.%u:%u,%u,%s,%s\n", + timestamp, + entry->outbound ? "OUT" : "IN", + getProtocolName(entry->protocol), + (entry->srcIP >> 24) & 0xFF, (entry->srcIP >> 16) & 0xFF, (entry->srcIP >> 8) & 0xFF, entry->srcIP & 0xFF, + entry->srcPort, + (entry->dstIP >> 24) & 0xFF, (entry->dstIP >> 16) & 0xFF, (entry->dstIP >> 8) & 0xFF, entry->dstIP & 0xFF, + entry->dstPort, entry->packetLen, entry->action, entry->module); + break; + + case LOG_FORMAT_JSON: + fprintf(logFile, "{\"ts\":\"%s\",\"dir\":\"%s\",\"proto\":\"%s\",\"src\":\"%u.%u.%u.%u:%u\",\"dst\":\"%u.%u.%u.%u:%u\",\"size\":%u,\"act\":\"%s\",\"mod\":\"%s\"}\n", + timestamp, + entry->outbound ? "out" : "in", + getProtocolName(entry->protocol), + (entry->srcIP >> 24) & 0xFF, (entry->srcIP >> 16) & 0xFF, (entry->srcIP >> 8) & 0xFF, entry->srcIP & 0xFF, + entry->srcPort, + (entry->dstIP >> 24) & 0xFF, (entry->dstIP >> 16) & 0xFF, (entry->dstIP >> 8) & 0xFF, entry->dstIP & 0xFF, + entry->dstPort, entry->packetLen, entry->action, entry->module); + break; + } + + fflush(logFile); + totalLogSize = ftell(logFile); + + // Check if log file exceeds maximum size + if (totalLogSize > (maxLogSizeMB * 1024 * 1024)) { + if (autoExport) { + // Create archived log file with timestamp + char archiveName[512]; + snprintf(archiveName, sizeof(archiveName), "%s.%s.archive", logFilePath, timestamp); + fclose(logFile); + MoveFile(logFilePath, archiveName); + + // Create new log file + logFile = fopen(logFilePath, "w"); + if (logFile && logFormat == LOG_FORMAT_CSV) { + fprintf(logFile, "timestamp,id,direction,protocol,src_ip,src_port,dst_ip,dst_port,length,action,module,latency\n"); + } + } + } + } + + LeaveCriticalSection(&logMutex); +} + +// Log packet from processing pipeline with action information +void logPacketAction(PacketNode* pac, const char* action, const char* module) { + if (!loggingStarted || loggingShutdown) return; + + PacketLogEntry entry = {0}; + entry.timestamp = GetTickCount(); + entry.packetId = ++packetCounter; + entry.outbound = pac->addr.Outbound; + entry.packetLen = pac->packetLen; + strcpy(entry.action, action); + strcpy(entry.module, module); + + // Parse packet headers + PWINDIVERT_IPHDR ipHeader = NULL; + PWINDIVERT_TCPHDR tcpHeader = NULL; + PWINDIVERT_UDPHDR udpHeader = NULL; + + WinDivertHelperParsePacket(pac->packet, pac->packetLen, &ipHeader, NULL, NULL, NULL, NULL, + &tcpHeader, &udpHeader, NULL, NULL, NULL, NULL); + + if (ipHeader) { + entry.protocol = ipHeader->Protocol; + entry.srcIP = ntohl(ipHeader->SrcAddr); + entry.dstIP = ntohl(ipHeader->DstAddr); + + if (tcpHeader) { + entry.srcPort = ntohs(tcpHeader->SrcPort); + entry.dstPort = ntohs(tcpHeader->DstPort); + } else if (udpHeader) { + entry.srcPort = ntohs(udpHeader->SrcPort); + entry.dstPort = ntohs(udpHeader->DstPort); + } + } + + logPacketEntry(&entry); +} + +// Log packet from processing pipeline (deprecated function for backward compatibility) +static void logPacketFromNode(PacketNode* pac, const char* action, const char* module) { + logPacketAction(pac, action, module); +} + +// Browse button callback +static int browseButtonCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + Ihandle *filedlg = IupFileDlg(); + IupSetAttribute(filedlg, "DIALOGTYPE", "SAVE"); + IupSetAttribute(filedlg, "TITLE", "Select Log File"); + IupSetAttribute(filedlg, "FILTER", "*.log"); + IupSetAttribute(filedlg, "FILTERINFO", "Log files"); + + IupPopup(filedlg, IUP_CENTER, IUP_CENTER); + + if (IupGetInt(filedlg, "STATUS") != -1) { + char* filename = IupGetAttribute(filedlg, "VALUE"); + IupStoreAttribute(logFileInput, "VALUE", filename); + strcpy(logFilePath, filename); + } + + IupDestroy(filedlg); + return IUP_DEFAULT; +} + +// Export button callback +static int exportButtonCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + if (logFile) { + fflush(logFile); + LOG("Log file exported/flushed: %s (%.2f MB)", logFilePath, totalLogSize / (1024.0 * 1024.0)); + } + return IUP_DEFAULT; +} + +// Clear log button callback +static int clearLogCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + if (!loggingInitialized) return IUP_DEFAULT; + + EnterCriticalSection(&logMutex); + totalLogSize = 0; + packetCounter = 0; + LeaveCriticalSection(&logMutex); + + // File operations OUTSIDE critical section + if (logFile) { + fclose(logFile); + logFile = NULL; + } + logFile = fopen(logFilePath, "w"); + if (logFile && logFormat == LOG_FORMAT_CSV) { + fprintf(logFile, "timestamp,direction,protocol,src,dst,size,action,module\n"); + } + + // Clear UI display + if (logDisplay) { + IupSetAttribute(logDisplay, "VALUE", ""); + } + + LOG("Log cleared"); + return IUP_DEFAULT; +} + +// Log level change callback +static int logLevelCallback(Ihandle *ih, char *text, int item, int state) { + UNREFERENCED_PARAMETER(ih); + UNREFERENCED_PARAMETER(text); + if (state == 1) { + logLevel = item - 1; // Convert from 1-based to 0-based + LOG("Log level changed to: %d", logLevel); + } + return IUP_DEFAULT; +} + +// Log format change callback +static int logFormatCallback(Ihandle *ih, char *text, int item, int state) { + UNREFERENCED_PARAMETER(ih); + UNREFERENCED_PARAMETER(text); + if (state == 1) { + logFormat = item - 1; // Convert from 1-based to 0-based + LOG("Log format changed to: %d", logFormat); + + // Restart log file with new format + if (logFile) { + fclose(logFile); + logFile = fopen(logFilePath, "w"); + if (logFile && logFormat == LOG_FORMAT_CSV) { + fprintf(logFile, "timestamp,id,direction,protocol,src_ip,src_port,dst_ip,dst_port,length,action,module,latency\n"); + } + } + } + return IUP_DEFAULT; +} + +static Ihandle* loggingSetupUI() { + if (!loggingInitialized) { + InitializeCriticalSection(&logMutex); + loggingInitialized = 1; + } + + Ihandle *loggingControlsBox = IupVbox( + IupHbox( + enabledCheckbox = IupToggle("Enable Logging", NULL), + IupLabel("Level:"), + logLevelList = IupList(NULL), + IupLabel("Format:"), + logFormatList = IupList(NULL), + NULL + ), + IupHbox( + IupLabel("Log File:"), + logFileInput = IupText(NULL), + browseButton = IupButton("Browse", NULL), + NULL + ), + IupHbox( + IupLabel("Max Size(MB):"), + maxSizeInput = IupText(NULL), + realTimeCheckbox = IupToggle("Real-time", NULL), + autoExportCheckbox = IupToggle("Auto-export", NULL), + NULL + ), + IupHbox( + exportButton = IupButton("Export Now", NULL), + clearLogButton = IupButton("Clear Log", NULL), + NULL + ), + logDisplay = IupText(NULL), // Add log display area + NULL + ); + + // Setup log level list + IupSetAttribute(logLevelList, "DROPDOWN", "YES"); + IupSetAttribute(logLevelList, "VISIBLECOLUMNS", "8"); + IupSetAttribute(logLevelList, "1", "Error"); + IupSetAttribute(logLevelList, "2", "Warning"); + IupSetAttribute(logLevelList, "3", "Info"); + IupSetAttribute(logLevelList, "4", "Debug"); + IupSetAttribute(logLevelList, "5", "Trace"); + IupSetAttribute(logLevelList, "VALUE", "3"); // Default to Info + IupSetCallback(logLevelList, "ACTION", (Icallback)logLevelCallback); + + // Setup log format list + IupSetAttribute(logFormatList, "DROPDOWN", "YES"); + IupSetAttribute(logFormatList, "VISIBLECOLUMNS", "8"); + IupSetAttribute(logFormatList, "1", "Text"); + IupSetAttribute(logFormatList, "2", "CSV"); + IupSetAttribute(logFormatList, "3", "JSON"); + IupSetAttribute(logFormatList, "4", "PCAP"); + IupSetAttribute(logFormatList, "VALUE", "1"); // Default to Text + IupSetCallback(logFormatList, "ACTION", (Icallback)logFormatCallback); + + // Setup log file input + IupSetAttribute(logFileInput, "EXPAND", "HORIZONTAL"); + IupSetAttribute(logFileInput, "VALUE", logFilePath); + + // Setup browse button + IupSetCallback(browseButton, "ACTION", (Icallback)browseButtonCallback); + IupSetAttribute(browseButton, "PADDING", "4x"); + + // Setup max size input + IupSetAttribute(maxSizeInput, "VISIBLECOLUMNS", "6"); + IupSetAttribute(maxSizeInput, "VALUE", "100"); + IupSetCallback(maxSizeInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(maxSizeInput, SYNCED_VALUE, (char*)&maxLogSizeMB); + IupSetAttribute(maxSizeInput, INTEGER_MIN, "1"); + IupSetAttribute(maxSizeInput, INTEGER_MAX, "10000"); + + // Setup checkboxes + IupSetCallback(enabledCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(enabledCheckbox, SYNCED_VALUE, (char*)&loggingEnabled); + IupSetAttribute(enabledCheckbox, "VALUE", "ON"); + + IupSetCallback(realTimeCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(realTimeCheckbox, SYNCED_VALUE, (char*)&realTimeLogging); + + IupSetCallback(autoExportCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(autoExportCheckbox, SYNCED_VALUE, (char*)&autoExport); + + // Setup log display area + IupSetAttribute(logDisplay, "MULTILINE", "YES"); + IupSetAttribute(logDisplay, "READONLY", "YES"); + IupSetAttribute(logDisplay, "EXPAND", "YES"); + IupSetAttribute(logDisplay, "SIZE", "400x200"); + IupSetAttribute(logDisplay, "FONT", "Courier, 9"); + IupSetAttribute(logDisplay, "VALUE", "Logs will appear here..."); + + // Setup buttons + IupSetCallback(exportButton, "ACTION", (Icallback)exportButtonCallback); + IupSetAttribute(exportButton, "PADDING", "4x"); + + IupSetCallback(clearLogButton, "ACTION", (Icallback)clearLogCallback); + IupSetAttribute(clearLogButton, "PADDING", "4x"); + + if (parameterized) { + setFromParameter(enabledCheckbox, "VALUE", NAME"-enabled"); + setFromParameter(logLevelList, "VALUE", NAME"-level"); + setFromParameter(logFormatList, "VALUE", NAME"-format"); + setFromParameter(logFileInput, "VALUE", NAME"-file"); + setFromParameter(maxSizeInput, "VALUE", NAME"-maxsize"); + setFromParameter(realTimeCheckbox, "VALUE", NAME"-realtime"); + setFromParameter(autoExportCheckbox, "VALUE", NAME"-autoexport"); + } + + return loggingControlsBox; +} + +static void loggingStartup() { + loggingShutdown = 0; + loggingStarted = 1; + + logFile = fopen(logFilePath, "a"); + if (logFile) { + if (logFormat == LOG_FORMAT_CSV && ftell(logFile) == 0) { + fprintf(logFile, "timestamp,direction,protocol,src,dst,size,action,module\n"); + } + totalLogSize = ftell(logFile); + } +} + +static void loggingCloseDown(PacketNode *head, PacketNode *tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + + loggingStarted = 0; + loggingShutdown = 1; + Sleep(10); + + if (logFile) { + fclose(logFile); + logFile = NULL; + } +} + +static short loggingProcess(PacketNode *head, PacketNode* tail) { + if (!loggingStarted || loggingShutdown || !realTimeLogging) return FALSE; + + PacketNode *pac = head->next; + while (pac != tail) { + logPacketAction(pac, "PASS", "main"); + pac = pac->next; + } + + return FALSE; +} + +Module loggingModule = { + "Enhanced Logging", + NAME, + (short*)&loggingEnabled, + loggingSetupUI, + loggingStartup, + loggingCloseDown, + loggingProcess, + // runtime fields + 0, 0, NULL +}; + +// Function to add log message to UI display +static void addLogToDisplay(const char* message) { + if (!logDisplay || !loggingStarted || loggingShutdown) return; + + EnterCriticalSection(&logMutex); + + char* currentText = IupGetAttribute(logDisplay, "VALUE"); + char newText[4096]; + + // Limit the size of the log display to prevent memory issues + if (currentText && strlen(currentText) > 3000) { + // Keep only the last portion of the log + int len = strlen(currentText); + const char* truncated = currentText + len - 3000; + snprintf(newText, sizeof(newText), "%s\n%s", truncated, message); + } else if (currentText && strlen(currentText) > 0) { + snprintf(newText, sizeof(newText), "%s\n%s", currentText, message); + } else { + snprintf(newText, sizeof(newText), "%s", message); + } + + IupSetAttribute(logDisplay, "VALUE", newText); + IupSetAttribute(logDisplay, "CARET", "1000000"); // Scroll to bottom + + LeaveCriticalSection(&logMutex); +} + +// Enhanced log function that also displays in UI +static void logMessageToUI(const char* format, ...) { + char buffer[1024]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + + // Add to UI display + addLogToDisplay(buffer); + + // Also log to file if enabled + if (logFile) { + fprintf(logFile, "%s\n", buffer); + fflush(logFile); + totalLogSize = ftell(logFile); + } +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index c6bb41c..6f49fa0 100644 --- a/src/main.c +++ b/src/main.c @@ -2,11 +2,12 @@ #include #include #include +#include #include #include "iup.h" #include "common.h" -// ! the order decides which module get processed first +// Module processing order - first in array is processed first Module* modules[MODULE_CNT] = { &lagModule, &dropModule, @@ -16,8 +17,33 @@ Module* modules[MODULE_CNT] = { &tamperModule, &resetModule, &bandwidthModule, + &lengthModule, + &tlsModule }; +// Excluded from main UI but processed separately +Module* hiddenModules[] = { + &statsModule, + &loggingModule, + &automationModule, + &protocolModule, + &profilesModule, +}; +#define HIDDEN_MODULE_CNT 5 + +// Helper function to process all modules (visible + hidden) +void processAllModules(void (*func)(Module*, int)) { + int ix; + // Process visible modules + for (ix = 0; ix < MODULE_CNT; ++ix) { + func(modules[ix], ix); + } + // Process hidden modules + for (ix = 0; ix < HIDDEN_MODULE_CNT; ++ix) { + func(hiddenModules[ix], MODULE_CNT + ix); + } +} + volatile short sendState = SEND_STATUS_NONE; // global iup handlers @@ -29,9 +55,16 @@ Ihandle *filterSelectList; static Ihandle *stateIcon; static Ihandle *timer; static Ihandle *timeout = NULL; +// side panels +static Ihandle *logDialog = NULL, *statsDialog = NULL, *automationDialog = NULL; +static volatile short logPanelVisible = 0, statsPanelVisible = 0, automationPanelVisible = 0; +// settings tabs +static Ihandle *settingsTabs = NULL; +static Ihandle *loggingToggle = NULL, *statsToggle = NULL, *automationToggle = NULL; void showStatus(const char *line); static int uiOnDialogShow(Ihandle *ih, int state); +static int uiOnDialogClose(Ihandle *ih); static int uiStopCb(Ihandle *ih); static int uiStartCb(Ihandle *ih); static int uiTimerCb(Ihandle *ih); @@ -39,6 +72,12 @@ static int uiTimeoutCb(Ihandle *ih); static int uiListSelectCb(Ihandle *ih, char *text, int item, int state); static int uiFilterTextCb(Ihandle *ih); static void uiSetupModule(Module *module, Ihandle *parent); +static int uiToggleLogPanel(Ihandle *ih, int state); +static int uiToggleStatsPanel(Ihandle *ih, int state); +static int uiToggleAutomationPanel(Ihandle *ih, int state); +static void uiCreateLogPanel(void); +static void uiCreateStatsPanel(void); +static void uiCreateAutomationPanel(void); // serializing config files using a stupid custom format #define CONFIG_FILE "config.txt" @@ -139,6 +178,15 @@ void init(int argc, char* argv[]) { topFrame = IupFrame( topVbox = IupVbox( + // Settings tabs + settingsTabs = IupHbox( + IupLabel("Panels:"), + loggingToggle = IupToggle("Logs", NULL), + statsToggle = IupToggle("Stats", NULL), + automationToggle = IupToggle("Auto", NULL), + IupFill(), + NULL + ), filterText = IupText(NULL), controlHbox = IupHbox( stateIcon = IupLabel(NULL), @@ -165,14 +213,23 @@ void init(int argc, char* argv[]) { } IupSetAttribute(topFrame, "TITLE", "Filtering"); - IupSetAttribute(topFrame, "EXPAND", "HORIZONTAL"); + IupSetAttribute(topVbox, "ALIGNMENT", "ALEFT"); IupSetAttribute(filterText, "EXPAND", "HORIZONTAL"); IupSetCallback(filterText, "VALUECHANGED_CB", (Icallback)uiFilterTextCb); IupSetAttribute(filterButton, "PADDING", "8x"); IupSetCallback(filterButton, "ACTION", uiStartCb); + + // Setup panel toggles + IupSetCallback(loggingToggle, "ACTION", (Icallback)uiToggleLogPanel); + IupSetCallback(statsToggle, "ACTION", (Icallback)uiToggleStatsPanel); + IupSetCallback(automationToggle, "ACTION", (Icallback)uiToggleAutomationPanel); + IupSetAttribute(loggingToggle, "VALUE", "OFF"); + IupSetAttribute(statsToggle, "VALUE", "OFF"); + IupSetAttribute(automationToggle, "VALUE", "OFF"); + IupSetAttribute(topVbox, "NCMARGIN", "4x4"); IupSetAttribute(topVbox, "NCGAP", "4x2"); - IupSetAttribute(controlHbox, "ALIGNMENT", "ACENTER"); + IupSetAttribute(controlHbox, "ALIGNMENT", "ARIGHT"); // setup state icon IupSetAttribute(stateIcon, "IMAGE", "none_icon"); @@ -183,7 +240,7 @@ void init(int argc, char* argv[]) { IupSetAttribute(filterSelectList, "DROPDOWN", "YES"); for (ix = 0; ix < filtersSize; ++ix) { char ixBuf[4]; - sprintf(ixBuf, "%d", ix+1); // ! staring from 1, following lua indexing + sprintf(ixBuf, "%d", ix+1); IupStoreAttribute(filterSelectList, ixBuf, filters[ix].filterName); } IupSetAttribute(filterSelectList, "VALUE", "1"); @@ -191,15 +248,21 @@ void init(int argc, char* argv[]) { // set filter text value since the callback won't take effect before main loop starts IupSetAttribute(filterText, "VALUE", filters[0].filterValue); - // functionalities frame - bottomFrame = IupFrame( + // functionalities frame with scrolling for horizontal overflow + Ihandle *scrollBox = IupScrollBox( bottomVbox = IupVbox( NULL ) ); + bottomFrame = IupFrame(scrollBox); IupSetAttribute(bottomFrame, "TITLE", "Functions"); - IupSetAttribute(bottomVbox, "NCMARGIN", "4x4"); + IupSetAttribute(bottomFrame, "ALIGNMENT", "ALEFT"); + IupSetAttribute(scrollBox, "SCROLLBAR", "HORIZONTAL"); // Show horizontal scrollbar only + IupSetAttribute(scrollBox, "EXPAND", "YES"); // Expand to fill available space + IupSetAttribute(bottomVbox, "NCMARGIN", "4x20"); // Add bottom margin to avoid scrollbar overlap IupSetAttribute(bottomVbox, "NCGAP", "4x2"); + IupSetAttribute(bottomVbox, "ALIGNMENT", "ALEFT"); // Align content to the left + // create icons noneIcon = IupImage(8, 8, icon8x8); @@ -231,13 +294,16 @@ void init(int argc, char* argv[]) { ); IupSetAttribute(dialog, "TITLE", "clumsy " CLUMSY_VERSION); - IupSetAttribute(dialog, "SIZE", "480x"); // add padding manually to width + IupSetAttribute(dialog, "SIZE", "480x"); // Minimum width, height will adjust IupSetAttribute(dialog, "RESIZE", "NO"); + IupSetAttribute(dialog, "MINBOX", "YES"); + IupSetAttribute(dialog, "MAXBOX", "YES"); IupSetCallback(dialog, "SHOW_CB", (Icallback)uiOnDialogShow); + IupSetCallback(dialog, "CLOSE_CB", (Icallback)uiOnDialogClose); // global layout settings to affect childrens - IupSetAttribute(dialogVBox, "ALIGNMENT", "ACENTER"); + IupSetAttribute(dialogVBox, "ALIGNMENT", "ALEFT"); IupSetAttribute(dialogVBox, "NCMARGIN", "4x4"); IupSetAttribute(dialogVBox, "NCGAP", "4x2"); @@ -267,7 +333,6 @@ void startup() { // kickoff event loops IupShowXY(dialog, IUP_CENTER, IUP_CENTER); IupMainLoop(); - // ! main loop won't return until program exit } void cleanup() { @@ -357,6 +422,26 @@ static int uiOnDialogShow(Ihandle *ih, int state) { return exit ? IUP_CLOSE : IUP_DEFAULT; } +static int uiOnDialogClose(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + // Close all additional panels before closing main window + if (logDialog) { + IupDestroy(logDialog); + logDialog = NULL; + } + if (statsDialog) { + IupDestroy(statsDialog); + statsDialog = NULL; + } + if (automationDialog) { + IupDestroy(automationDialog); + automationDialog = NULL; + } + + return IUP_DEFAULT; +} + static int uiStartCb(Ihandle *ih) { char buf[MSG_BUFSIZE]; UNREFERENCED_PARAMETER(ih); @@ -395,6 +480,10 @@ static int uiStopCb(Ihandle *ih) { modules[ix]->processTriggered = 0; // use = here since is threads already stopped IupSetAttribute(modules[ix]->iconHandle, "IMAGE", "none_icon"); } + // clean up hidden modules + for (ix = 0; ix < HIDDEN_MODULE_CNT; ++ix) { + hiddenModules[ix]->processTriggered = 0; + } sendState = SEND_STATUS_NONE; IupSetAttribute(stateIcon, "IMAGE", "none_icon"); @@ -406,6 +495,7 @@ static int uiToggleControls(Ihandle *ih, int state) { Ihandle *controls = (Ihandle*)IupGetAttribute(ih, CONTROLS_HANDLE); short *target = (short*)IupGetAttribute(ih, SYNCED_VALUE); int controlsActive = IupGetInt(controls, "ACTIVE"); + if (controlsActive && !state) { IupSetAttribute(controls, "ACTIVE", "NO"); InterlockedExchange16(target, I2S(state)); @@ -414,6 +504,9 @@ static int uiToggleControls(Ihandle *ih, int state) { InterlockedExchange16(target, I2S(state)); } + // Refresh the dialog layout to ensure proper sizing + IupRefresh(dialog); + return IUP_DEFAULT; } @@ -470,25 +563,29 @@ static int uiFilterTextCb(Ihandle *ih) { } static void uiSetupModule(Module *module, Ihandle *parent) { - Ihandle *groupBox, *toggle, *controls, *icon; + Ihandle *groupBox, *toggle, *controls, *icon, *nameLabel; + groupBox = IupHbox( icon = IupLabel(NULL), - toggle = IupToggle(module->displayName, NULL), - IupFill(), + toggle = IupToggle("", NULL), + nameLabel = IupLabel(module->displayName), controls = module->setupUIFunc(), NULL ); - IupSetAttribute(groupBox, "EXPAND", "HORIZONTAL"); - IupSetAttribute(groupBox, "ALIGNMENT", "ACENTER"); - IupSetAttribute(controls, "ALIGNMENT", "ACENTER"); + IupSetAttribute(groupBox, "ALIGNMENT", "ALEFT"); // Align elements to the LEFT + IupSetAttribute(controls, "ALIGNMENT", "ACENTER"); // Center controls vertically + + // Set fixed width for the name label and icon + IupSetAttribute(icon, "SIZE", "8x"); + IupSetAttribute(nameLabel, "SIZE", "60x"); IupAppend(parent, groupBox); - + // set controls as attribute to toggle and enable toggle callback IupSetCallback(toggle, "ACTION", (Icallback)uiToggleControls); IupSetAttribute(toggle, CONTROLS_HANDLE, (char*)controls); IupSetAttribute(toggle, SYNCED_VALUE, (char*)module->enabledFlag); IupSetAttribute(controls, "ACTIVE", "NO"); // startup as inactive - IupSetAttribute(controls, "NCGAP", "4"); // startup as inactive + IupSetAttribute(controls, "NCGAP", "4"); // set default icon IupSetAttribute(icon, "IMAGE", "none_icon"); @@ -501,6 +598,157 @@ static void uiSetupModule(Module *module, Ihandle *parent) { } } +// Create side panels for hidden modules +static void uiCreateLogPanel(void) { + if (logDialog) return; // Already created + + // Create dialog with the actual logging module UI + Ihandle *loggingUI = loggingModule.setupUIFunc(); + + logDialog = IupDialog( + IupVbox( + IupLabel("Enhanced Logging Module"), + loggingUI, + NULL + ) + ); + + IupSetAttribute(logDialog, "TITLE", "clumsy - Logging"); + IupSetAttribute(logDialog, "RESIZE", "YES"); + IupSetAttribute(logDialog, "MINSIZE", "400x300"); + IupSetAttribute(logDialog, "TOPMOST", "YES"); +} + +static void uiCreateStatsPanel(void) { + if (statsDialog) return; // Already created + + // Create dialog with the actual stats module UI + Ihandle *statsUI = statsModule.setupUIFunc(); + + statsDialog = IupDialog( + IupVbox( + IupLabel("Network Statistics Module"), + statsUI, + NULL + ) + ); + + IupSetAttribute(statsDialog, "TITLE", "clumsy - Statistics"); + IupSetAttribute(statsDialog, "RESIZE", "YES"); + IupSetAttribute(statsDialog, "MINSIZE", "400x250"); + IupSetAttribute(statsDialog, "TOPMOST", "YES"); +} + +static void uiCreateAutomationPanel(void) { + if (automationDialog) return; // Already created + + // Create dialog with the actual automation module UI + Ihandle *automationUI = automationModule.setupUIFunc(); + + automationDialog = IupDialog( + IupVbox( + IupLabel("Automation & Scripting Module"), + automationUI, + NULL + ) + ); + + IupSetAttribute(automationDialog, "TITLE", "clumsy - Automation"); + IupSetAttribute(automationDialog, "RESIZE", "YES"); + IupSetAttribute(automationDialog, "MINSIZE", "500x400"); + IupSetAttribute(automationDialog, "TOPMOST", "YES"); +} + +static int uiToggleLogPanel(Ihandle *ih, int state) { + UNREFERENCED_PARAMETER(ih); + + if (state) { + if (!logDialog) uiCreateLogPanel(); + + // Enable logging module + *(loggingModule.enabledFlag) = 1; + + // Position panel to the right of main window + int x, y; + IupGetIntInt(dialog, "SCREENPOSITION", &x, &y); + int width = IupGetInt(dialog, "RASTERSIZE"); + IupShowXY(logDialog, x + width + 10, y); + logPanelVisible = 1; + } else { + // Disable logging module + *(loggingModule.enabledFlag) = 0; + + if (logDialog) { + IupHide(logDialog); + logPanelVisible = 0; + } + } + + return IUP_DEFAULT; +} + +static int uiToggleStatsPanel(Ihandle *ih, int state) { + UNREFERENCED_PARAMETER(ih); + + if (state) { + if (!statsDialog) uiCreateStatsPanel(); + + // Enable stats module + *(statsModule.enabledFlag) = 1; + + // Position panel to the right of main window + int x, y; + IupGetIntInt(dialog, "SCREENPOSITION", &x, &y); + int width = IupGetInt(dialog, "RASTERSIZE"); + int offsetY = logPanelVisible ? 320 : 0; // Stack below log panel if visible + IupShowXY(statsDialog, x + width + 10, y + offsetY); + statsPanelVisible = 1; + } else { + // Disable stats module + *(statsModule.enabledFlag) = 0; + + if (statsDialog) { + IupHide(statsDialog); + statsPanelVisible = 0; + } + } + + return IUP_DEFAULT; +} + +static int uiToggleAutomationPanel(Ihandle *ih, int state) { + UNREFERENCED_PARAMETER(ih); + + if (state) { + if (!automationDialog) uiCreateAutomationPanel(); + + // Enable automation module + *(automationModule.enabledFlag) = 1; + + // Position panel to the right of main window + int x, y; + IupGetIntInt(dialog, "SCREENPOSITION", &x, &y); + int width = IupGetInt(dialog, "RASTERSIZE"); + int offsetY = 0; + if (logPanelVisible) offsetY += 320; + if (statsPanelVisible) offsetY += 270; + IupShowXY(automationDialog, x + width + 10, y + offsetY); + automationPanelVisible = 1; + } else { + // Disable automation module + *(automationModule.enabledFlag) = 0; + + if (automationDialog) { + IupHide(automationDialog); + automationPanelVisible = 0; + } + } + + return IUP_DEFAULT; +} + + + int main(int argc, char* argv[]) { LOG("Is Run As Admin: %d", IsRunAsAdmin()); LOG("Is Elevated: %d", IsElevated()); diff --git a/src/mingw_compat.c b/src/mingw_compat.c new file mode 100644 index 0000000..c0658d8 --- /dev/null +++ b/src/mingw_compat.c @@ -0,0 +1,34 @@ +// Файл совместимости для разных версий MinGW +#include +#include +#include + +// Заглушка для __ms_vsnprintf (нужна для IUP) +int __ms_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) { + return vsnprintf(buffer, count, format, argptr); +} + +// Условные определения для старых версий MinGW (только для 32-бит) +#ifndef _WIN64 +// Для 32-битной версии добавляем недостающие функции +void __stdcall DisableProcessWindowsGhosting(void) { + // Заглушка +} + +DWORD __stdcall GetLayout(HDC hdc) { + UNREFERENCED_PARAMETER(hdc); + return 0; +} + +BOOL __stdcall IsWow64Process(HANDLE hProcess, PBOOL Wow64Process) { + UNREFERENCED_PARAMETER(hProcess); + if (Wow64Process) { + *Wow64Process = FALSE; + } + return TRUE; +} + +// Альтернативные символы для линкера (только 32-бит) +void* _imp__DisableProcessWindowsGhosting = &DisableProcessWindowsGhosting; +void* _imp__GetLayout = &GetLayout; +#endif \ No newline at end of file diff --git a/src/ood.c b/src/ood.c index 253d72a..909c5db 100644 --- a/src/ood.c +++ b/src/ood.c @@ -16,14 +16,15 @@ static int giveUpCnt; static Ihandle *oodSetupUI() { Ihandle *oodControlsBox = IupHbox( - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Chance(%):"), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Chance:"), chanceInput = IupText(NULL), + IupLabel("%"), NULL ); - IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "3"); IupSetAttribute(chanceInput, "VALUE", "10.0"); IupSetCallback(chanceInput, "VALUECHANGED_CB", uiSyncChance); IupSetAttribute(chanceInput, SYNCED_VALUE, (char*)&chance); @@ -57,6 +58,8 @@ static void oodCloseDown(PacketNode *head, PacketNode *tail) { LOG("ood disabled"); if (oodPacket != NULL) { insertAfter(oodPacket, head); + // Log the packet action when it's sent + logPacketAction(oodPacket, "OOD", "ood"); oodPacket = NULL; // ! need to empty the ood packet } } @@ -105,6 +108,8 @@ static short oodProcess(PacketNode *head, PacketNode *tail) { if (!isListEmpty() || --giveUpCnt == 0) { LOG("Ooo sent direction %s, is giveup %s", oodPacket->addr.Outbound ? "OUTBOUND" : "INBOUND", giveUpCnt ? "NO" : "YES"); insertAfter(oodPacket, head); + // Log the packet action when it's sent + logPacketAction(oodPacket, "OOD", "ood"); oodPacket = NULL; giveUpCnt = KEEP_TURNS_MAX; } // skip picking packets when having oodPacket already @@ -127,6 +132,9 @@ static short oodProcess(PacketNode *head, PacketNode *tail) { if (first && second && calcChance(chance)) { swapNode(first, second); LOG("Multiple packets OOD swapping"); + // Log the packet actions for swapped packets + logPacketAction(first, "OOD", "ood"); + logPacketAction(second, "OOD", "ood"); } else { // move forward first to progress first = second; diff --git a/src/profiles.c b/src/profiles.c new file mode 100644 index 0000000..1d14e16 --- /dev/null +++ b/src/profiles.c @@ -0,0 +1,231 @@ +// network condition profiles module +#include +#include +#include "iup.h" +#include "common.h" +#define NAME "profiles" + +// Network profile definitions +typedef struct { + const char* name; + int lag_min; // ms + int lag_max; // ms + int loss_rate; // 0-10000 (0-100%) + int bandwidth; // kbps + int jitter; // ms + int duplicate_rate; // 0-10000 (0-100%) +} NetworkProfile; + +// Predefined network profiles +static NetworkProfile profiles[] = { + {"2G (GPRS)", 400, 800, 500, 50, 100, 100}, // 2G: 400-800ms lag, 5% loss, 50kbps + {"2.5G (EDGE)", 200, 500, 300, 200, 80, 50}, // 2.5G: 200-500ms lag, 3% loss, 200kbps + {"3G", 100, 300, 200, 2000, 50, 30}, // 3G: 100-300ms lag, 2% loss, 2Mbps + {"3.5G (HSPA)", 50, 150, 100, 8000, 30, 20}, // 3.5G: 50-150ms lag, 1% loss, 8Mbps + {"4G (LTE)", 20, 80, 50,50000, 20, 10}, // 4G: 20-80ms lag, 0.5% loss, 50Mbps + {"5G", 10, 40, 20,100000, 10, 5}, // 5G: 10-40ms lag, 0.2% loss, 100Mbps + {"WiFi (Good)", 10, 50, 30,25000, 15, 10}, // WiFi Good: 10-50ms lag, 0.3% loss, 25Mbps + {"WiFi (Poor)", 50, 200, 800, 2000, 80, 100}, // WiFi Poor: 50-200ms lag, 8% loss, 2Mbps + {"Satellite", 500, 800, 200,20000, 100, 50}, // Satellite: 500-800ms lag, 2% loss, 20Mbps + {"Dial-up", 1000, 2000, 1000, 56, 200, 200}, // Dial-up: 1-2s lag, 10% loss, 56kbps + {"Custom", 0, 0, 0, 0, 0, 0} // Custom profile +}; + +#define PROFILE_COUNT (sizeof(profiles) / sizeof(profiles[0])) + +static Ihandle *profileList, *applyButton, *detailsLabel; +static Ihandle *customLagInput, *customLossInput, *customBandwidthInput; + +static volatile short profilesEnabled = 0; +static volatile short selectedProfile = 0; +static volatile short customLag = 100, customLoss = 100, customBandwidth = 1000; + +// Apply profile settings to other modules via UI +static void applyProfile(int profileIndex) { + if (profileIndex < 0 || profileIndex >= PROFILE_COUNT) return; + + NetworkProfile* profile = &profiles[profileIndex]; + + LOG("Applying network profile: %s", profile->name); + + // Instead of direct variable access, we'll need to update UI elements + // and let the sync mechanisms handle the updates + char valueStr[32]; + + if (profileIndex == PROFILE_COUNT - 1) { // Custom profile + sprintf(valueStr, "%d", customLag); + LOG("Custom profile: lag=%dms, loss=%.1f%%, bandwidth=%dkbps", + customLag, customLoss/100.0, customBandwidth); + } else { + sprintf(valueStr, "%d", (profile->lag_min + profile->lag_max) / 2); + LOG("Profile applied: %s - lag=%dms, loss=%.1f%%, bandwidth=%dkbps", + profile->name, (profile->lag_min + profile->lag_max) / 2, + profile->loss_rate/100.0, profile->bandwidth); + } + + // Note: In the real implementation, we would need access to other module's UI elements + // to properly set their values. This would require architectural changes. + // For now, this is a placeholder that demonstrates the concept. +} + +// Update details display +static void updateProfileDetails(int profileIndex) { + char details[512]; + + if (profileIndex < 0 || profileIndex >= PROFILE_COUNT) return; + + NetworkProfile* profile = &profiles[profileIndex]; + + if (profileIndex == PROFILE_COUNT - 1) { // Custom profile + snprintf(details, sizeof(details), + "Custom Profile:\nLag: %dms | Loss: %.1f%% | Bandwidth: %dkbps", + customLag, customLoss/100.0, customBandwidth); + } else { + snprintf(details, sizeof(details), + "%s:\nLag: %d-%dms | Loss: %.1f%% | Bandwidth: %dkbps | Jitter: %dms", + profile->name, profile->lag_min, profile->lag_max, + profile->loss_rate/100.0, profile->bandwidth, profile->jitter); + } + + IupStoreAttribute(detailsLabel, "TITLE", details); +} + +// Profile selection callback +static int profileSelectCallback(Ihandle *ih, char *text, int item, int state) { + UNREFERENCED_PARAMETER(ih); + UNREFERENCED_PARAMETER(text); + + if (state == 1) { + selectedProfile = item - 1; // Convert from 1-based to 0-based + updateProfileDetails(selectedProfile); + + // Show/hide custom controls + if (selectedProfile == PROFILE_COUNT - 1) { + IupSetAttribute(customLagInput, "VISIBLE", "YES"); + IupSetAttribute(customLossInput, "VISIBLE", "YES"); + IupSetAttribute(customBandwidthInput, "VISIBLE", "YES"); + } else { + IupSetAttribute(customLagInput, "VISIBLE", "NO"); + IupSetAttribute(customLossInput, "VISIBLE", "NO"); + IupSetAttribute(customBandwidthInput, "VISIBLE", "NO"); + } + } + return IUP_DEFAULT; +} + +// Apply button callback +static int applyProfileCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + applyProfile(selectedProfile); + return IUP_DEFAULT; +} + +static Ihandle* profilesSetupUI() { + Ihandle *profilesControlsBox = IupVbox( + IupHbox( + IupLabel("Network Profile:"), + profileList = IupList(NULL), + applyButton = IupButton("Apply", NULL), + NULL + ), + detailsLabel = IupLabel("Select a profile to see details..."), + IupHbox( + IupLabel("Custom - Lag(ms):"), + customLagInput = IupText(NULL), + IupLabel("Loss(%):"), + customLossInput = IupText(NULL), + IupLabel("BW(kbps):"), + customBandwidthInput = IupText(NULL), + NULL + ), + NULL + ); + + // Setup profile list + IupSetAttribute(profileList, "DROPDOWN", "YES"); + IupSetAttribute(profileList, "VISIBLECOLUMNS", "15"); + + for (int i = 0; i < PROFILE_COUNT; i++) { + char indexStr[8]; + sprintf(indexStr, "%d", i + 1); + IupStoreAttribute(profileList, indexStr, profiles[i].name); + } + + IupSetAttribute(profileList, "VALUE", "1"); + IupSetCallback(profileList, "ACTION", (Icallback)profileSelectCallback); + + // Setup apply button + IupSetCallback(applyButton, "ACTION", (Icallback)applyProfileCallback); + IupSetAttribute(applyButton, "PADDING", "4x"); + + // Setup custom inputs + IupSetAttribute(customLagInput, "VISIBLECOLUMNS", "6"); + IupSetAttribute(customLagInput, "VALUE", "100"); + IupSetAttribute(customLagInput, "VISIBLE", "NO"); + IupSetCallback(customLagInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(customLagInput, SYNCED_VALUE, (char*)&customLag); + IupSetAttribute(customLagInput, INTEGER_MIN, "0"); + IupSetAttribute(customLagInput, INTEGER_MAX, "5000"); + + IupSetAttribute(customLossInput, "VISIBLECOLUMNS", "6"); + IupSetAttribute(customLossInput, "VALUE", "1.0"); + IupSetAttribute(customLossInput, "VISIBLE", "NO"); + IupSetCallback(customLossInput, "VALUECHANGED_CB", uiSyncChance); + IupSetAttribute(customLossInput, SYNCED_VALUE, (char*)&customLoss); + + IupSetAttribute(customBandwidthInput, "VISIBLECOLUMNS", "8"); + IupSetAttribute(customBandwidthInput, "VALUE", "1000"); + IupSetAttribute(customBandwidthInput, "VISIBLE", "NO"); + IupSetCallback(customBandwidthInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(customBandwidthInput, SYNCED_VALUE, (char*)&customBandwidth); + IupSetAttribute(customBandwidthInput, INTEGER_MIN, "1"); + IupSetAttribute(customBandwidthInput, INTEGER_MAX, "1000000"); + + // Setup details label + IupSetAttribute(detailsLabel, "EXPAND", "HORIZONTAL"); + IupSetAttribute(detailsLabel, "ALIGNMENT", "ALEFT"); + IupSetAttribute(detailsLabel, "PADDING", "4x4"); + + // Initialize with first profile + updateProfileDetails(0); + + if (parameterized) { + setFromParameter(profileList, "VALUE", NAME"-profile"); + setFromParameter(customLagInput, "VALUE", NAME"-lag"); + setFromParameter(customLossInput, "VALUE", NAME"-loss"); + setFromParameter(customBandwidthInput, "VALUE", NAME"-bandwidth"); + } + + return profilesControlsBox; +} + +static void profilesStartup() { + LOG("network profiles enabled"); +} + +static void profilesCloseDown(PacketNode *head, PacketNode *tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + LOG("network profiles disabled"); +} + +static short profilesProcess(PacketNode *head, PacketNode* tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + // Profiles module doesn't directly process packets + // It configures other modules instead + return FALSE; +} + +Module profilesModule = { + "Network Profiles", + NAME, + (short*)&profilesEnabled, + profilesSetupUI, + profilesStartup, + profilesCloseDown, + profilesProcess, + // runtime fields + 0, 0, NULL +}; \ No newline at end of file diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 0000000..be0f72b --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,264 @@ +// protocol-specific filtering module +#include +#include +#include "iup.h" +#include "common.h" +#define NAME "protocol" + +// Common protocol definitions +#define PROTO_HTTP_PORT 80 +#define PROTO_HTTPS_PORT 443 +#define PROTO_DNS_PORT 53 +#define PROTO_SSH_PORT 22 +#define PROTO_FTP_PORT 21 +#define PROTO_TELNET_PORT 23 +#define PROTO_SMTP_PORT 25 +#define PROTO_POP3_PORT 110 +#define PROTO_IMAP_PORT 143 +#define PROTO_SNMP_PORT 161 + +typedef enum { + PROTO_FILTER_NONE = 0, + PROTO_FILTER_HTTP = 1, + PROTO_FILTER_HTTPS = 2, + PROTO_FILTER_DNS = 4, + PROTO_FILTER_SSH = 8, + PROTO_FILTER_FTP = 16, + PROTO_FILTER_EMAIL = 32, + PROTO_FILTER_CUSTOM = 64 +} ProtocolFilter; + +static Ihandle *inboundCheckbox, *outboundCheckbox; +static Ihandle *httpCheckbox, *httpsCheckbox, *dnsCheckbox, *sshCheckbox; +static Ihandle *ftpCheckbox, *emailCheckbox; +static Ihandle *customPortInput, *chanceInput; + +static volatile short protocolEnabled = 0, + protocolInbound = 1, protocolOutbound = 1, + filterHTTP = 0, filterHTTPS = 0, filterDNS = 0, + filterSSH = 0, filterFTP = 0, filterEmail = 0, + customPort = 8080, + chance = 1000; // [0-10000] - 10% default + + +// Helper function to check if packet matches selected protocols +static BOOL matchesProtocolFilter(PacketNode* pac) { + PWINDIVERT_TCPHDR tcpHeader = NULL; + PWINDIVERT_UDPHDR udpHeader = NULL; + UINT16 srcPort, dstPort; + + // Parse packet headers + WinDivertHelperParsePacket(pac->packet, pac->packetLen, NULL, NULL, NULL, NULL, NULL, + &tcpHeader, &udpHeader, NULL, NULL, NULL, NULL); + + if (tcpHeader) { + srcPort = ntohs(tcpHeader->SrcPort); + dstPort = ntohs(tcpHeader->DstPort); + } else if (udpHeader) { + srcPort = ntohs(udpHeader->SrcPort); + dstPort = ntohs(udpHeader->DstPort); + } else { + return FALSE; // Not TCP or UDP + } + + // Check HTTP + if (filterHTTP && (srcPort == PROTO_HTTP_PORT || dstPort == PROTO_HTTP_PORT)) { + return TRUE; + } + + // Check HTTPS + if (filterHTTPS && (srcPort == PROTO_HTTPS_PORT || dstPort == PROTO_HTTPS_PORT)) { + return TRUE; + } + + // Check DNS + if (filterDNS && (srcPort == PROTO_DNS_PORT || dstPort == PROTO_DNS_PORT)) { + return TRUE; + } + + // Check SSH + if (filterSSH && (srcPort == PROTO_SSH_PORT || dstPort == PROTO_SSH_PORT)) { + return TRUE; + } + + // Check FTP + if (filterFTP && (srcPort == PROTO_FTP_PORT || dstPort == PROTO_FTP_PORT)) { + return TRUE; + } + + // Check Email protocols + if (filterEmail && + (srcPort == PROTO_SMTP_PORT || dstPort == PROTO_SMTP_PORT || + srcPort == PROTO_POP3_PORT || dstPort == PROTO_POP3_PORT || + srcPort == PROTO_IMAP_PORT || dstPort == PROTO_IMAP_PORT)) { + return TRUE; + } + + // Check custom port + if (customPort > 0 && (srcPort == customPort || dstPort == customPort)) { + return TRUE; + } + + return FALSE; +} + +// Helper function to get protocol name for logging +static const char* getProtocolName(UINT16 port) { + switch (port) { + case PROTO_HTTP_PORT: return "HTTP"; + case PROTO_HTTPS_PORT: return "HTTPS"; + case PROTO_DNS_PORT: return "DNS"; + case PROTO_SSH_PORT: return "SSH"; + case PROTO_FTP_PORT: return "FTP"; + case PROTO_SMTP_PORT: return "SMTP"; + case PROTO_POP3_PORT: return "POP3"; + case PROTO_IMAP_PORT: return "IMAP"; + default: return "Custom"; + } +} + +static Ihandle* protocolSetupUI() { + Ihandle *protocolControlsBox = IupVbox( + IupHbox( + inboundCheckbox = IupToggle("Inbound", NULL), + outboundCheckbox = IupToggle("Outbound", NULL), + IupLabel("Chance(%):"), + chanceInput = IupText(NULL), + NULL + ), + IupHbox( + httpCheckbox = IupToggle("HTTP", NULL), + httpsCheckbox = IupToggle("HTTPS", NULL), + dnsCheckbox = IupToggle("DNS", NULL), + sshCheckbox = IupToggle("SSH", NULL), + NULL + ), + IupHbox( + ftpCheckbox = IupToggle("FTP", NULL), + emailCheckbox = IupToggle("Email", NULL), + IupLabel("Custom Port:"), + customPortInput = IupText(NULL), + NULL + ), + NULL + ); + + // Setup chance input + IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(chanceInput, "VALUE", "10.0"); + IupSetCallback(chanceInput, "VALUECHANGED_CB", uiSyncChance); + IupSetAttribute(chanceInput, SYNCED_VALUE, (char*)&chance); + + // Setup custom port input + IupSetAttribute(customPortInput, "VISIBLECOLUMNS", "6"); + IupSetAttribute(customPortInput, "VALUE", "8080"); + IupSetCallback(customPortInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(customPortInput, SYNCED_VALUE, (char*)&customPort); + IupSetAttribute(customPortInput, INTEGER_MIN, "1"); + IupSetAttribute(customPortInput, INTEGER_MAX, "65535"); + + // Setup protocol checkboxes + IupSetCallback(httpCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(httpCheckbox, SYNCED_VALUE, (char*)&filterHTTP); + + IupSetCallback(httpsCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(httpsCheckbox, SYNCED_VALUE, (char*)&filterHTTPS); + + IupSetCallback(dnsCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(dnsCheckbox, SYNCED_VALUE, (char*)&filterDNS); + + IupSetCallback(sshCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(sshCheckbox, SYNCED_VALUE, (char*)&filterSSH); + + IupSetCallback(ftpCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(ftpCheckbox, SYNCED_VALUE, (char*)&filterFTP); + + IupSetCallback(emailCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(emailCheckbox, SYNCED_VALUE, (char*)&filterEmail); + + // Setup direction checkboxes + IupSetCallback(inboundCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(inboundCheckbox, SYNCED_VALUE, (char*)&protocolInbound); + IupSetCallback(outboundCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(outboundCheckbox, SYNCED_VALUE, (char*)&protocolOutbound); + + // Enable by default + IupSetAttribute(inboundCheckbox, "VALUE", "ON"); + IupSetAttribute(outboundCheckbox, "VALUE", "ON"); + + if (parameterized) { + setFromParameter(inboundCheckbox, "VALUE", NAME"-inbound"); + setFromParameter(outboundCheckbox, "VALUE", NAME"-outbound"); + setFromParameter(chanceInput, "VALUE", NAME"-chance"); + setFromParameter(httpCheckbox, "VALUE", NAME"-http"); + setFromParameter(httpsCheckbox, "VALUE", NAME"-https"); + setFromParameter(dnsCheckbox, "VALUE", NAME"-dns"); + setFromParameter(sshCheckbox, "VALUE", NAME"-ssh"); + setFromParameter(ftpCheckbox, "VALUE", NAME"-ftp"); + setFromParameter(emailCheckbox, "VALUE", NAME"-email"); + setFromParameter(customPortInput, "VALUE", NAME"-port"); + } + + return protocolControlsBox; +} + +static void protocolStartup() { + LOG("protocol filter enabled: HTTP=%d, HTTPS=%d, DNS=%d, SSH=%d, FTP=%d, Email=%d, Custom=%d", + filterHTTP, filterHTTPS, filterDNS, filterSSH, filterFTP, filterEmail, customPort); +} + +static void protocolCloseDown(PacketNode *head, PacketNode *tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + LOG("protocol filter disabled"); +} + +static short protocolProcess(PacketNode *head, PacketNode* tail) { + int processed = 0; + + while (head->next != tail) { + PacketNode *pac = head->next; + + if (checkDirection(pac->addr.Outbound, protocolInbound, protocolOutbound)) { + if (matchesProtocolFilter(pac) && calcChance(chance)) { + PWINDIVERT_TCPHDR tcpHeader = NULL; + PWINDIVERT_UDPHDR udpHeader = NULL; + UINT16 port = 0; + + // Get port for logging + WinDivertHelperParsePacket(pac->packet, pac->packetLen, NULL, NULL, NULL, NULL, NULL, + &tcpHeader, &udpHeader, NULL, NULL, NULL, NULL); + if (tcpHeader) { + port = pac->addr.Outbound ? ntohs(tcpHeader->DstPort) : ntohs(tcpHeader->SrcPort); + } else if (udpHeader) { + port = pac->addr.Outbound ? ntohs(udpHeader->DstPort) : ntohs(udpHeader->SrcPort); + } + + LOG("dropped %s packet on port %d with chance %.1f%%, direction %s", + getProtocolName(port), port, chance/100.0, + pac->addr.Outbound ? "OUTBOUND" : "INBOUND"); + + freeNode(popNode(pac)); + ++processed; + } else { + head = head->next; + } + } else { + head = head->next; + } + } + + return processed > 0; +} + +Module protocolModule = { + "Protocol Filter", + NAME, + (short*)&protocolEnabled, + protocolSetupUI, + protocolStartup, + protocolCloseDown, + protocolProcess, + // runtime fields + 0, 0, NULL +}; \ No newline at end of file diff --git a/src/reset.c b/src/reset.c index 3a59230..d4ea411 100644 --- a/src/reset.c +++ b/src/reset.c @@ -28,15 +28,16 @@ static int resetSetRSTNextButtonCb(Ihandle *ih) { static Ihandle* resetSetupUI() { Ihandle *dupControlsBox = IupHbox( - rstButton = IupButton("RST next packet", NULL), - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Chance(%):"), + rstButton = IupButton("RST Next", NULL), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Chance:"), chanceInput = IupText(NULL), + IupLabel("%"), NULL - ); + ); - IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "3"); IupSetAttribute(chanceInput, "VALUE", "0"); IupSetCallback(chanceInput, "VALUECHANGED_CB", uiSyncChance); IupSetAttribute(chanceInput, SYNCED_VALUE, (char*)&chance); @@ -100,6 +101,8 @@ static short resetProcess(PacketNode *head, PacketNode *tail) { LOG("injecting reset w/ chance %.1f%%", chance/100.0); pTcpHdr->Rst = 1; WinDivertHelperCalcChecksums(pac->packet, pac->packetLen, NULL, 0); + // Log the packet action + logPacketAction(pac, "RST", "reset"); reset = TRUE; if (setNextCount > 0) { diff --git a/src/stats.c b/src/stats.c new file mode 100644 index 0000000..2290172 --- /dev/null +++ b/src/stats.c @@ -0,0 +1,260 @@ +// real-time statistics and monitoring module +#include +#include +#include "iup.h" +#include "common.h" +#define NAME "stats" + +// Statistics structure +typedef struct { + DWORD startTime; + DWORD lastUpdateTime; + + // Packet counters + UINT64 totalPackets; + UINT64 inboundPackets; + UINT64 outboundPackets; + UINT64 droppedPackets; + UINT64 modifiedPackets; + + // Byte counters + UINT64 totalBytes; + UINT64 inboundBytes; + UINT64 outboundBytes; + UINT64 droppedBytes; + + // Protocol breakdown + UINT64 tcpPackets; + UINT64 udpPackets; + UINT64 icmpPackets; + UINT64 otherPackets; + + // Performance metrics + UINT32 minLatency; + UINT32 maxLatency; + UINT64 totalLatency; + UINT32 avgLatency; + + // Current rates (packets/sec, bytes/sec) + UINT32 packetRate; + UINT32 byteRate; +} NetworkStatistics; + +static Ihandle *enabledCheckbox, *statsDisplay, *resetButton; + +static volatile short statsEnabled = 1; +static volatile short statsShutdown = 0; +static volatile short displayMode = 1; +static NetworkStatistics stats = {0}; +static CRITICAL_SECTION statsMutex; +static int statsInitialized = 0; +static int statsStarted = 0; + +// Helper function to update statistics +void updateStatistics(PacketNode* pac, BOOL wasDropped, BOOL wasModified) { + if (!statsStarted || statsShutdown) return; + + EnterCriticalSection(&statsMutex); + + DWORD currentTime = GetTickCount(); + if (stats.startTime == 0) { + stats.startTime = currentTime; + } + + // Update packet counters + stats.totalPackets++; + if (pac->addr.Outbound) { + stats.outboundPackets++; + } else { + stats.inboundPackets++; + } + + if (wasDropped) { + stats.droppedPackets++; + stats.droppedBytes += pac->packetLen; + } else { + stats.totalBytes += pac->packetLen; + if (pac->addr.Outbound) { + stats.outboundBytes += pac->packetLen; + } else { + stats.inboundBytes += pac->packetLen; + } + } + + if (wasModified) { + stats.modifiedPackets++; + } + + // Update protocol breakdown + PWINDIVERT_IPHDR ipHeader = NULL; + PWINDIVERT_TCPHDR tcpHeader = NULL; + PWINDIVERT_UDPHDR udpHeader = NULL; + PWINDIVERT_ICMPHDR icmpHeader = NULL; + + WinDivertHelperParsePacket(pac->packet, pac->packetLen, &ipHeader, NULL, NULL, + &icmpHeader, NULL, &tcpHeader, &udpHeader, NULL, NULL, NULL, NULL); + + if (tcpHeader) { + stats.tcpPackets++; + } else if (udpHeader) { + stats.udpPackets++; + } else if (icmpHeader) { + stats.icmpPackets++; + } else { + stats.otherPackets++; + } + + // Calculate rates (update every second) + if (currentTime - stats.lastUpdateTime >= 1000) { + DWORD timeDiff = currentTime - stats.lastUpdateTime; + if (timeDiff > 0) { + stats.packetRate = (UINT32)((stats.totalPackets * 1000) / (currentTime - stats.startTime)); + stats.byteRate = (UINT32)((stats.totalBytes * 1000) / (currentTime - stats.startTime)); + } + stats.lastUpdateTime = currentTime; + } + + LeaveCriticalSection(&statsMutex); +} + +// Format statistics for display - show all information on one panel +static void formatStatistics(char* buffer, size_t bufferSize) { + EnterCriticalSection(&statsMutex); + + DWORD runtime = (GetTickCount() - stats.startTime) / 1000; // Convert to seconds + if (runtime == 0) runtime = 1; // Avoid division by zero + + // Show all statistics in a comprehensive format + snprintf(buffer, bufferSize, + "Runtime: %dm %ds | Total Packets: %I64u (Dropped: %I64u, Modified: %I64u)\n" + "Inbound: %I64u pkts (%I64u MB) | Outbound: %I64u pkts (%I64u MB)\n" + "Protocols - TCP: %I64u | UDP: %I64u | ICMP: %I64u | Other: %I64u\n" + "Current Rate: %u pps (%u kBps) | Drop Rate: %.2f%%", + (int)(runtime / 60), (int)(runtime % 60), + stats.totalPackets, stats.droppedPackets, stats.modifiedPackets, + stats.inboundPackets, stats.inboundBytes / (1024 * 1024), + stats.outboundPackets, stats.outboundBytes / (1024 * 1024), + stats.tcpPackets, stats.udpPackets, stats.icmpPackets, stats.otherPackets, + stats.packetRate, stats.byteRate / 1024, + stats.totalPackets > 0 ? (stats.droppedPackets * 100.0) / stats.totalPackets : 0.0); + + LeaveCriticalSection(&statsMutex); +} + +// Reset statistics +static int resetStatsCallback(Ihandle *ih) { + UNREFERENCED_PARAMETER(ih); + + if (!statsInitialized) return IUP_DEFAULT; + + EnterCriticalSection(&statsMutex); + memset(&stats, 0, sizeof(NetworkStatistics)); + stats.startTime = GetTickCount(); + stats.lastUpdateTime = stats.startTime; + stats.minLatency = UINT32_MAX; + LeaveCriticalSection(&statsMutex); + + if (statsDisplay) { + IupSetAttribute(statsDisplay, "VALUE", "Statistics reset"); + } + + LOG("stats reset"); + return IUP_DEFAULT; +} + + + +static Ihandle* statsSetupUI() { + if (!statsInitialized) { + InitializeCriticalSection(&statsMutex); + statsInitialized = 1; + } + + Ihandle *statsControlsBox = IupVbox( + IupHbox( + enabledCheckbox = IupToggle("Enable Statistics", NULL), + resetButton = IupButton("Reset", NULL), + NULL + ), + statsDisplay = IupText(NULL), + NULL + ); + + // Setup reset button + IupSetCallback(resetButton, "ACTION", (Icallback)resetStatsCallback); + IupSetAttribute(resetButton, "PADDING", "4x"); + + // Setup enabled checkbox + IupSetCallback(enabledCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(enabledCheckbox, SYNCED_VALUE, (char*)&statsEnabled); + IupSetAttribute(enabledCheckbox, "VALUE", "ON"); + + // Setup display text area + IupSetAttribute(statsDisplay, "MULTILINE", "YES"); + IupSetAttribute(statsDisplay, "READONLY", "YES"); + IupSetAttribute(statsDisplay, "EXPAND", "YES"); + IupSetAttribute(statsDisplay, "SIZE", "400x200"); + IupSetAttribute(statsDisplay, "FONT", "Courier, 9"); + IupSetAttribute(statsDisplay, "VALUE", "Statistics will appear here..."); + + if (parameterized) { + setFromParameter(enabledCheckbox, "VALUE", NAME"-enabled"); + } + + return statsControlsBox; +} + +static void statsStartup() { + statsShutdown = 0; + statsStarted = 1; + memset(&stats, 0, sizeof(NetworkStatistics)); + stats.startTime = GetTickCount(); + stats.lastUpdateTime = stats.startTime; + stats.minLatency = UINT32_MAX; +} + +static void statsCloseDown(PacketNode *head, PacketNode *tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + + statsStarted = 0; + statsShutdown = 1; + Sleep(10); +} + +static short statsProcess(PacketNode *head, PacketNode* tail) { + if (!statsEnabled) return FALSE; + + // Update display every few iterations + static int updateCounter = 0; + if (++updateCounter >= 100) { // Update every 100 packets + char displayBuffer[1024]; + formatStatistics(displayBuffer, sizeof(displayBuffer)); + + // Update UI text area + IupSetAttribute(statsDisplay, "VALUE", displayBuffer); + + updateCounter = 0; + } + + // Monitor packets (statistics are updated by other modules via updateStatistics) + PacketNode *pac = head->next; + while (pac != tail) { + updateStatistics(pac, FALSE, FALSE); // Assume not dropped/modified by this module + pac = pac->next; + } + + return FALSE; // Don't modify packet flow +} + +Module statsModule = { + "Statistics", + NAME, + (short*)&statsEnabled, + statsSetupUI, + statsStartup, + statsCloseDown, + statsProcess, + // runtime fields + 0, 0, NULL +}; \ No newline at end of file diff --git a/src/tamper.c b/src/tamper.c index 9207860..db50fa7 100644 --- a/src/tamper.c +++ b/src/tamper.c @@ -15,14 +15,15 @@ static volatile short tamperEnabled = 0, static Ihandle* tamperSetupUI() { Ihandle *dupControlsBox = IupHbox( checksumCheckbox = IupToggle("Redo Checksum", NULL), - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Chance(%):"), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Chance:"), chanceInput = IupText(NULL), + IupLabel("%"), NULL - ); + ); - IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "3"); IupSetAttribute(chanceInput, "VALUE", "10.0"); IupSetCallback(chanceInput, "VALUECHANGED_CB", uiSyncChance); IupSetAttribute(chanceInput, SYNCED_VALUE, (char*)&chance); @@ -111,6 +112,10 @@ static short tamperProcess(PacketNode *head, PacketNode *tail) { if (doChecksum) { WinDivertHelperCalcChecksums(pac->packet, pac->packetLen, NULL, 0); } + // Log the packet action + logPacketAction(pac, "MODIFY", "tamper"); + // Update statistics for modified packet + updateStatistics(pac, FALSE, TRUE); // wasDropped = FALSE, wasModified = TRUE tampered = TRUE; } @@ -130,4 +135,4 @@ Module tamperModule = { tamperProcess, // runtime fields 0, 0, NULL -}; +}; \ No newline at end of file diff --git a/src/throttle.c b/src/throttle.c index 7cc170f..4084469 100644 --- a/src/throttle.c +++ b/src/throttle.c @@ -30,18 +30,21 @@ static INLINE_FUNCTION short isBufEmpty() { } static Ihandle *throttleSetupUI() { + // Create a more compact layout by grouping related controls Ihandle *throttleControlsBox = IupHbox( dropThrottledCheckbox = IupToggle("Drop Throttled", NULL), - IupLabel("Timeframe(ms):"), + IupLabel("Timeframe:"), frameInput = IupText(NULL), - inboundCheckbox = IupToggle("Inbound", NULL), - outboundCheckbox = IupToggle("Outbound", NULL), - IupLabel("Chance(%):"), + IupLabel("ms"), + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + IupLabel("Chance:"), chanceInput = IupText(NULL), + IupLabel("%"), NULL - ); + ); - IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(chanceInput, "VISIBLECOLUMNS", "3"); IupSetAttribute(chanceInput, "VALUE", "10.0"); IupSetCallback(chanceInput, "VALUECHANGED_CB", uiSyncChance); IupSetAttribute(chanceInput, SYNCED_VALUE, (char*)&chance); @@ -90,7 +93,10 @@ static void clearBufPackets(PacketNode *tail) { PacketNode *oldLast = tail->prev; LOG("Throttled end, send all %d packets. Buffer at max: %s", bufSize, bufSize == KEEP_AT_MOST ? "YES" : "NO"); while (!isBufEmpty()) { - insertAfter(popNode(bufTail->prev), oldLast); + PacketNode *pac = popNode(bufTail->prev); + insertAfter(pac, oldLast); + // Log the packet action when it's sent + logPacketAction(pac, "THROTTLE", "throttle"); --bufSize; } throttleStartTick = 0; @@ -99,7 +105,10 @@ static void clearBufPackets(PacketNode *tail) { static void dropBufPackets() { LOG("Throttled end, drop all %d packets. Buffer at max: %s", bufSize, bufSize == KEEP_AT_MOST ? "YES" : "NO"); while (!isBufEmpty()) { - freeNode(popNode(bufTail->prev)); + PacketNode *pac = popNode(bufTail->prev); + // Log the packet action when it's dropped + logPacketAction(pac, "THROTTLE_DROP", "throttle"); + freeNode(pac); --bufSize; } throttleStartTick = 0; @@ -132,7 +141,10 @@ static short throttleProcess(PacketNode *head, PacketNode *tail) { DWORD currentTick = timeGetTime(); while (bufSize < KEEP_AT_MOST && pac != head) { if (checkDirection(pac->addr.Outbound, throttleInbound, throttleOutbound)) { - insertAfter(popNode(pac), bufHead); + PacketNode *moved = popNode(pac); + insertAfter(moved, bufHead); + // Log the packet action when it's throttled + logPacketAction(moved, "THROTTLE", "throttle"); ++bufSize; pac = tail->prev; } else { diff --git a/src/tls.c b/src/tls.c new file mode 100644 index 0000000..5f70a9d --- /dev/null +++ b/src/tls.c @@ -0,0 +1,269 @@ +// TLS-aware traffic handler module +// +// This module simulates network impairments specifically for TLS/SSL encrypted traffic (HTTPS, IMAPS, etc). +// It intelligently differentiates between: +// - TLS HANDSHAKE packets (initial connection setup) - critical for connection establishment +// - TLS APPLICATION DATA packets (actual encrypted content) - user data +// +// WHAT IT DOES: +// 1. Identifies TLS traffic on standard ports (443, 8443, 993, 995) +// 2. Parses TLS record headers to determine packet type +// 3. Applies different loss/delay strategies based on packet type +// +// UI PARAMETERS: +// - In/Out toggles: Filter traffic by direction (inbound/outbound) +// - "Seq Aware": Enable sequence-aware retransmission delays +// - "Handshake %": Drop probability for handshake packets (95% = protect 95%, drop 5%) +// - "Data Loss %": Drop probability for application data packets (5% = drop 5%) +// - "Delay ms": Artificial delay for retransmission simulation (currently informational) +// +// PRACTICAL USES: +// - Test HTTPS connection robustness during handshake failures +// - Simulate packet loss in encrypted sessions +// - Debug TLS timeout behavior +// - Stress-test HTTPS server stability +// +#include +#include +#include "iup.h" +#include "common.h" +#define NAME "tls" + +// TLS record types (RFC 5246) +#define TLS_RECORD_CHANGE_CIPHER_SPEC 20 +#define TLS_RECORD_ALERT 21 +#define TLS_RECORD_HANDSHAKE 22 +#define TLS_RECORD_APPLICATION_DATA 23 + +// TLS handshake types +#define TLS_HANDSHAKE_CLIENT_HELLO 1 +#define TLS_HANDSHAKE_SERVER_HELLO 2 +#define TLS_HANDSHAKE_CERTIFICATE 11 +#define TLS_HANDSHAKE_SERVER_KEY_EXCHANGE 12 +#define TLS_HANDSHAKE_CERTIFICATE_REQUEST 13 +#define TLS_HANDSHAKE_SERVER_HELLO_DONE 14 +#define TLS_HANDSHAKE_CERTIFICATE_VERIFY 15 +#define TLS_HANDSHAKE_CLIENT_KEY_EXCHANGE 16 +#define TLS_HANDSHAKE_FINISHED 20 + +typedef struct { + UINT8 contentType; + UINT16 version; + UINT16 length; +} TLSRecordHeader; + +static Ihandle *inboundCheckbox, *outboundCheckbox; +static Ihandle *handshakeProtectionInput, *dataLossRateInput; +static Ihandle *sequenceAwareCheckbox, *retransmissionDelayInput; + +static volatile short tlsEnabled = 0, + tlsInbound = 1, tlsOutbound = 1, + sequenceAware = 1, + handshakeProtection = 95, // Protect handshake packets with 95% probability + dataLossRate = 500, // 5% loss rate for application data + retransmissionDelay = 200; // 200ms delay for retransmissions + + +// Helper function to parse TLS record from packet data +static BOOL parseTLSRecord(char* packet, UINT packetLen, TLSRecordHeader* tlsHeader, char** tlsPayload) { + PWINDIVERT_IPHDR ipHeader = NULL; + PWINDIVERT_TCPHDR tcpHeader = NULL; + char* data = NULL; + UINT dataLen = 0; + + // Parse packet to get TCP payload + if (!WinDivertHelperParsePacket(packet, packetLen, &ipHeader, NULL, NULL, NULL, NULL, + &tcpHeader, NULL, (PVOID*)&data, &dataLen, NULL, NULL)) { + return FALSE; + } + + // Check if this is TCP traffic + if (!tcpHeader || !data || dataLen < sizeof(TLSRecordHeader)) { + return FALSE; + } + + // Parse TLS record header + tlsHeader->contentType = data[0]; + tlsHeader->version = (data[1] << 8) | data[2]; + tlsHeader->length = (data[3] << 8) | data[4]; + + // Validate TLS record + if (tlsHeader->contentType < TLS_RECORD_CHANGE_CIPHER_SPEC || + tlsHeader->contentType > TLS_RECORD_APPLICATION_DATA || + tlsHeader->length + 5 > dataLen) { + return FALSE; + } + + *tlsPayload = data + 5; // Skip TLS record header + return TRUE; +} + +// Check if this is a TLS handshake packet +static BOOL isTLSHandshakePacket(TLSRecordHeader* tlsHeader, char* tlsPayload) { + if (tlsHeader->contentType == TLS_RECORD_HANDSHAKE && tlsHeader->length > 0) { + UINT8 handshakeType = tlsPayload[0]; + return (handshakeType >= TLS_HANDSHAKE_CLIENT_HELLO && + handshakeType <= TLS_HANDSHAKE_FINISHED); + } + return FALSE; +} + +// Check if packet is on TLS port (443 or other HTTPS ports) +static BOOL isTLSPort(PWINDIVERT_TCPHDR tcpHeader) { + UINT16 srcPort = ntohs(tcpHeader->SrcPort); + UINT16 dstPort = ntohs(tcpHeader->DstPort); + + // Common TLS ports + return (srcPort == 443 || dstPort == 443 || // HTTPS + srcPort == 8443 || dstPort == 8443 || // Alternative HTTPS + srcPort == 993 || dstPort == 993 || // IMAPS + srcPort == 995 || dstPort == 995); // POP3S +} + +static Ihandle* tlsSetupUI() { + Ihandle *tlsControlsBox = IupHbox( + inboundCheckbox = IupToggle("In", NULL), + outboundCheckbox = IupToggle("Out", NULL), + sequenceAwareCheckbox = IupToggle("Seq Aware", NULL), + IupLabel("Handshake:"), + handshakeProtectionInput = IupText(NULL), + IupLabel("%"), + IupLabel("Data Loss:"), + dataLossRateInput = IupText(NULL), + IupLabel("%"), + IupLabel("Delay:"), + retransmissionDelayInput = IupText(NULL), + IupLabel("ms"), + NULL + ); + + // Setup handshake protection input + IupSetAttribute(handshakeProtectionInput, "VISIBLECOLUMNS", "3"); + IupSetAttribute(handshakeProtectionInput, "VALUE", "95.0"); + IupSetCallback(handshakeProtectionInput, "VALUECHANGED_CB", uiSyncChance); + IupSetAttribute(handshakeProtectionInput, SYNCED_VALUE, (char*)&handshakeProtection); + + // Setup data loss rate input + IupSetAttribute(dataLossRateInput, "VISIBLECOLUMNS", "3"); + IupSetAttribute(dataLossRateInput, "VALUE", "5.0"); + IupSetCallback(dataLossRateInput, "VALUECHANGED_CB", uiSyncChance); + IupSetAttribute(dataLossRateInput, SYNCED_VALUE, (char*)&dataLossRate); + + // Setup retransmission delay input + IupSetAttribute(retransmissionDelayInput, "VISIBLECOLUMNS", "4"); + IupSetAttribute(retransmissionDelayInput, "VALUE", "200"); + IupSetCallback(retransmissionDelayInput, "VALUECHANGED_CB", uiSyncInteger); + IupSetAttribute(retransmissionDelayInput, SYNCED_VALUE, (char*)&retransmissionDelay); + IupSetAttribute(retransmissionDelayInput, INTEGER_MIN, "0"); + IupSetAttribute(retransmissionDelayInput, INTEGER_MAX, "5000"); + + // Setup direction checkboxes + IupSetCallback(inboundCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(inboundCheckbox, SYNCED_VALUE, (char*)&tlsInbound); + IupSetCallback(outboundCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(outboundCheckbox, SYNCED_VALUE, (char*)&tlsOutbound); + + // Setup sequence aware checkbox + IupSetCallback(sequenceAwareCheckbox, "ACTION", (Icallback)uiSyncToggle); + IupSetAttribute(sequenceAwareCheckbox, SYNCED_VALUE, (char*)&sequenceAware); + + // Enable by default + IupSetAttribute(inboundCheckbox, "VALUE", "ON"); + IupSetAttribute(outboundCheckbox, "VALUE", "ON"); + IupSetAttribute(sequenceAwareCheckbox, "VALUE", "ON"); + + if (parameterized) { + setFromParameter(inboundCheckbox, "VALUE", NAME"-inbound"); + setFromParameter(outboundCheckbox, "VALUE", NAME"-outbound"); + setFromParameter(sequenceAwareCheckbox, "VALUE", NAME"-sequence"); + setFromParameter(handshakeProtectionInput, "VALUE", NAME"-handshake"); + setFromParameter(dataLossRateInput, "VALUE", NAME"-data"); + setFromParameter(retransmissionDelayInput, "VALUE", NAME"-delay"); + } + + return tlsControlsBox; +} + +static void tlsStartup() { + LOG("TLS handler enabled: handshake protection=%d%%, data loss=%d%%, delay=%dms", + handshakeProtection/100, dataLossRate/100, retransmissionDelay); +} + +static void tlsCloseDown(PacketNode *head, PacketNode *tail) { + UNREFERENCED_PARAMETER(head); + UNREFERENCED_PARAMETER(tail); + LOG("TLS handler disabled"); +} + +static short tlsProcess(PacketNode *head, PacketNode* tail) { + int processed = 0; + + while (head->next != tail) { + PacketNode *pac = head->next; + + if (checkDirection(pac->addr.Outbound, tlsInbound, tlsOutbound)) { + PWINDIVERT_TCPHDR tcpHeader = NULL; + TLSRecordHeader tlsHeader; + char* tlsPayload = NULL; + BOOL shouldDrop = FALSE; + + // Parse TCP header first + WinDivertHelperParsePacket(pac->packet, pac->packetLen, NULL, NULL, NULL, NULL, NULL, + &tcpHeader, NULL, NULL, NULL, NULL, NULL); + + // Only process TLS traffic + if (tcpHeader && isTLSPort(tcpHeader)) { + if (parseTLSRecord(pac->packet, pac->packetLen, &tlsHeader, &tlsPayload)) { + // TLS traffic detected + if (isTLSHandshakePacket(&tlsHeader, tlsPayload)) { + // Handshake packet - apply protection + if (!calcChance(handshakeProtection)) { + shouldDrop = TRUE; + LOG("Dropped TLS handshake packet (protection: %d%%), direction %s", + handshakeProtection/100, pac->addr.Outbound ? "OUTBOUND" : "INBOUND"); + } + } else if (tlsHeader.contentType == TLS_RECORD_APPLICATION_DATA) { + // Application data - apply normal loss rate + if (calcChance(dataLossRate)) { + shouldDrop = TRUE; + LOG("Dropped TLS application data (loss rate: %d%%), direction %s", + dataLossRate/100, pac->addr.Outbound ? "OUTBOUND" : "INBOUND"); + } + } + + if (shouldDrop) { + freeNode(popNode(pac)); + ++processed; + continue; + } + + // Add artificial delay for retransmissions if enabled + if (retransmissionDelay > 0 && sequenceAware) { + // Check for potential retransmission (simplified heuristic) + if (tlsHeader.contentType == TLS_RECORD_APPLICATION_DATA) { + // Add to lag processing if lag module exists + // For now, just pass through with logging + LOG("TLS packet processed with %dms delay consideration", retransmissionDelay); + } + } + } + } + } + + head = head->next; + } + + return processed > 0; +} + +Module tlsModule = { + "TLS Handler", + NAME, + (short*)&tlsEnabled, + tlsSetupUI, + tlsStartup, + tlsCloseDown, + tlsProcess, + // runtime fields + 0, 0, NULL +}; \ No newline at end of file