This is an enhanced Fork that adds full disaster recovery to the
fixeddialing mode. Pre-built binaries are available at Releases.
The stock fixed mode has a critical flaw: if the fixed node fails, traffic is cut off immediately — no failover mechanism, a single point of failure.
This Fork introduces fixed_fallback, upgrading fixed from a fragile single-node mode to a production-grade high-availability strategy with full disaster recovery semantics.
# In dae config file (e.g. /etc/dae/config.dae):
[group]
my_group {
policy: fixed_fallback(1, 5s, 3)
}
[global]
check_tolerance: 60msMeaning: prefer the 2nd node in the group (index 1, 0-based). On failure, timeout 5s per attempt, retry up to 3 times → on total failure, auto-switch to the best alive node. When the fixed node recovers, switch back immediately. 60ms tolerance prevents flapping between s2/s5 during failover.
fixed_fallback(<index>, <timeout>, <retries>[, <fallback_policy>])
| # | Parameter | Required | Description | Example |
|---|---|---|---|---|
| 1 | index |
✅ | 0-based index of the fixed node in the group's node list. | 1 → use 2nd node |
| 2 | timeout |
✅ | Timeout per connection attempt. Supports unit suffixes: ms, s, m. No suffix = seconds (backward compatible). |
5s, 500ms, 2m, 10 |
| 3 | retries |
✅ | Max retries before declaring the fixed node dead and triggering fallback. WARN-level log on exhaustion. | 3 |
| 4 | fallback_policy |
❌ | Policy for selecting fallback node after fixed node failure. Defaults to min_moving_avg. |
See below |
| Policy | Behavior | Use Case |
|---|---|---|
min_moving_avg ⭐ (default) |
Select node with lowest moving average latency. Works with check_tolerance to prevent flapping. |
General — latency-sensitive traffic |
min |
Select node with lowest last measured latency (official name, same as min_last_latency). |
Environments with rapidly changing network conditions |
min_avg10 |
Select node with lowest average latency over last 10 checks. | Environments with high latency variance |
random |
Randomly select from alive fallback nodes. | Load balancing — spread traffic across fallback pool |
The check_tolerance config (under [global]) works with the fallback policy, only affecting fallback node selection during disaster recovery, preventing s2/s5 from flapping due to minor latency fluctuations.
[global]
check_tolerance: 60ms # During failover: s2↔s5 switch only if latency diff ≥60msImportant: s4 recovery is completely independent of
check_tolerance. Once s4 passes the liveness check, traffic switches back immediately unconditionally — no latency comparison, no tolerance threshold.check_tolerancegoverns "which backup is better", not "has the primary recovered".
┌─ Normal Flow ────────────────────────────────────────────┐
│ │
│ fixed_fallback(1, 5s, 3, min_moving_avg) │
│ │
│ 1. Try fixed node (index 1 = 2nd node) │
│ ├─ Alive? → Use it. Done ✅ │
│ └─ Dead/timeout? → Retry (max 3 times) │
│ │
│ 2. All 3 retries exhausted? │
│ └─ WARN log: "fixed dialer retries exhausted (3/3)" │
│ → Select best node by min_moving_avg │
│ → INFO log: "falling back to <node>" │
│ │
│ 3. Connectivity Checker periodically probes fixed node │
│ └─ Fixed node recovered? → Switch back immediately 🔄 │
│ (no check_tolerance comparison, alive = switch) │
│ │
└─────────────────────────────────────────────────────────────┘
Example A: Simple failover — prefer s4, fall back to other alive nodes on failure:
[group]
my_group {
nodes: s4, s2, s5
policy: fixed_fallback(0, 5s, 3)
}Example B: Random fallback (load balancing) — spread fallback traffic across backup nodes:
[group]
my_group {
nodes: s4, s2, s5
policy: fixed_fallback(0, 3s, 2, random)
}Example C: With check_tolerance — prevent oscillation when latencies are close:
[global]
check_tolerance: 80ms
[group]
my_group {
nodes: s_hk, s_jp, s_sg
policy: fixed_fallback(0, 5s, 3) # min_moving_avg (default)
}Example D: Aggressive timeout — fast failure, single retry:
policy: fixed_fallback(0, 500ms, 1, min)- Backward compatible: existing
fixed_fallback(1, 5s, 3)config works without changes - Timeout unit backward compatible:
fixed_fallback(1, 5, 3)(no unit) still works — treated as seconds - Coexists with all existing policies:
fixed_fallbackis a new policy; others (random,min_moving_avg, etc.) unchanged
Log timestamps now use human-readable format with ForceFormatting enabled:
Before: INFO selected dialer: s4 ...
After: [2026-06-13 15:04:05] INFO selected dialer: s4 ...
The [YYYY-MM-DD HH:MM:SS] prefix aids readability and is compatible with standard log parsing tools (grep, awk, log viewers).
Corresponding upstream PR: daeuniverse/dae#1011
Stock dae hardcodes default values for tcp_check_url, udp_check_dns, and check_interval, so health checks always run even when the user didn't explicitly configure them. This Fork makes all health check options fully optional — if you don't write it, it won't check.
Health checks are now completely opt-in. Nothing runs unless you explicitly configure it.
| Config | Behavior |
|---|---|
check_interval not set or 0s |
All health checks disabled. Zero network probes. |
tcp_check_url not set |
No TCP connectivity checks at all. |
tcp_check_url set (IPv4 only) |
TCP IPv4 check only. IPv6 TCP probe auto-skipped. |
tcp_check_url set (with IPv6) |
TCP IPv4 + IPv6 checks both run. |
udp_check_dns not set |
No UDP DNS connectivity checks at all. |
udp_check_dns set (IPv4 only) |
UDP IPv4 DNS check only. IPv6 UDP probe auto-skipped. |
udp_check_dns set (with IPv6) |
UDP IPv4 + IPv6 DNS checks both run. |
global {
# No tcp_check_url, no udp_check_dns, no check_interval
# → Zero connectivity checks. Zero network probes. Maximum performance.
log_level: info
}global {
tcp_check_url: 'http://cp.cloudflare.com,1.1.1.1'
check_interval: 60s
# udp_check_dns not set → no UDP checks
}
# Actual probes: tcp4 only (1 probe)global {
tcp_check_url: 'http://cp.cloudflare.com,1.1.1.1'
udp_check_dns: 'dns.google:53,8.8.8.8'
check_interval: 60s
check_tolerance: 50ms
}
# Actual probes: tcp4 + udp4_dns (2 probes, no IPv6)global {
tcp_check_url: 'http://cp.cloudflare.com,1.1.1.1,2606:4700:4700::1111'
udp_check_dns: 'dns.google:53,8.8.8.8,2001:4860:4860::8888'
check_interval: 60s
}
# Actual probes: tcp4 + tcp6 + udp4_dns + udp6_dns (4 probes)With log_level: debug, dae outputs the probe configuration for each dialer on startup:
DEBUG Connectivity check probes configured dialer=my-node tcp4=true tcp6=false udp4_dns=false udp6_dns=false
DEBUG Connectivity check disabled (check_interval=0) dialer=my-node
Migration note: If you upgrade from stock dae and want health checks, you must now explicitly add
tcp_check_url,udp_check_dns, andcheck_intervalto your config. They no longer have defaults.
To enable fine-grained operational analysis without enabling debug mode, this Fork adds WARN/INFO-level logs for node state transitions:
| Event | Level | Trigger |
|---|---|---|
Node became DEAD |
WARN | Node transitions from ALIVE → DEAD (shows network type) |
Node became ALIVE |
INFO | Node recovers from DEAD → ALIVE (shows latency + network) |
| Event | Level | Trigger |
|---|---|---|
fixed dialer dead, starting retry |
WARN | Fixed node detected DEAD, entering retry phase |
fixed dialer retry N/M |
INFO | Per-retry attempt count |
fixed dialer retries exhausted, falling back |
WARN | All retries exhausted, switching to fallback pool |
fixed dialer recovered, traffic returned |
INFO | Fixed node revived, traffic unconditionally switched back |
log_level |
What You See |
|---|---|
info |
All WARN/INFO operational events above — ideal for production monitoring |
debug |
Everything above + per-check latency data + probe configuration — for deep analysis |
[2026-06-14 02:02:36] WARN Node became DEAD dialer=s4 network=tcp4
[2026-06-14 02:02:45] WARN fixed dialer dead, starting retry (timeout=3s, max_retries=3)
[2026-06-14 02:03:19] INFO fixed dialer retry 1/3
[2026-06-14 02:03:36] WARN fixed dialer retries exhausted (3/3), falling back to min_moving_avg
[2026-06-14 02:15:00] INFO Node became ALIVE dialer=s4 network=tcp4 latency=156ms
[2026-06-14 02:15:00] INFO fixed dialer recovered, traffic returned
- PR #1009 — fixed_fallback disaster recovery enhancement
- PR #1010 — Log timestamp format optimization
- PR #1011 — Fully configurable health checks (opt-in)
dae, means goose, is a high-performance transparent proxy solution.
To enhance traffic split performance as much as possible, dae employs the transparent proxy and traffic split suite within the Linux kernel using eBPF. As a result, dae can enable direct traffic to bypass the proxy application's forwarding, facilitating genuine direct traffic passage. Through this remarkable feat, there is minimal performance loss and negligible additional resource consumption for direct traffic.
As a successor of v2rayA, dae abandoned v2ray-core to meet the needs of users more freely.
- Implement
Real Directtraffic split (need ipforward on) to achieve high performance. - Support to split traffic by process name in local host.
- Support to split traffic by MAC address in LAN.
- Support to split traffic with invert match rules.
- Support to automatically switch nodes according to policy. That is to say, support to automatically test independent TCP/UDP/IPv4/IPv6 latencies, and then use the best nodes for corresponding traffic according to user-defined policy.
- Support advanced DNS resolution process.
- Support full-cone NAT for shadowsocks, trojan(-go) and socks5 (no test).
- Support various trending proxy protocols, seen in proxy-protocols.md.
Please refer to Quick Start Guide to start using dae right away!
- If you setup dae and also a shadowsocks server (or any UDP servers) on the same machine in public network, such as a VPS, don't forget to add
l4proto(udp) && sport(your server ports) -> must_directrule for your UDP server port. Because states of UDP are hard to maintain, all outgoing UDP packets will potentially be proxied (depends on your routing), including traffic to your client. This behaviour is not what we want to see.must_directmakes all traffic from this port including DNS traffic direct. - If users in mainland China find that the first screen time is very long when they visit some domestic websites for the first time, please check whether you use foreign DNS to handle some domestic domain in DNS routing. Sometimes this is hard to spot. For example,
ocsp.digicert.cnis included ingeosite:geolocation-!cnunexpectedly, which will cause some tls handshakes to take a long time. Be careful to use such domain sets in DNS routing.
See How it works.
- Automatically check dns upstream and source loop (whether upstream is also a client of us) and remind the user to add sip rule.
- MACv2 extension extraction.
- Log to userspace.
- Protocol-oriented node features detecting (or filter), such as full-cone (especially VMess and VLESS).
- Add quick-start guide
- ...
Special thanks goes to all contributors. If you would like to contribute, please see the instructions. Also, it is recommended following the commit-msg-guide.
