package trafficmode import ( "fmt" "strconv" "strings" ) type RunCommandFunc func(name string, args ...string) (stdout string, stderr string, code int, err error) type RulesConfig struct { RoutesTableName string Mark string MarkIngress string PrefSelective int PrefFull int PrefMarkIngressReply int ModeFull string ModeSelective string ModeDirect string } type RulesState struct { Mark bool Full bool IngressReply bool } func PrefStr(v int) string { return strconv.Itoa(v) } func ReadRules(cfg RulesConfig, run RunCommandFunc) RulesState { if run == nil { return RulesState{} } out, _, _, _ := run("ip", "rule", "show") var st RulesState for _, line := range strings.Split(out, "\n") { l := strings.ToLower(strings.TrimSpace(line)) if l == "" { continue } fields := strings.Fields(l) if len(fields) == 0 { continue } prefRaw := strings.TrimSuffix(fields[0], ":") pref, _ := strconv.Atoi(prefRaw) switch pref { case cfg.PrefSelective: if strings.Contains(l, "lookup "+cfg.RoutesTableName) { st.Mark = true } case cfg.PrefFull: if strings.Contains(l, "lookup "+cfg.RoutesTableName) { st.Full = true } case cfg.PrefMarkIngressReply: if strings.Contains(l, "fwmark "+strings.ToLower(cfg.MarkIngress)) && strings.Contains(l, "lookup main") { st.IngressReply = true } } } return st } func DetectAppliedMode(cfg RulesConfig, rules RulesState) string { if rules.Full { return cfg.ModeFull } if rules.Mark { return cfg.ModeSelective } return cfg.ModeDirect } func ProbeMode(cfg RulesConfig, mode string, iface string, run RunCommandFunc) (bool, string) { if run == nil { return false, "run command func is nil" } mode = strings.ToLower(strings.TrimSpace(mode)) iface = strings.TrimSpace(iface) args := []string{"-4", "route", "get", "1.1.1.1"} if mode == strings.ToLower(cfg.ModeSelective) { args = append(args, "mark", cfg.Mark) } out, _, code, err := run("ip", args...) if err != nil || code != 0 { if err == nil { err = fmt.Errorf("ip route get exited with %d", code) } return false, err.Error() } text := strings.ToLower(out) switch mode { case strings.ToLower(cfg.ModeDirect): if strings.Contains(text, " table "+strings.ToLower(cfg.RoutesTableName)) { return false, "route probe still uses agvpn table" } return true, "route probe direct path ok" case strings.ToLower(cfg.ModeFull), strings.ToLower(cfg.ModeSelective): if iface == "" { return false, "route probe has empty iface" } if !strings.Contains(text, "dev "+strings.ToLower(iface)) { return false, fmt.Sprintf("route probe mismatch: expected dev %s", iface) } return true, "route probe vpn path ok" default: return false, "route probe unknown mode" } }