Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
63096f2
feat: add fixed_fallback policy for fixed() mode with health-aware fa…
Jun 13, 2026
aa76568
add workflow_dispatch trigger for manual build
Jun 13, 2026
f22e01a
fix: fixed_fallback gracefully falls back when index out of range
Jun 13, 2026
e0a7845
fix: remove unused variable fixedAlive
Jun 13, 2026
d44eb31
feat: add timeout and retry params to fixed_fallback
Jun 13, 2026
f51a8e8
fixed_fallback: add timeout unit suffix (ms/s/m) and rate-limited log…
Jun 13, 2026
179c6ee
fixed_fallback: add dynamic fallback policy support
Jun 13, 2026
0098234
fix: allow up to 4 params in fixed_fallback (was incorrectly limited …
Jun 13, 2026
9383373
fix: type cast in parsePolicyName default return
Jun 13, 2026
cfd0545
fixed_fallback: use FallbackPolicy for AliveDialerSet to enable laten…
Jun 13, 2026
f554498
fixed_fallback: handle random fallback policy correctly
Jun 13, 2026
0d33003
fix: syntax error in FixedWithFallback fallback path
Jun 13, 2026
7082c3c
fix: brace indentation in FixedWithFallback block
Jun 13, 2026
770b083
fix: remove spurious closing brace in FixedWithFallback case
Jun 13, 2026
e7a6f03
docs: add fork enhancements to README
itoywh Jun 13, 2026
b4e114b
docs: add comprehensive fixed_fallback parameter reference and usage …
Jun 13, 2026
2943e82
fix: s/dial_mode/policy/ in README examples
Jun 13, 2026
d09b48e
docs: add Chinese README (README_zh.md) with full parameter reference
Jun 13, 2026
161c6b5
docs: add language switch (English | 简体中文) to both READMEs
Jun 13, 2026
8b63a5c
fix: move language switch to left align
Jun 13, 2026
0dc137e
docs: fix check_tolerance description — only governs fallback nodes, …
Jun 13, 2026
4d599bd
feat: unify fallback policy names with upstream (min_last_latency → min)
Jun 13, 2026
9f4db21
feat: dynamic CheckOpts — skip IPv6/UDP probes when not configured
Jun 13, 2026
bd3093f
fix: split IPv6 skip logic per-protocol (tcp/udp independent)
Jun 13, 2026
4174a97
docs: add dynamic CheckOpts to English and Chinese READMEs
Jun 14, 2026
4ab2d13
debug: print CheckDnsOptionRaw.Raw to diagnose UDP check issue
Jun 14, 2026
c69d284
feat: make connectivity checks fully configuration-driven
Jun 14, 2026
52342a2
docs: update example.dae with configuration-driven check notes
Jun 14, 2026
390a9f8
docs: update READMEs for fully configuration-driven health checks
Jun 14, 2026
0ba247f
feat: add operational logs for node alive/dead transitions
Jun 14, 2026
b1dae4e
docs: add node status operational logging (Section 4) to READMEs
Jun 14, 2026
7a23070
fix(logger): force CST (UTC+8) timezone for log timestamps
Jun 14, 2026
27763ea
ci: auto-update GitHub Release after successful build
Jun 14, 2026
2fbc3c4
ci: auto-generate changelog in Release body on every build
Jun 14, 2026
5261544
ci: add scripts/** to build trigger paths
Jun 14, 2026
813cc24
fix(ci): correct artifact extraction path (build/* prefix)
Jun 14, 2026
43a3773
fix(ci): download-artifact auto-extracts, no unzip needed
Jun 14, 2026
b645d6e
fix(ci): add contents:write permission for release asset management
Jun 14, 2026
bb353b4
fix(gitignore): separate .sisyphus and dae-linux-* patterns
Jun 14, 2026
3d17895
fix(reload): async core.Close in closeTail when BPF is ejected
Jun 18, 2026
2426766
fix: gofmt cleanup, DRY refactor, and add FixedWithFallback tests
Jun 18, 2026
6bab3ab
fix(dialer_selection_policy): correct error message to 1-4 params
Jun 18, 2026
d0f8f98
optimize: skip IP family probe when only one family is configured
Jun 18, 2026
57d3279
feat: upgrade health-check disabled messages from Debug to Warn level
Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
name: Build (Main)

on:
workflow_dispatch:
push:
branches:
- main
Expand All @@ -24,6 +25,7 @@ on:
- "go.sum"
- ".github/workflows/build.yml"
- ".github/workflows/seed-build.yml"
- "scripts/**"
- "Makefile"

jobs:
Expand Down Expand Up @@ -52,3 +54,34 @@ jobs:
check-run-id: "dae-bot[bot]/main-build-passed"
check-run-conclusion: ${{ needs.build.result }}
secrets: inherit

update-release:
if: needs.build.result == 'success'
needs: [build]
runs-on: ubuntu-latest
permissions:
contents: write # Required for release asset upload/delete
steps:
- name: Checkout codebase
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Download x86_64 artifact
uses: actions/download-artifact@v4
with:
name: dae-linux-x86_64.zip
path: /tmp/dae_artifact/

- name: Verify dae binary (auto-extracted by download-artifact)
run: |
ls -lh /tmp/dae_artifact/dae-linux-x86_64
chmod +x /tmp/dae_artifact/dae-linux-x86_64

- name: Update GitHub Release (delete old + upload new + update body)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPO: ${{ github.repository }}
GITHUB_SHA: ${{ github.sha }}
run: |
python3 scripts/update-release.py "338955206" "/tmp/dae_artifact/dae-linux-x86_64"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ node_modules/
venv
CLAUDE.md
AGENTS.md
.sisyphus
.sisyphus
dae-linux-*
279 changes: 279 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,285 @@
<img src="https://custom-icon-badges.herokuapp.com/github/last-commit/daeuniverse/dae?logo=history&logoColor=white" alt="lastcommit"/>
</p>

<p align="left">
<a href="README.md">English</a> | <a href="README_zh.md">简体中文</a>
</p>

> **This is an enhanced Fork** that adds full disaster recovery to the `fixed` dialing mode.
> Pre-built binaries are available at [Releases](https://github.com/itoywh/dae/releases).

## Fork Enhancements

### 1. fixed_fallback — Disaster-Recoverable Dialing Strategy

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.

---

#### Quick Start

```ini
# In dae config file (e.g. /etc/dae/config.dae):
[group]
my_group {
policy: fixed_fallback(1, 5s, 3)
}

[global]
check_tolerance: 60ms
```

Meaning: 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.

---

#### Parameter Reference

```
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 |

##### Fallback Policy Options

| 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 |

##### `check_tolerance` with `fixed_fallback`

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.

```ini
[global]
check_tolerance: 60ms # During failover: s2↔s5 switch only if latency diff ≥60ms
```

> **Important**: 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_tolerance` governs "which backup is better", not "has the primary recovered".

---

#### How It Works

```
┌─ 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) │
│ │
└─────────────────────────────────────────────────────────────┘
```

---

#### Configuration Examples

**Example A: Simple failover** — prefer s4, fall back to other alive nodes on failure:
```ini
[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:
```ini
[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:
```ini
[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:
```ini
policy: fixed_fallback(0, 500ms, 1, min)
```

---

#### Compatibility

- **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_fallback` is a new policy; others (`random`, `min_moving_avg`, etc.) unchanged

---

### 2. Log Timestamp Format Optimization

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).

---

### 3. Fully Configuration-Driven Health Checks

> Corresponding upstream PR: [daeuniverse/dae#1011](https://github.com/daeuniverse/dae/pull/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.

#### Core Rule

> **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. |

#### Zero-Check Config (Maximum Performance)

```ini
global {
# No tcp_check_url, no udp_check_dns, no check_interval
# → Zero connectivity checks. Zero network probes. Maximum performance.
log_level: info
}
```

#### TCP Check Only

```ini
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)
```

#### TCP + UDP, IPv4 Only (No IPv6)

```ini
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)
```

#### Full Checks (IPv4 + IPv6)

```ini
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)
```

#### Debug: View Current Probe Configuration

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`, and `check_interval` to your config. They no longer have defaults.

---

### 4. Node Status Operational Logging

To enable fine-grained operational analysis without enabling debug mode, this Fork adds WARN/INFO-level logs for node state transitions:

#### Node-Level Events

| 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`) |

#### Fixed-Fallback Events (existing)

| 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 Guide

| `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 |

#### Example Log Output

```
[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
```

---

### Upstream PRs

- [PR #1009](https://github.com/daeuniverse/dae/pull/1009) — fixed_fallback disaster recovery enhancement
- [PR #1010](https://github.com/daeuniverse/dae/pull/1010) — Log timestamp format optimization
- [PR #1011](https://github.com/daeuniverse/dae/pull/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.
Expand Down
Loading
Loading