package detect // DetectConfig holds all tunable thresholds for the detection engine. // All fields are filled with production defaults by DefaultConfig(). // 15F (hot-reload) may overwrite fields at runtime; the Engine reads via a // pointer so updates take effect on the next Tick without a restart. type DetectConfig struct { // DomesticFailNumerator and DomesticFailDenominator define the ISP-failure // fraction that triggers the suspect rule. // Default: 2/3 (two out of every three domestic ISPs must fail). DomesticFailNumerator int // 2 DomesticFailDenominator int // 3 // SuspectStreakMin is the number of consecutive failing cycles required to // transition a node from "up" to "blocked_suspect". // Default: 2 cycles (10 min at the 5-min tick period). SuspectStreakMin int // TrafficDropThreshold is the minimum percentage drop in online-connection // count over the 15-minute window that activates the traffic-warning rule. // Default: 80.0 %. TrafficDropThreshold float64 // TrafficBaselineMin is the minimum current online-connection count for // the traffic-warning relaxation to apply. Below this threshold the node // may simply be idle rather than experiencing user flight. // Default: 20 connections. TrafficBaselineMin int // ConfirmedStreakMin is the number of consecutive cycles that a node must // spend in "blocked_suspect" before being promoted to "blocked_confirmed". // Default: 6 cycles (30 min). ConfirmedStreakMin int // RecoverStreakMin is the number of consecutive passing cycles while in // "blocked_suspect" required to recover the node back to "up". // Default: 2 cycles (10 min). RecoverStreakMin int // SuspectWeight is the routing weight applied when a node first enters // "blocked_suspect", reducing traffic directed to it. // Default: 10. SuspectWeight int } // DefaultConfig returns a DetectConfig pre-filled with production defaults. func DefaultConfig() DetectConfig { return DetectConfig{ DomesticFailNumerator: 2, DomesticFailDenominator: 3, SuspectStreakMin: 2, TrafficDropThreshold: 80.0, TrafficBaselineMin: 20, ConfirmedStreakMin: 6, RecoverStreakMin: 2, SuspectWeight: 10, } } // isSuspectTriggered reports whether the domestic ISP-failure rate meets or // exceeds the configured fraction (default ≥ 2/3). // // Integer arithmetic avoids floating-point rounding: // // fail/total >= num/den ↔ fail × den >= total × num func (cfg *DetectConfig) isSuspectTriggered(sig NodeSignal) bool { if sig.DomesticTotalISPs == 0 { return false // no domestic probe data this cycle; cannot judge } return sig.DomesticFailISPs*cfg.DomesticFailDenominator >= sig.DomesticTotalISPs*cfg.DomesticFailNumerator } // isFault reports whether the node appears to have a local outage rather than // a domestic censorship event. // // Condition: domestic probes failing AND overseas probes also failing. // When both sides are down the most likely cause is a node-level failure // (hardware, network, crashed process) rather than GFW interference. // // The fault rule takes precedence over all other rules and must be evaluated // first in the processing loop. func isFault(sig NodeSignal) bool { return sig.DomesticFailISPs > 0 && sig.OverseasHasData && !sig.OverseasOK } // effectiveSuspectStreakMin returns the cycle threshold required to enter // "blocked_suspect", relaxed to 1 when the traffic-warning rule is active. func (cfg *DetectConfig) effectiveSuspectStreakMin(sig NodeSignal) int { if cfg.isTrafficWarning(sig) { return 1 } return cfg.SuspectStreakMin } // isTrafficWarning reports whether the 15-minute traffic-drop signal exceeds // the threshold and the baseline is large enough to be meaningful. func (cfg *DetectConfig) isTrafficWarning(sig NodeSignal) bool { return sig.TrafficDropPct >= cfg.TrafficDropThreshold && sig.TrafficBaseline >= cfg.TrafficBaselineMin }