Connection Strategies
Connection strategies determine how TRECO establishes and manages TCP/TLS connections for HTTP requests. The choice of connection strategy significantly impacts race window timing and overall performance.
Overview
TRECO provides four connection strategies:
Preconnect (Recommended): Establish connections before synchronization point
Lazy: Connect on-demand when sending requests
Pooled: Share connection pool across threads
Multiplexed: HTTP/2 multiplexing over single connection
Each strategy has different performance characteristics and use cases.
Preconnect Strategy (Recommended)
The preconnect strategy provides the best timing precision for race condition testing by eliminating connection overhead from the race window.
How It Works
Establish TCP/TLS connections for all threads before reaching the synchronization point
Threads perform complete handshake (TCP + TLS if applicable)
Connections are ready and warm when threads synchronize
Threads send requests immediately after release from barrier
No connection setup time included in race window
race:
threads: 20
sync_mechanism: barrier
connection_strategy: preconnect # Recommended for race conditions
Performance Characteristics
Race Window: < 1μs with Python 3.14t, ~10μs with Python 3.10+
Connection Overhead: Eliminated from race window
Memory Usage: Higher (one connection per thread)
CPU Usage: Moderate (during connection phase)
Best For: Race condition testing (all types)
Visual Representation
Timeline:
[Pre-connection Phase]
Thread 1: TCP handshake → TLS handshake → [Ready]
Thread 2: TCP handshake → TLS handshake → [Ready]
Thread 3: TCP handshake → TLS handshake → [Ready]
Thread 4: TCP handshake → TLS handshake → [Ready]
[Synchronization Phase]
All threads: [Wait at barrier] → [Release] → Send requests
↑
Race window starts here
(no connection overhead!)
Advantages
- ✅ Eliminates Connection Overhead
TCP and TLS handshakes completed before race, resulting in sub-microsecond race windows
- ✅ Consistent Timing
All threads start from the same ready state, minimizing timing variance
- ✅ Best Success Rate
Highest probability of triggering race conditions
- ✅ Predictable Performance
Connection issues detected before the race begins
- ✅ Optimal for Python 3.14t
Fully leverages GIL-free parallelism for connection establishment
Disadvantages
- ❌ Higher Memory Usage
Maintains one connection per thread simultaneously
- ❌ More Socket Descriptors
May hit OS limits with very high thread counts (>100)
- ❌ Connection Establishment Time
Additional upfront time before race begins (but not in race window)
- ❌ Server Load
Creates burst of connections during setup phase
Use Cases
Ideal for:
✅ All race condition testing
✅ Double-spending attacks
✅ Inventory manipulation
✅ Fund redemption exploits
✅ TOCTOU vulnerabilities
✅ Any scenario requiring sub-microsecond timing
Example: Double-Spending Attack
states:
race_payment:
description: "Process payment multiple times"
request: |
POST /api/process-payment HTTP/1.1
Authorization: Bearer {{ token }}
Content-Type: application/json
{"payment_token": "{{ payment_token }}"}
race:
threads: 5
sync_mechanism: barrier
connection_strategy: preconnect # Essential for timing
logger:
on_state_leave: |
Race window: {{ race_window }}μs
{% if race_window < 1 %}
✓ EXCELLENT: Sub-microsecond precision
{% elif race_window < 10 %}
✓ VERY GOOD: Optimal for race conditions
{% else %}
⚠ Check configuration
{% endif %}
Example: Inventory Race
states:
race_purchase:
description: "Purchase limited stock concurrently"
request: |
POST /api/purchase HTTP/1.1
Authorization: Bearer {{ token }}
Content-Type: application/json
{"item_id": "LIMITED_001", "quantity": 1}
race:
threads: 50
sync_mechanism: barrier
connection_strategy: preconnect
Configuration Tips
Optimal Configuration:
race:
threads: 20 # Reasonable count
sync_mechanism: barrier # Best with preconnect
connection_strategy: preconnect # Always use for races
thread_propagation: single # Default
Thread Count Guidelines:
Start with 10-20 threads
Increase gradually if needed
Monitor connection success rate
Consider system limits (ulimit -n)
System Tuning:
# Check current file descriptor limit
ulimit -n
# Increase if needed (temporary)
ulimit -n 4096
# Permanent (add to /etc/security/limits.conf)
* soft nofile 4096
* hard nofile 4096
Lazy Strategy
The lazy strategy connects on-demand when sending each request, without pre-establishing connections.
How It Works
Threads synchronize at barrier/latch
Threads are released simultaneously
Each thread establishes TCP/TLS connection after release
Connection overhead included in race window
Requests sent after connection completes
race:
threads: 20
sync_mechanism: barrier
connection_strategy: lazy # Not recommended for races
Performance Characteristics
Race Window: 50-500ms+ (includes connection time)
Connection Overhead: Included in race window
Memory Usage: Lower (connects only when needed)
CPU Usage: Lower overall
Best For: Testing connection timing, sequential requests
Visual Representation
Timeline:
[Synchronization Phase]
All threads: [Wait at barrier] → [Release] → Connect → Send requests
↑
Race window starts here
(includes connection overhead!)
Thread 1: TCP → TLS → Request (total: 100ms)
Thread 2: TCP → TLS → Request (total: 105ms)
Thread 3: TCP → TLS → Request (total: 98ms)
Thread 4: TCP → TLS → Request (total: 110ms)
Race window: ~12ms (variation in connection times)
Advantages
- ✅ Lower Memory Usage
Connections created only when needed
- ✅ Simpler Implementation
No pre-connection management required
- ✅ Tests Real Conditions
Includes connection overhead in timing
- ✅ Fewer Socket Descriptors
Suitable for very high thread counts
Disadvantages
- ❌ Poor Timing Precision
Race window typically 50-500ms or more
- ❌ Variable Timing
Connection times introduce significant variance
- ❌ Lower Success Rate
Difficult to trigger race conditions reliably
- ❌ Not Suitable for Races
Should not be used for race condition testing
Use Cases
Suitable for:
Testing connection timing vulnerabilities
Sequential request testing
Low-resource environments
Scenarios where connection timing matters
NOT suitable for:
❌ Race condition testing
❌ Double-spending attacks
❌ Inventory manipulation
❌ Time-sensitive exploits
Example: Connection Timing Test
states:
test_connection:
description: "Test authentication during connection"
request: |
GET /api/resource HTTP/1.1
Authorization: Bearer {{ token }}
race:
threads: 10
sync_mechanism: barrier
connection_strategy: lazy # Include connection in timing
logger:
on_thread_leave: |
[Thread {{ thread.id }}] Total time: {{ response_time }}ms
(includes connection establishment)
When to Use
Use lazy strategy when:
Testing scenarios where connection timing is relevant
Memory is severely constrained
You need many threads (>100) with socket descriptor limits
Not testing race conditions
Don’t use lazy strategy when:
Testing race conditions (use preconnect)
Need sub-100ms timing precision (use preconnect)
Testing double-spending or similar (use preconnect)
Pooled Strategy
The pooled strategy shares a connection pool across threads, reusing connections for multiple requests.
How It Works
Connection pool created with configurable size
Threads acquire connection from pool
Requests serialized through pool
Connection released back to pool after use
Next thread acquires same connection
race:
threads: 20
sync_mechanism: barrier
connection_strategy: pooled
pool_size: 5 # Share 5 connections among 20 threads
Performance Characteristics
Race Window: Variable, depends on pool size and contention
Connection Overhead: Mixed (first request per connection, then reuse)
Memory Usage: Low (limited by pool size)
CPU Usage: Low
Best For: Sequential testing, connection reuse scenarios
Visual Representation
Connection Pool: [C1][C2][C3][C4][C5]
Thread 1: Acquire C1 → Request → Release C1
Thread 2: Acquire C2 → Request → Release C2
Thread 3: Acquire C3 → Request → Release C3
Thread 4: Acquire C4 → Request → Release C4
Thread 5: Acquire C5 → Request → Release C5
Thread 6: Wait for C1 → Acquire C1 → Request → Release C1
...
Serialization: Threads wait for available connections
Advantages
- ✅ Resource Efficient
Limited number of connections regardless of thread count
- ✅ Connection Reuse
Amortizes connection overhead across requests
- ✅ Predictable Resource Usage
Won’t exceed pool size connections
- ✅ Server-Friendly
Limits connection burst to target
Disadvantages
- ❌ Serializes Requests
Threads wait for available connections, destroying race timing
- ❌ Unpredictable Timing
Pool contention introduces significant variance
- ❌ NOT for Race Conditions
Should never be used for race condition testing
- ❌ Complex Behavior
Pool dynamics can be difficult to reason about
Use Cases
Suitable for:
Connection reuse testing
Sequential request patterns
Resource-constrained environments
Testing keep-alive behavior
NOT suitable for:
❌ Race condition testing (destroys timing)
❌ Concurrent request scenarios
❌ Double-spending or inventory races
❌ Any scenario requiring simultaneous execution
Example: Sequential API Testing
states:
sequential_test:
description: "Test API with connection reuse"
request: |
GET /api/data/{{ thread.id }} HTTP/1.1
Authorization: Bearer {{ token }}
race:
threads: 50
sync_mechanism: semaphore
connection_strategy: pooled
pool_size: 10 # Reuse 10 connections
logger:
on_thread_leave: |
[Thread {{ thread.id }}] Completed using pooled connection
When to Use
Use pooled strategy when:
Testing connection reuse behavior
Making sequential requests
Need to limit connections to server
Testing keep-alive functionality
Don’t use pooled strategy when:
Testing race conditions (use preconnect)
Need concurrent execution (use preconnect)
Timing precision matters (use preconnect)
Multiplexed Strategy
The multiplexed strategy uses HTTP/2 multiplexing to send multiple requests over a single connection.
How It Works
Single HTTP/2 connection established
Multiple streams created over connection
Requests multiplexed over streams
Server processes streams concurrently (if supported)
Responses received asynchronously
race:
threads: 20
sync_mechanism: barrier
connection_strategy: multiplexed
http_version: "2" # Required
Performance Characteristics
Race Window: Variable, depends on HTTP/2 support and implementation
Connection Overhead: Single connection for all requests
Memory Usage: Low (single connection)
CPU Usage: Moderate (stream management)
Best For: HTTP/2-specific testing
Visual Representation
Single HTTP/2 Connection
│
├─ Stream 1: Request 1 → Response 1
├─ Stream 2: Request 2 → Response 2
├─ Stream 3: Request 3 → Response 3
├─ Stream 4: Request 4 → Response 4
└─ ...
All streams over single TCP connection
Advantages
- ✅ Single Connection
Minimal connection overhead and resource usage
- ✅ HTTP/2 Features
Access to header compression, server push, etc.
- ✅ Server-Friendly
Single connection to server
- ✅ Efficient for High Thread Counts
No socket descriptor limits
Disadvantages
- ❌ Requires HTTP/2
Not all servers support HTTP/2
- ❌ Complex Implementation
HTTP/2 adds significant complexity
- ❌ Stream Ordering
Server may process streams sequentially despite concurrency
- ❌ Unpredictable Timing
Difficult to achieve precise race windows
Use Cases
Suitable for:
HTTP/2-specific vulnerability testing
Testing stream-based race conditions
Server push vulnerabilities
Header compression attacks
NOT typically suitable for:
Traditional race condition testing (use preconnect)
Double-spending attacks (use preconnect)
Most common vulnerabilities (use preconnect)
Example: HTTP/2 Stream Race
states:
http2_race:
description: "Test HTTP/2 stream handling"
request: |
POST /api/resource HTTP/2
Authorization: Bearer {{ token }}
{"action": "process"}
race:
threads: 10
sync_mechanism: barrier
connection_strategy: multiplexed
logger:
on_state_leave: |
Tested HTTP/2 multiplexing with {{ threads }} streams
When to Use
Use multiplexed strategy when:
Specifically testing HTTP/2 vulnerabilities
Target only supports HTTP/2
Testing stream-based race conditions
Testing server push behavior
Don’t use multiplexed strategy when:
Target doesn’t support HTTP/2
Testing traditional race conditions (use preconnect)
Need predictable timing (use preconnect)
HTTP/1.1 is sufficient (use preconnect)
Comparison Table
Feature |
Preconnect |
Lazy |
Pooled |
Multiplexed |
|---|---|---|---|---|
Race Window |
< 1-10μs |
50-500ms+ |
Variable |
Variable |
Precision |
Excellent |
Poor |
Poor |
Moderate |
Memory Usage |
High |
Low |
Low |
Very Low |
Connection Overhead |
Before race |
In race |
Mixed |
Single conn |
Best For |
Race conditions |
Connection timing |
Sequential testing |
HTTP/2 testing |
Recommended |
✅ Yes |
⚠️ Rarely |
⚠️ Rarely |
⚠️ Specific cases |
Race Testing |
✅ Excellent |
❌ Poor |
❌ Poor |
⚠️ Limited |
Choosing the Right Strategy
Decision Tree
Testing race condition?
│
├─ Yes → Use PRECONNECT
│ (99% of cases)
│
└─ No → Testing HTTP/2?
│
├─ Yes → Use MULTIPLEXED
│
└─ No → Need connection reuse?
│
├─ Yes → Use POOLED
│
└─ No → Testing connection timing?
│
├─ Yes → Use LAZY
│
└─ No → Use PRECONNECT
(safest default)
Quick Reference
For Race Conditions (99% of cases):
race:
connection_strategy: preconnect # Always use this
sync_mechanism: barrier
threads: 20
For HTTP/2 Testing:
race:
connection_strategy: multiplexed
http_version: "2"
For Connection Reuse:
race:
connection_strategy: pooled
pool_size: 10
For Connection Timing:
race:
connection_strategy: lazy
Performance Optimization
Achieving Optimal Timing
To achieve the best possible race window with preconnect:
1. Use Python 3.14t
uv python install 3.14t
uv sync
2. Configure for optimal performance
race:
threads: 20 # Reasonable count
sync_mechanism: barrier # Best timing
connection_strategy: preconnect # Essential!
thread_propagation: single
3. Test on low-latency network
Localhost: Best (< 1ms latency)
Same datacenter: Excellent (< 10ms)
Same region: Good (< 50ms)
Different regions: Poor (> 100ms)
4. Monitor and tune
logger:
on_state_leave: |
Race window: {{ race_window }}μs
{% if race_window < 1 %}
✓ Excellent: Sub-microsecond precision
{% elif race_window < 10 %}
✓ Very Good: Optimal for most races
{% elif race_window < 100 %}
⚠ Good: May need optimization
{% else %}
❌ Poor: Check configuration
{% endif %}
Troubleshooting Connection Issues
Issue: Connection timeouts
Solution:
target:
timeout: 60 # Increase timeout
race:
threads: 10 # Reduce thread count
Issue: Too many open files
Solution:
# Check limit
ulimit -n
# Increase limit
ulimit -n 4096
Issue: Connection refused
Solution:
Check target is accessible
Verify firewall rules
Confirm port is correct
Reduce thread count
Examples
Example 1: Optimal Configuration
metadata:
name: "Optimal Race Configuration"
target:
host: "api.example.com"
port: 443
tls:
enabled: true
verify_cert: true
states:
race_attack:
request: |
POST /api/vulnerable HTTP/1.1
Authorization: Bearer {{ token }}
race:
threads: 20
sync_mechanism: barrier
connection_strategy: preconnect # Optimal!
logger:
on_state_leave: |
Configuration: OPTIMAL
- Strategy: preconnect ✓
- Sync: barrier ✓
- Threads: {{ threads }}
- Race window: {{ race_window }}μs
Example 2: HTTP/2 Testing
states:
http2_test:
request: |
POST /api/resource HTTP/2
Authorization: Bearer {{ token }}
race:
threads: 10
sync_mechanism: barrier
connection_strategy: multiplexed
http_version: "2"
Example 3: Connection Reuse
states:
reuse_test:
request: |
GET /api/data HTTP/1.1
Connection: keep-alive
race:
threads: 50
sync_mechanism: semaphore
connection_strategy: pooled
pool_size: 10
Best Practices
General Guidelines
Default to preconnect for all race condition testing
Use barrier sync with preconnect for best results
Monitor race window and optimize based on results
Test on low-latency network for best timing
Start with moderate thread counts (10-20)
Configuration Checklist
Before running a race attack:
✅ Using
preconnectstrategy✅ Using
barrierorcountdown_latchsync✅ Thread count is reasonable (10-30 typical)
✅ Python 3.14t installed (for best performance)
✅ Network latency is low (< 50ms)
✅ Target system is accessible
Common Mistakes to Avoid
❌ Using lazy for race conditions
# DON'T DO THIS for race testing:
race:
connection_strategy: lazy # Will fail!
❌ Using pooled for race conditions
# DON'T DO THIS for race testing:
race:
connection_strategy: pooled # Serializes requests!
❌ Too many threads with preconnect
# May cause issues:
race:
threads: 500 # Too many connections
connection_strategy: preconnect
✅ Correct configuration
# DO THIS for race testing:
race:
threads: 20
sync_mechanism: barrier
connection_strategy: preconnect
See Also
Synchronization Mechanisms - Synchronization mechanisms
Configuration Reference - Complete YAML reference
Attack Examples - Working examples
Best Practices - Performance optimization
Troubleshooting - Common issues and solutions